Перейти к основному содержимому

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) — форма брони перестаёт предзаполняться. Персона и документы, на которые ссылается шаблон, архивацию не блокируют и не затрагиваются.

Metadata

ПолеЗначение
Сервис/контекстprofile
API areapublic
Feature grouprequisites
Статусdraft
Документdocs/06-services/profile/api/public/archive-requisite-template.md

Актор и доступ

ПроверкаЗначение
Actor typeUser (Bearer JWT), UserType = Investor
Auth policy / Permissionsauthenticated; permission profile.requisites.manage-self (INV-RQT-6)
Scope (RBAC)self
Record-levelOwnerUserId = UserId (sub); чужой templateId → 404
Tenant isolationне применяется (ADR-0052: инвестор вне tenant; изоляция по OwnerUserId)
Auditdelete (soft): DeletedAt = now, DeletedByUserId = UserId, Version++, RequisiteTemplateArchivedEvent

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/profile/requisite-templates/{templateId}/archive
Success status200
Idempotency headerне применяется (повторный archive архивного шаблона — идемпотентный no-op 200)
CorrelationOpenTelemetry trace (traceparent)

Входные данные / параметры запроса

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
UserIdidentity claims (sub)GuidДавалидный JWT401 без токена
templateIdrouteGuidДаNotEmpty; шаблон владельцачужой → 404

Модель ответа RequisiteTemplateModel

Дом модели — list-requisite-templates.md (список — постраничный); возвращается заархивированный шаблон со status = Archived, isDefault = false. Cipher-поля контактов наружу отдаются только маской (NumberMask), состав — идентификаторами документов (requisite-template.md).

Алгоритм

  1. Контекст и доступ. Операцию выполняет инвестор (UserId = sub) с permission profile.requisites.manage-self (INV-RQT-6). Tenant не применяется (ADR-0052).
  2. Проверка входа. templateId не пустой — иначе ValidationError.
  3. Проверка профиля и шаблона. Профиль нужен и для 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 — идемпотентный успех. Ссылок, блокирующих архив, нет: персона/документы шаблона не проверяются (архивный шаблон просто не выдаёт снапшот).
  4. Переход и сброс 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.
  5. События. Только доменное RequisiteTemplateArchivedEvent (Id, OwnerUserId). Интеграционных (Kafka) событий нет: шаблоны реквизитов в Kafka не публикуются (simplicity-first, ADR-0056; PII в шину не выносится) — requisite-template.md.
  6. Согласованность. Шаблон (+ профиль, если был default) фиксируются в одном согласованном изменении; строка шаблона сохраняется (soft-delete), ссылка DefaultRequisiteTemplateId обнуляется; конфликт версии → PROFILE_CONCURRENCY_CONFLICT.
  7. Результат. RequisiteTemplateModel (status = Archived, isDefault = false). HTTP 200. Снапшоты в сделках CRM не затрагиваются (владение crm после выдачи).

Диаграмма

Побочные эффекты

ТипДетали
Integration eventsnone (шаблоны в Kafka не публикуются — ADR-0056; только доменное RequisiteTemplateArchivedEvent)
External callsnone
Cache / projections / searchсброс Profile.DefaultRequisiteTemplateId при архиве default-шаблона (форма брони теряет предзаполнение)
Notificationsnone

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
— (auth)401Нет/просрочен JWTбез телаre-login
ValidationError400Пустой templateIdRestApiValidationErrorResponseno retry
PROFILE_NOT_FOUND404Профиль ещё не создан консюмером identity«Профиль ещё создаётся»retry
PROFILE_USER_BLOCKED400Profile.Status != Active (INV-PRF-3)«Учётная запись заблокирована»no retry
PROFILE_MANAGED_EXTERNALLY403ContactsManagedBy = TalentSync (INV-PRF-2)«Данные управляются внешней системой»no retry
PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE403UserType != Investor (INV-PRF-4/INV-RQT-6)«Доступно только инвесторам»no retry
PROFILE_TEMPLATE_NOT_FOUND404Нет шаблона / чужой OwnerUserId«Шаблон реквизитов не найден»no retry
PROFILE_CONCURRENCY_CONFLICT409Гонка 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).

Обратные ссылки

Автоссылки: где используется этот документ.

Связанные документы