POST /api/v1/profile/requisite-templates/{templateId}/archive — Архивация шаблона реквизитов (archiveRequisiteTemplate)
Назначение
Архивировать (soft-delete) шаблон реквизитов инвестора: шаблон скрывается из выбора в новых бронях и
сделках CRM, но ранее выданные снапшоты остаются в crm неизменными (снапшот — иммутабельная копия на
момент выдачи, requisite-template.md, INV-RQT-8). Если архивируемый
шаблон был по умолчанию (IsDefault = true), в том же согласованном изменении сбрасывается
Profile.DefaultRequisiteTemplateId → null (INV-PRF-5, profile.md) — форма брони
перестаёт предзаполняться. Персона и документы, на которые ссылается шаблон, архивацию не блокируют и
не затрагиваются.
| Поле | Значение |
|---|
| Сервис/контекст | profile |
| API area | public |
| Feature group | requisites |
| Статус | draft |
| Документ | docs/06-services/profile/api/public/archive-requisite-template.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Bearer JWT), UserType = Investor |
| Auth policy / Permissions | authenticated; permission profile.requisites.manage-self (INV-RQT-6) |
| Scope (RBAC) | self |
| Record-level | OwnerUserId = UserId (sub); чужой templateId → 404 |
| Tenant isolation | не применяется (ADR-0052: инвестор вне tenant; изоляция по OwnerUserId) |
| Audit | delete (soft): DeletedAt = now, DeletedByUserId = UserId, Version++, RequisiteTemplateArchivedEvent |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/v1/profile/requisite-templates/{templateId}/archive |
| Success status | 200 |
| Idempotency header | не применяется (повторный archive архивного шаблона — идемпотентный no-op 200) |
| Correlation | OpenTelemetry trace (traceparent) |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
UserId | identity claims (sub) | Guid | Да | валидный JWT | 401 без токена |
templateId | route | Guid | Да | NotEmpty; шаблон владельца | чужой → 404 |
Модель ответа RequisiteTemplateModel
Дом модели — list-requisite-templates.md (список — постраничный);
возвращается заархивированный шаблон со status = Archived, isDefault = false. Cipher-поля контактов
наружу отдаются только маской (NumberMask), состав — идентификаторами документов
(requisite-template.md).
Алгоритм
- Контекст и доступ. Операцию выполняет инвестор (
UserId = sub) с permission
profile.requisites.manage-self (INV-RQT-6). Tenant не применяется (ADR-0052).
- Проверка входа.
templateId не пустой — иначе ValidationError.
- Проверка профиля и шаблона. Профиль нужен и для guard-ов, и для возможного сброса default; отсутствие →
PROFILE_NOT_FOUND; guard-ы self-service — ContactsManagedBy != TalentSync (INV-PRF-2 →
PROFILE_MANAGED_EXTERNALLY), Status = Active (INV-PRF-3 → PROFILE_USER_BLOCKED), UserType = Investor
(INV-PRF-4/INV-RQT-6 → PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE). Шаблон должен принадлежать владельцу —
иначе PROFILE_TEMPLATE_NOT_FOUND (чужой не отличаем от несуществующего). Уже Status = Archived —
идемпотентный успех. Ссылок, блокирующих архив, нет: персона/документы шаблона не проверяются (архивный
шаблон просто не выдаёт снапшот).
- Переход и сброс default. Запоминаем
wasDefault = template.IsDefault. Шаблон переходит
Active → Archived (stateDiagram requisite-template.md):
Status = Archived, IsDefault = false, DeletedAt = now, DeletedByUserId = UserId. Если wasDefault —
в том же изменении Profile.DefaultRequisiteTemplateId → null (INV-PRF-5); сброс IsDefault снимает
partial-unique ux_requisite_templates_owner_default.
- События. Только доменное
RequisiteTemplateArchivedEvent (Id, OwnerUserId). Интеграционных
(Kafka) событий нет: шаблоны реквизитов в Kafka не публикуются (simplicity-first, ADR-0056; PII в шину не
выносится) — requisite-template.md.
- Согласованность. Шаблон (+ профиль, если был default) фиксируются в одном согласованном изменении;
строка шаблона сохраняется (soft-delete), ссылка
DefaultRequisiteTemplateId обнуляется; конфликт версии →
PROFILE_CONCURRENCY_CONFLICT.
- Результат.
RequisiteTemplateModel (status = Archived, isDefault = false). HTTP 200. Снапшоты в
сделках CRM не затрагиваются (владение crm после выдачи).
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|
| Integration events | none (шаблоны в Kafka не публикуются — ADR-0056; только доменное RequisiteTemplateArchivedEvent) |
| External calls | none |
| Cache / projections / search | сброс Profile.DefaultRequisiteTemplateId при архиве default-шаблона (форма брони теряет предзаполнение) |
| Notifications | none |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
| — (auth) | 401 | Нет/просрочен JWT | без тела | re-login |
ValidationError | 400 | Пустой templateId | RestApiValidationErrorResponse | no retry |
PROFILE_NOT_FOUND | 404 | Профиль ещё не создан консюмером identity | «Профиль ещё создаётся» | retry |
PROFILE_USER_BLOCKED | 400 | Profile.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 | UserType != Investor (INV-PRF-4/INV-RQT-6) | «Доступно только инвесторам» | no retry |
PROFILE_TEMPLATE_NOT_FOUND | 404 | Нет шаблона / чужой OwnerUserId | «Шаблон реквизитов не найден» | no retry |
PROFILE_CONCURRENCY_CONFLICT | 409 | Гонка RowVersion (xmin) шаблона/профиля | «Данные изменены, повторите» | retry |
Совместимость и наблюдаемость
- Семантика archive стабильна: hard-delete в public API не появится (ADR-0053); обратной публичной
операции
Restore в реестре README не заявлено (доменный переход Archived → Active зарезервирован
в requisite-template.md, но endpoint отсутствует).
- Logs:
UserId, TemplateId, wasDefault (без значений реквизитов/PII); metrics:
profile_api_operations_total{operation="archiveRequisiteTemplate",result}, latency p95; traces —
OTel; dashboard profile-overview.
Acceptance Criteria
- Happy path (не default). Given активный шаблон владельца с
IsDefault = false; When archive; Then 200, Active → Archived (IsDefault = false), поднято доменное RequisiteTemplateArchivedEvent; ранее выданные снапшоты в crm не затронуты.
- Архивация default. Given шаблон был
IsDefault = true; When archive; Then в том же изменении Profile.DefaultRequisiteTemplateId = null (INV-PRF-5) — форма брони теряет предзаполнение.
- Идемпотентность. Given шаблон уже
Archived; When archive; Then 200 (no-op); сброса default и события нет.
- Чужой/несуществующий. Given чужой или отсутствующий
templateId; When archive; Then 404 PROFILE_TEMPLATE_NOT_FOUND (чужой неотличим от несуществующего).
- Ссылки не блокируют. Given шаблон ссылается на персону/документы; When archive; Then архив проходит — персона и документы не проверяются и не затрагиваются.
- Гонка. Given параллельная правка шаблона/профиля; When обе операции фиксируются; Then победитель —
200, проигравший — 409 PROFILE_CONCURRENCY_CONFLICT.
- Kafka молчит. Given шаблон заархивирован; When смотрят шину; Then интеграционных событий нет (шаблоны реквизитов в Kafka не публикуются, ADR-0056).
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы