POST /api/developer/crm/bookings/{bookingId}/confirm-documents — Подтвердить документы брони
Менеджер застройщика подтверждает получение документов для бронирования — фактически это приём (Accept) над
паспортными документами брони (тип ClientPassport), загруженными инвестором/агентом. Проверяющий —
сторона-получатель (застройщик, INV-DOC-2), поэтому чужой актор получает
CRM_DOCUMENT_REVIEWER_FORBIDDEN. Документы переходят Uploaded/UnderReview → Accepted с проставлением
ReviewedByUserId/ReviewedAt, лента сделки получает событие DocumentAccepted. Дом полей —
deal-document.md.
Назначение
По кнопке «Подтвердить документы» карточки брони менеджер застройщика переводит паспортные документы брони в
Accepted (US-INV-14 — инвестор видит «принято»). Вызывает менеджер застройщика; тела ответа нет. По каждому
подтверждённому документу публикуется интеграционное
document-status-changed для уведомления инвестора.
Отдельного ключа идемпотентности нет — естественная: если у брони нет паспортных документов в Uploaded/
UnderReview (все уже терминальны), повторный вызов возвращает 204 без изменений, строк ленты и событий.
Metadata
| Поле | Значение |
|---|---|
| Сервис/контекст | crm |
| API area | developer (JWT менеджера застройщика) |
| Feature group | deal-documents |
| Статус | draft |
| Документ | docs/06-services/crm/api/developer/confirm-booking-documents.md |
Актор и доступ
| Проверка | Значение |
|---|---|
| Actor type | User (Менеджер застройщика); Operator (Менеджер ПИН — platform-scope, арбитраж) |
| Auth policy / Permissions | crm.deal-documents.review (сторона-получатель паспортных документов) |
| Scope (RBAC) | организация (застройщик): tenant + record-level по участию в сделке |
| Record-level | актор — SellManager/менеджер застройщика сделки брони (booking.DealId) либо Менеджер ПИН |
| Tenant isolation | TenantId = DeveloperCompanyId из claims; бронь и документы читаются только в этом tenant (booking.TenantId == actor.TenantId, ADR-0052) |
| Audit | update-review (ReviewedByUserId = actor.UserId); имена/номера паспортов в логи не пишутся (только DocumentId) |
INV-DOC-2: проверяющий — сторона-получатель документа. Для ClientPassport это менеджер застройщика
сделки брони (SellManager, record-level по booking.DealId) либо Менеджер ПИН (platform-scope, арбитраж).
Чужой актор (в т.ч. агентская сторона, загрузившая документ) получает CRM_DOCUMENT_REVIEWER_FORBIDDEN.
HTTP-контракт
| Поле | Значение |
|---|---|
| Method | POST |
| Route | /api/developer/crm/bookings/{bookingId}/confirm-documents |
| Success status | 204 (подтверждено; повтор без ожидающих документов — тот же 204, идемпотентно) |
| Idempotency header | не применяется (естественная идемпотентность: нет документов в Uploaded/UnderReview → no-op) |
| Correlation | сквозной trace через заголовок traceparent |
Входные данные / параметры запроса
| Поле | Источник | Тип | Обязательность | Валидация / Ограничения | Безопасность |
|---|---|---|---|---|---|
TenantId | identity claims | Guid | Да | Required; = DeveloperCompanyId актора | Tenant isolation |
BookingId | route | Guid | Да | != Guid.Empty; существующая бронь в tenant (шаг 2) | — |
DocumentIds | body | IReadOnlyList<Guid>? | Нет | если задан — 1..50 элементов, каждый != Guid.Empty, все принадлежат брони и типу ClientPassport (шаг 3); null → подтверждаются все ожидающие проверки паспортные документы брони | — |
ActorUserId | identity claims | Guid | Да | JWT менеджера застройщика; пишется в ReviewedByUserId | — |
DocumentIds — единственная коллекция тела, живёт внутри named-модели ConfirmBookingDocumentsCommand
(container). Вложенных сущностей нет: дом полей документа — deal-document.md.
Модель ответа
Тела нет — по успеху HTTP 204. Актуальные статусы документов и запись таймлайна DocumentAccepted читаются
отдельными query карточки сделки/брони (обновляются тем же изменением).
Алгоритм
- Контекст и доступ. Выполняет менеджер застройщика (
User, permissioncrm.deal-documents.review) либо менеджер ПИН (platform-scope, арбитраж); tenant —TenantId = DeveloperCompanyId. Естественная идемпотентность: если нет документов вUploaded/UnderReview— no-op (шаг 3), отдельного ключа нет. - Проверка входа и предусловий.
BookingId != Guid.Empty; при заданномDocumentIds—1..50элементов, каждый!= Guid.Empty, без дублей — иначеVALIDATION_FAILED;- бронь существует в tenant застройщика — иначе
CRM_BOOKING_NOT_FOUND(чужой tenant не раскрывается: 404); - состояние допускает подтверждение:
booking.Status ∈ {Reserved, AwaitingPayment, Booked}(нетерминальная бронь, INV-DOC-5) — иначеCRM_BOOKING_ALREADY_TERMINAL(на отменённой/истёкшей броне подтверждать нечего); - доступ: актор —
SellManager/менеджер застройщика сделки брони (record-level поbooking.DealId) либо менеджер ПИН — иначеCRM_DOCUMENT_REVIEWER_FORBIDDEN.
- Что читаем. Паспортные документы брони (тип
ClientPassport, не удалённые; при заданномDocumentIds— только их) под блокировкой строк (гонка с повторной загрузкой инвестором):- если выборка пуста: при заданном
DocumentIdsчасть id не найдена/не паспорт/не этой брони →CRM_DOCUMENT_NOT_FOUND; иначе паспортных документов у брони нет →CRM_DOCUMENTS_NOT_UPLOADED; - ожидающие проверки — документы в
Uploaded/UnderReview; если таких нет (все ужеAccepted/Rejected— терминальны, перезалив идёт новой записью, INV-DOC-4) → идемпотентный no-op:204, шаги 4–7 не выполняются.
- если выборка пуста: при заданном
- Бизнес-правила и переходы. Каждый ожидающий документ принимается (INV-DOC-2: проверяющий =
сторона-получатель типа
ClientPassport, т.е. менеджер застройщика; иначеCRM_DOCUMENT_REVIEWER_FORBIDDEN): переходUploaded/UnderReview → Accepted; маппинг:Status = Accepted,ReviewedByUserId = ActorUserId,ReviewedAt = now,UpdatedByUserId = ActorUserId,UpdatedAt = now. Фиксируется доменный фактDocumentReviewed; в той же транзакции материализуется запись ленты (INV-TLE-2):DealTimelineEventEntity(Type = DocumentAccepted, Status = Done, OccurredAt = now, ActorRole = SellManager, Payload = TimelineDocumentPayload { DocumentId, DocumentType = "ClientPassport", Title, RequestComment })— дом типов/payload deal-timeline-event.md. - Что меняется. Ожидающие паспортные документы переходят в
Accepted; в ленту добавляются append-only строкиDocumentAccepted(INV-TLE-1). Бронь не мутируется. Конкурентный доступ — по версии строки: если инвестор перезалил документ параллельно → ретрай с перечиткой шага 3. - События. По каждому подтверждённому документу — интеграционное
DocumentStatusChangedEvent→ топикpin.crm.document-status-changed.v1(ContractVersion = 1); payload:DocumentType = ClientPassport,FromStatus(Uploaded/UnderReview),ToStatus = Accepted,RejectReason = null,ChangedByUserId = ReviewedByUserId,ChangedAt = ReviewedAt(дом — document-status-changed.md, INV-DOC-6). Потребительnotifications— инвестору «документ принят». - Согласованность. Обновление всех документов, строки таймлайна и события фиксируются в одном согласованном изменении (атомарно).
- Результат. Тела ответа нет → HTTP
204 No Content.
Диаграмма
Побочные эффекты
| Тип | Детали |
|---|---|
| Integration events | DocumentStatusChangedEvent → pin.crm.document-status-changed.v1 (по одному на подтверждённый документ, ToStatus = Accepted), outbox |
| External calls | none (бинарь файла не читается — подтверждаются метаданные) |
| Cache / projections / search | обновляется ix_deal_documents_status; лента таймлайна получает строки DocumentAccepted |
| Notifications | опосредованно: notifications — консюмер document-status-changed (инвестору «документ принят», US-INV-14.3) и TimelineItemAdded (US-INV-11) |
Ошибки и failure modes
Два кода различают отсутствие документов от неверной адресации: CRM_DOCUMENTS_NOT_UPLOADED — у брони вовсе
нет паспортных документов; CRM_DOCUMENT_NOT_FOUND — заданный DocumentIds не паспорт или не этой брони. Ни
тот, ни другой не путать с идемпотентным no-op (шаг 3): если все паспортные документы уже терминальны
(Accepted/Rejected), ответ — 204 без ошибки.
| Код | HTTP status | Условие | Сообщение | Retry |
|---|---|---|---|---|
VALIDATION_FAILED | 400 | формат BookingId/DocumentIds | «Проверьте корректность запроса» | no retry |
CRM_BOOKING_NOT_FOUND | 404 | бронь не найдена в tenant застройщика | «Бронирование не найдено» | no retry |
CRM_BOOKING_ALREADY_TERMINAL | 409 | booking.Status терминальный (Completed/*Expired/Cancelled) | «Бронирование завершено — подтверждение недоступно» | no retry |
CRM_DOCUMENTS_NOT_UPLOADED | 409 | у брони нет паспортных документов для подтверждения | «Документы ещё не загружены» | no retry (ждать загрузку) |
CRM_DOCUMENT_NOT_FOUND | 404 | заданный DocumentIds не принадлежит брони / не тип ClientPassport | «Документ не найден» | no retry |
CRM_DOCUMENT_REVIEWER_FORBIDDEN | 403 | INV-DOC-2: актор не сторона-получатель (не менеджер застройщика сделки) | «Нет прав на подтверждение документов» | no retry |
Предусловия и постусловия
Предусловия (истинны до сценария):
- актор — менеджер застройщика сделки брони (
SellManager) либо Менеджер ПИН с permissioncrm.deal-documents.review(сторона-получатель паспортных документов, INV-DOC-2); - бронь существует в
TenantId = DeveloperCompanyIdактора и нетерминальна (Reserved/AwaitingPayment/Booked, INV-DOC-5); - при заданном
DocumentIdsвсе id — паспортные документы (ClientPassport) этой брони.
Постусловия (гарантированы после успеха):
- каждый ожидающий паспортный документ (
Uploaded/UnderReview) переведён вAcceptedсReviewedByUserId/ReviewedAt; уже терминальные (Accepted/Rejected) не трогаются; - по каждому подтверждённому документу записана строка ленты
DocumentAccepted(INV-TLE-1) и в outbox — событиеdocument-status-changed.v1(ToStatus = Accepted); бронь не мутируется; - если ожидающих документов не было — идемпотентный no-op:
204, строк ленты и событий нет.
Интеграционная последовательность (после коммита)
По каждому подтверждённому документу DocumentStatusChangedEvent → pin.crm.document-status-changed.v1
обрабатывается асинхронно; потребитель notifications уведомляет инвестора «документ принят» (US-INV-14.3).
Инвестор видит «принято» с задержкой (окно eventual-consistency — секунды); в промежутке карточка ещё показывает
прежний статус. Что предоставляет: событие — additive-only, по одному на документ, ключ-эффект идемпотентен по
DocumentId.
Acceptance Criteria
- Happy path (все). Дано: у брони есть паспортные документы в
Uploaded/UnderReview,DocumentIdsне задан. Когда: подтверждение. Тогда:204, все ожидающие →Accepted, по строке ленты и событию на каждый документ. - Подмножество. Дано: задан
DocumentIdsиз части документов брони. Когда: подтверждение. Тогда: подтверждены только указанные. - Идемпотентный повтор. Дано: все паспортные документы уже
Accepted. Когда: повтор. Тогда:204no-op, строк ленты и событий нет. - Документы не загружены. Дано: у брони нет паспортных документов. Когда: подтверждение. Тогда:
409 CRM_DOCUMENTS_NOT_UPLOADED. - Некорректные
DocumentIds. Дано: id не паспорт / не этой брони. Когда: подтверждение. Тогда:404 CRM_DOCUMENT_NOT_FOUND. - Терминальная бронь. Дано: бронь
Cancelled/*Expired/Completed. Когда: подтверждение. Тогда:409 CRM_BOOKING_ALREADY_TERMINAL. - Чужой проверяющий. Дано: актор не сторона-получатель (не менеджер застройщика сделки). Когда: подтверждение.
Тогда:
403 CRM_DOCUMENT_REVIEWER_FORBIDDEN(INV-DOC-2). - Гонка перезалива. Дано: инвестор параллельно перезалил документ. Когда: конфликт версии строки. Тогда: ретрай с перечиткой ожидающих (шаг 3).
Совместимость и наблюдаемость
DocumentIds— additive optional (при отсутствии — подтверждаются все ожидающие); удаление/переименование поля или сменаSuccess status— breaking (migration + rollout). Контракт события — additive-only (document-status-changed.md).- Logs:
booking-documents confirmed(BookingId,DealId,count,DocumentId[]; ФИО/паспорт — не пишутся); metrics:crm_document_reviews_total{decision="accept"}, latency p95; traces — сквозной trace черезtraceparent; dashboardpin-crm; runbook —docs/06-services/crm/runbooks/(будущий путь).
Обратные ссылки
Автоссылки: где используется этот документ.
- API (3): GET /api/developer/crm/fixations — Список фиксаций застройщика (+ FTS), POST /api/developer/crm/deals/{dealId}/complete — Завершить сделку, POST /api/developer/crm/deals/{dealId}/rosreestr — Подтвердить регистрацию Росреестра
- registries (1): Реестр API-методов PIN
Связанные документы
- Сущности: deal-document.md (агрегат, INV-DOC-2/5/6,
Review, enum-ы), booking.md (родительский процесс,BookingId), deal-timeline-event.md (DocumentAccepted,TimelineDocumentPayload). - Событие: document-status-changed.md; карта области — README.
- ADR: 0052 tenancy, 0056 simplicity-first.