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

POST /api/v1/profile/persons/{personId}/documents/{documentId}/archive — Архивация документа персоны (archivePersonDocument)

Назначение

Архивировать документ персоны (soft-delete): скрыть из списков и запретить использование в новых шаблонах/верификациях; исторические снапшоты (сделки CRM, KYC) остаются валидными (person-document.md). Документ, указанный в активном шаблоне реквизитов, не архивируется (INV-DOC-6). Файлы сканов в storage удаляются отложенно retention-политикой storage.

Metadata

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

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

ПроверкаЗначение
Actor typeUser (Bearer JWT), UserType = Investor
Auth policy / Permissionsauthenticated; permission profile.documents.manage-self
Scope (RBAC)self
Record-levelOwnerUserId = UserId; чужие id → 404
Tenant isolationне применяется (ADR-0052)
Auditdelete (soft): DeletedAt = now, DeletedByUserId = UserId, PersonDocumentArchivedEvent

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/v1/profile/persons/{personId}/documents/{documentId}/archive
Success status200
Idempotency headerне применяется (повтор — no-op 200)
CorrelationOpenTelemetry trace (traceparent)

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

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

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

Дом модели — list-person-documents.md; документ со status = Archived (payload в ответе архивной операции не расшифровывается — поле payload = null, дельта от дома).

Алгоритм

  1. Контекст и доступ. Операцию выполняет инвестор (UserId = sub) с permission profile.documents.manage-self. Tenant не применяется (ADR-0052).
  2. Проверка входа. personId/documentId не пустые — иначе ValidationError.
  3. Проверка профиля, персоны, документа и шаблонов. Профиль self-service (guard-ы INV-PRF-2..4 → PROFILE_MANAGED_EXTERNALLY/PROFILE_USER_BLOCKED/PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE; отсутствие профиля → PROFILE_NOT_FOUND). Персона должна принадлежать владельцу — иначе PROFILE_PERSON_NOT_FOUND. Документ существует у персоны — иначе PROFILE_DOCUMENT_NOT_FOUND; уже Archived — идемпотентный успех. INV-DOC-6: документ не должен входить в состав ни одного активного шаблона реквизитов владельца (Composition.{IdentityDocumentId, InnDocumentId, AdditionalDocumentIds}) — иначе PROFILE_DOCUMENT_USED_IN_TEMPLATE.
  4. Переход. Документ переходит Active → Archived: DeletedAt = now, DeletedByUserId = UserId; у персоны DocumentsCount--.
  5. События. PersonUpsertedKafkaEvent (топик pin.profile.persons.v1, key PersonId; новый DocumentsCount; без PII).
  6. Согласованность. Документ, персона и публикация события фиксируются атомарно; гонка версии → PROFILE_CONCURRENCY_CONFLICT (409).
  7. Результат. PersonDocumentModel (status = Archived, payload = null). HTTP 200.

Диаграмма

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

ТипДетали
Integration eventsPersonUpsertedKafkaEvent / pin.profile.persons.v1 / key = PersonId
External callsnone (удаление файлов — retention storage после архива)
Cache / projections / searchnone
Notificationsnone

Ошибки и failure modes

КодHTTP statusУсловиеСообщениеRetry
— (auth)401Нет/просрочен JWTбез телаre-login
ValidationError400Пустые route-idRestApiValidationErrorResponseno retry
PROFILE_NOT_FOUND404Профиль не создан«Профиль ещё создаётся»retry
PROFILE_USER_BLOCKED400Status != Active«Учётная запись заблокирована»no retry
PROFILE_MANAGED_EXTERNALLY403TalentSync«Данные управляются внешней системой»no retry
PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE403не Investor«Доступно только инвесторам»no retry
PROFILE_PERSON_NOT_FOUND404Нет персоны / чужая«Персона не найдена»no retry
PROFILE_DOCUMENT_NOT_FOUND404Нет документа«Документ не найден»no retry
PROFILE_DOCUMENT_USED_IN_TEMPLATE409INV-DOC-6«Документ используется в шаблоне реквизитов»после правки шаблона
PROFILE_CONCURRENCY_CONFLICT409Гонка (xmin)«Данные изменены, повторите»retry

Совместимость и наблюдаемость

  • Hard-delete в public API не появится (ADR-0053 — crypto-erase по регламенту администратора платформы).
  • Logs: UserId, PersonId, DocumentId, Type; metrics: profile_api_operations_total{operation="archivePersonDocument",result}; traces — OTel.

Acceptance Criteria

  • Happy path. Given активный документ персоны владельца, не входящий в состав активного шаблона; When archive; Then 200, Active → Archived (DeletedAt, DeletedByUserId), у персоны DocumentsCount--, опубликован PersonUpsertedKafkaEvent, в ответе payload = null.
  • Идемпотентность. Given документ уже Archived; When archive; Then 200 (no-op); повторного DocumentsCount-- и события нет.
  • В активном шаблоне. Given документ указан в Composition активного шаблона; When archive; Then 409 PROFILE_DOCUMENT_USED_IN_TEMPLATE (INV-DOC-6).
  • Чужой/несуществующий. Given чужой или отсутствующий documentId; When archive; Then 404 PROFILE_DOCUMENT_NOT_FOUND; чужая персона → 404 PROFILE_PERSON_NOT_FOUND.
  • Гонка. Given параллельное изменение документа; When обе операции фиксируются; Then победитель — 200, проигравший — 409 PROFILE_CONCURRENCY_CONFLICT.
  • Исторические снапшоты. Given документ входил в ранее выданные снапшоты (сделки CRM, KYC); When archive; Then эти снапшоты остаются валидными (архив не затрагивает копии); файлы сканов удаляются отложенно retention-политикой storage.

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

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

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