POST /api/v1/profile/persons/{personId}/documents/{documentId}/archive — Архивация документа персоны (archivePersonDocument)
Назначение
Архивировать документ персоны (soft-delete): скрыть из списков и запретить использование в новых
шаблонах/верификациях; исторические снапшоты (сделки CRM, KYC) остаются валидными
(person-document.md). Документ, указанный в активном шаблоне реквизитов,
не архивируется (INV-DOC-6). Файлы сканов в storage удаляются отложенно retention-политикой storage.
| Поле | Значение |
|---|
| Сервис/контекст | profile |
| API area | public |
| Feature group | documents |
| Статус | draft |
| Документ | docs/06-services/profile/api/public/archive-person-document.md |
Актор и доступ
| Проверка | Значение |
|---|
| Actor type | User (Bearer JWT), UserType = Investor |
| Auth policy / Permissions | authenticated; permission profile.documents.manage-self |
| Scope (RBAC) | self |
| Record-level | OwnerUserId = UserId; чужие id → 404 |
| Tenant isolation | не применяется (ADR-0052) |
| Audit | delete (soft): DeletedAt = now, DeletedByUserId = UserId, PersonDocumentArchivedEvent |
HTTP-контракт
| Поле | Значение |
|---|
| Method | POST |
| Route | /api/v1/profile/persons/{personId}/documents/{documentId}/archive |
| Success status | 200 |
| Idempotency header | не применяется (повтор — no-op 200) |
| Correlation | OpenTelemetry trace (traceparent) |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|
UserId | identity claims (sub) | Guid | Да | валидный JWT | 401 без токена |
personId | route | Guid | Да | NotEmpty | чужая → 404 |
documentId | route | Guid | Да | NotEmpty; документ этой персоны | чужой → 404 |
Модель ответа PersonDocumentModel
Дом модели — list-person-documents.md; документ со status = Archived
(payload в ответе архивной операции не расшифровывается — поле payload = null, дельта от дома).
Алгоритм
- Контекст и доступ. Операцию выполняет инвестор (
UserId = sub) с permission
profile.documents.manage-self. Tenant не применяется (ADR-0052).
- Проверка входа.
personId/documentId не пустые — иначе ValidationError.
- Проверка профиля, персоны, документа и шаблонов. Профиль 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.
- Переход. Документ переходит
Active → Archived: DeletedAt = now, DeletedByUserId = UserId; у
персоны DocumentsCount--.
- События.
PersonUpsertedKafkaEvent (топик pin.profile.persons.v1, key PersonId; новый
DocumentsCount; без PII).
- Согласованность. Документ, персона и публикация события фиксируются атомарно; гонка версии →
PROFILE_CONCURRENCY_CONFLICT (409).
- Результат.
PersonDocumentModel (status = Archived, payload = null). HTTP 200.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|
| Integration events | PersonUpsertedKafkaEvent / pin.profile.persons.v1 / key = PersonId |
| External calls | none (удаление файлов — retention storage после архива) |
| Cache / projections / search | none |
| Notifications | none |
Ошибки и failure modes
| Код | HTTP status | Условие | Сообщение | Retry |
|---|
| — (auth) | 401 | Нет/просрочен JWT | без тела | re-login |
ValidationError | 400 | Пустые route-id | RestApiValidationErrorResponse | no retry |
PROFILE_NOT_FOUND | 404 | Профиль не создан | «Профиль ещё создаётся» | retry |
PROFILE_USER_BLOCKED | 400 | Status != Active | «Учётная запись заблокирована» | no retry |
PROFILE_MANAGED_EXTERNALLY | 403 | TalentSync | «Данные управляются внешней системой» | no retry |
PROFILE_PERSONS_NOT_ALLOWED_FOR_USER_TYPE | 403 | не Investor | «Доступно только инвесторам» | no retry |
PROFILE_PERSON_NOT_FOUND | 404 | Нет персоны / чужая | «Персона не найдена» | no retry |
PROFILE_DOCUMENT_NOT_FOUND | 404 | Нет документа | «Документ не найден» | no retry |
PROFILE_DOCUMENT_USED_IN_TEMPLATE | 409 | INV-DOC-6 | «Документ используется в шаблоне реквизитов» | после правки шаблона |
PROFILE_CONCURRENCY_CONFLICT | 409 | Гонка (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.
Обратные ссылки
Автоссылки: где используется этот документ.
Связанные документы