POST /api/v1/profile/requisite-templates/{templateId}/set-default — Шаблон по умолчанию (setDefaultTemplate)
Назначение
Пометить активный шаблон реквизитов инвестора как «по умолчанию» (IsDefault = true) — для быстрого
предзаполнения формы бронирования выбранными реквизитами (requisite-template.md,
INV-RQT-5). Операция снимает флаг с прежнего default-шаблона и синхронизирует
Profile.DefaultRequisiteTemplateId (profile.md, INV-PRF-5) — в одной
транзакции, чтобы у владельца всегда был не более чем один default и проекция в профиле оставалась
согласованной. Вызывает инвестор из ЛК.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | profile |
| API area | public |
| Feature group | requisites |
| Статус | draft |
| Документ | docs/06-services/profile/api/public/set-default-template.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Bearer JWT), UserType = Investor |
| Auth policy / Permissions | authenticated; permission profile.requisites.manage-self |
| Scope (RBAC) | self |
| Record-level | OwnerUserId = UserId (sub); чужой templateId → 404 |
| Tenant isolation | не применяется (ADR-0052; инвестор вне tenant) |
| Audit | update: IsDefault (прежний → false, текущий → true) + Profile.DefaultRequisiteTemplateId, Version++ на всех, UpdatedByUserId = UserId, событие RequisiteTemplateChangedEvent |
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/v1/profile/requisite-templates/{templateId}/set-default |
| Success status | 200 |
| Idempotency header | не применяется (повтор для уже-default шаблона — идемпотентный no-op 200) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
UserId | identity claims (sub) | Guid | Да | валидный JWT | 401 без токена |
templateId | route | Guid | Да | NotEmpty; активный шаблон владельца | чужой → 404 |
Модель ответа RequisiteTemplateModel
Дом модели — list-requisite-templates.md; поля-в-поле — из
requisite-template.md, контакты — только маски. Возвращается целевой
шаблон с isDefault = true и status = Active.
Алгоритм
- Контекст и доступ. Операцию выполняет инвестор (
UserIdиз токена); tenant не применяется; требуется правоprofile.requisites.manage-self. - Проверка входа.
templateIdзадан. Нарушение →ValidationError. - Проверка предусловий. Профиль владельца существует; иначе
PROFILE_NOT_FOUND(реплика identity ещё догоняется — retry). Профиль в режиме самоуправления (ContactsManagedBy = SelfService, иначеPROFILE_MANAGED_EXTERNALLY, INV-PRF-2), активен (Status = Active, иначеPROFILE_USER_BLOCKED, INV-PRF-3), тип пользователя —Investor(иначеPROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE, INV-PRF-4). ШаблонtemplateIdсуществует и принадлежит владельцу; иначеPROFILE_TEMPLATE_NOT_FOUND; шаблон активен (Status = Active), иначеPROFILE_TEMPLATE_ARCHIVED(default назначается только активному, INV-RQT-5). Определяется прежний default-шаблон владельца (может отсутствовать или совпадать с целевым). - Бизнес-правила и переходы. Если целевой шаблон уже default — идемпотентный no-op (шаг 6, без
изменений). Иначе (INV-RQT-5, SingleDefault): снять флаг с прежнего default (
IsDefault = false,Version++), пометить целевой (IsDefault = true,Version++); кросс-агрегатная синхронизация —Profile.DefaultRequisiteTemplateId = templateId(Version++, INV-PRF-5); фиксируется фактRequisiteTemplateChangedEvent(changedFields: ["IsDefault"]). - Согласованность. В одном согласованном изменении сохраняются оба шаблона и профиль; порядок — снять
прежний флаг до установки нового, чтобы уложиться в требование единственного default. Гонка с
параллельным
set-defaultдругого шаблона →PROFILE_TEMPLATE_DEFAULT_CONFLICT(retry); гонка по версии агрегата →PROFILE_CONCURRENCY_CONFLICT(retry). - Результат. Модель
RequisiteTemplateModel(целевой шаблон,isDefault = true,status = Active); HTTP200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Проекции | Profile.DefaultRequisiteTemplateId обновляется в той же транзакции (INV-PRF-5) |
Интеграционные события в шину не публикуются (ADR-0056; DefaultRequisiteTemplateId читается формой брони
синхронно, PII в шину не выносится).
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
| — (auth) | 401 | Нет/просрочен JWT | без тела | re-login |
ValidationError | 400 | Пустой templateId | перечень невалидных полей | no retry |
PROFILE_NOT_FOUND | 404 | Профиль не создан (реплика identity отстаёт) | «Профиль ещё создаётся» | retry |
PROFILE_USER_BLOCKED | 400 | Status != Active (INV-PRF-3) | «Учётная запись заблокирована» | no retry |
PROFILE_MANAGED_EXTERNALLY | 403 | ContactsManagedBy = TalentSync (INV-PRF-2) | «Данные управляются внешней системой» | no retry |
PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE | 403 | не Investor (INV-PRF-4) | «Доступно только инвесторам» | no retry |
PROFILE_TEMPLATE_NOT_FOUND | 404 | Нет шаблона / чужой | «Шаблон реквизитов не найден» | no retry |
PROFILE_TEMPLATE_ARCHIVED | 409 | Status = Archived (INV-RQT-5) | «Архивный шаблон нельзя сделать основным» | после восстановления |
PROFILE_TEMPLATE_DEFAULT_CONFLICT | 409 | Гонка default (INV-RQT-5/INV-PRF-5) | «Данные изменены, повторите» | retry |
PROFILE_CONCURRENCY_CONFLICT | 409 | Гонка версии агрегата | «Данные изменены, повторите» | retry |
Совместимость и наблюдаемость
- Семантика стабильна; расширения
RequisiteTemplateModel— additive (optional-поля без breaking change); удаление/переименование/смена типа — breaking (migration + rollout). - Logs:
UserId,templateId,previousDefaultId; metrics:profile_api_operations_total{operation="setDefaultTemplate",result}(в т.ч.result=default_conflict), latency p99; traces — OTel; dashboardprofile-overview.
Acceptance criteria
- Дано активный шаблон владельца, не являющийся default, и другой шаблон-default; когда владелец делает целевой основным; тогда прежний default →
IsDefault = false, целевой →true,Profile.DefaultRequisiteTemplateId = templateId— всё в одной транзакции; у владельца остаётся ровно один default; HTTP200. - Дано у владельца нет ни одного default; когда делает шаблон основным; тогда целевой становится единственным default,
Profile.DefaultRequisiteTemplateIdзаполняется (INV-PRF-5). - Дано целевой шаблон уже default; когда повтор set-default; тогда идемпотентный no-op
200, версии не растут. - Дано шаблон в
Archived; когда set-default; тогдаPROFILE_TEMPLATE_ARCHIVED(409) — default назначается только активному (INV-RQT-5). - Дано
templateIdчужой или не существует; когда set-default; тогдаPROFILE_TEMPLATE_NOT_FOUND(404). - Дано два параллельных set-default разных шаблонов владельца; когда оба коммитятся; тогда один выигрывает, второй получает
PROFILE_TEMPLATE_DEFAULT_CONFLICT/PROFILE_CONCURRENCY_CONFLICT(409) и повторяет — инвариант «≤ 1 default» сохраняется. - Дано профиль заблокирован /
TalentSync/ не инвестор; когда set-default; тогда соответствующий guard-отказ.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (5): GET /api/v1/profile/requisite-templates — Список шаблонов реквизитов (listRequisiteTemplates), POST /api/v1/profile/requisite-templates — Создание шаблона реквизитов (createRequisiteTemplate), POST /api/v1/profile/requisite-templates/{templateId}/archive — Архивация шаблона реквизитов (archiveRequisiteTemplate), Profile API — реестр операций областей public / internal / admin, PUT /api/v1/profile/requisite-templates/{templateId} — Изменение шаблона реквизитов (updateRequisiteTemplate)
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: requisite-template.md (INV-RQT-5 SingleDefault, метод
SetDefault), profile.md (INV-PRF-5 DefaultTemplateConsistency,DefaultRequisiteTemplateId, методSetDefaultTemplate). - Модель ответа и соседние операции группы: list-requisite-templates.md, create-requisite-template.md, archive-requisite-template.md.