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

POST /api/developer/crm/deals/{dealId}/rosreestr — Подтвердить регистрацию Росреестра

Менеджер застройщика подтверждает регистрацию сделки в Росреестре — предпоследний шаг жизненного цикла сделки перед завершением. Операция ставит RosreestrRegisteredAt, продвигает сделку на системный этап группы Registration канбан-доски и опционально прикрепляет документ о регистрации (RosreestrDocument, дом — deal-document.md). Порядок строгий (INV-DEAL-7): договоры подготовлены → (для Ddu — эскроу Provided) → Росреестр → завершение; нарушение → CRM_DEAL_STAGE_ORDER_VIOLATION. Дом полей сделки — deal.md.

Назначение

По кнопке «Подтвердить регистрацию» карточки сделки менеджер застройщика фиксирует регистрацию в Росреестре: проставляет дату, переводит карточку в системный этап группы Registration, записывает событие таймлайна RosreestrRegistered. Вызывает менеджер застройщика; на выходе — свежая карточка сделки. Публикуется deal-stage-changed для чата/уведомлений участников.

Metadata

ПолеЗначение
Сервис/контекстcrm
API areadeveloper (JWT менеджера застройщика)
Feature groupdeals
Статусdraft
Документdocs/06-services/crm/api/developer/confirm-rosreestr.md

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

Доступ
ПроверкаЗначение
Actor typeUser (Менеджер застройщика); Operator (Менеджер ПИН — platform-scope, арбитраж US-MGR)
Auth policy / Permissionscrm.deals.decide (решение застройщика по стадии сделки)
Scope (RBAC)организация (застройщик) — TenantId = DeveloperCompanyId
Record-levelзастройщик своего TenantId; Менеджер ПИН — platform-scope
Tenant isolationTenantId = DeveloperCompanyId из claims; сделка читается/пишется только в этом tenant (ADR-0052)
Auditupdate-lifecycle (UpdatedByUserId = actor.UserId, стамп RosreestrRegisteredAt); EscrowAccountNumber/PII в логи не пишутся

HTTP-контракт

ПолеЗначение
MethodPOST
Route/api/developer/crm/deals/{dealId}/rosreestr
Success status200 (подтверждено; повтор при уже проставленном RosreestrRegisteredAt — тот же 200 с текущей карточкой, идемпотентно)
Idempotency headerне применяется (естественная идемпотентность: RosreestrRegisteredAt != null → no-op, шаг 4)
Correlationсквозной trace через заголовок traceparent

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

ПолеИсточникТипОбязательностьВалидация / ОграниченияБезопасность
TenantIdidentity claimsGuidДаRequired; = DeveloperCompanyId актораTenant isolation
DealIdrouteGuidДа!= Guid.Empty; существующая сделка в tenant (шаг 2)
RegisteredAtbodyDateTimeOffset?Нетесли задан — <= now (дата из выписки, не в будущем) и >= ContractsPreparedAt; null → серверный now
DocumentbodyRosreestrDocumentInput?Нетесли задан — валидируется как модель ниже; null → документ не прикрепляется
ActorUserIdidentity claimsGuidДаJWT менеджера застройщика; пишется в UpdatedByUserId/UploadedByUserId

RosreestrDocumentInput

Единственная вложенная named-модель тела (container); при её наличии создаётся DealDocumentEntity типа RosreestrDocument (загружает застройщик, проверяющего нет — deal-document.md).

ПолеТипОбязательностьВалидация / ОграниченияОписание
FileIdGuidДа!= Guid.Empty; ссылка на storage/CDN (tenantId + fileId)Загруженный файл выписки/зарегистрированного договора.
FileNamestringДа1..512Оригинальное имя файла.
ContentTypestringДаapplication/pdf / image/jpeg / image/png (INV-DOC-3)MIME-тип.
SizeByteslongДа> 0 and <= 52_428_800 (50 МБ)Размер файла.
Titlestring?Нет0..256; default «Документ о регистрации Росреестра»Отображаемое название.

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

Дом модели и её подмоделей — get-deal.md («Модель ответа DealCardModel»); возвращается свежая карточка сделки после мутации (та же проекция, что читает GET карточки). Дельты полей нет — застройщик получает обновлённое состояние без второго запроса: StepId/StepGroup — новый системный этап группы Registration, Documents — пересчитанная сводка (учтён прикреплённый RosreestrDocument), UpdatedAt — момент операции. RosreestrRegisteredAt наружу несёт событие таймлайна RosreestrRegistered (вкладка «Ход сделки», отдельный метод ленты — дом deal-timeline-event.md).

Алгоритм

  1. Контекст и доступ. Выполняет менеджер застройщика (User, permission crm.deals.decide, иначе CRM_DEALS_FORBIDDEN) либо менеджер ПИН (platform-scope, арбитраж); tenant — TenantId = DeveloperCompanyId. Естественная идемпотентность: если RosreestrRegisteredAt != null — no-op (шаг 4), отдельного ключа нет.

  2. Проверка входа и предусловий.

    • DealId != Guid.Empty; RegisteredAt (если задан) <= now и >= ContractsPreparedAt; Document (если задан) — FileId != Guid.Empty, FileName 1..512, ContentType из белого списка (application/pdf/image/jpeg/image/png, INV-DOC-3), SizeBytes ∈ 1..52_428_800 — иначе VALIDATION_FAILED;
    • сделка существует в tenant застройщика — иначе CRM_DEAL_NOT_FOUND (чужой tenant не раскрывается);
    • состояние: Status == Sale (Росреестр — фаза продажи после брони); терминальные Completed/CancelledCRM_DEAL_ALREADY_TERMINAL; иной статус → нарушение порядка (шаг 4, CRM_DEAL_STAGE_ORDER_VIOLATION).
  3. Что читаем. Сделку — с защитой от гонки с параллельным Complete/MoveToStep. Целевой этап продвижения — системный этап группы Registration застройщика (pipeline-step.md); если его нет → CRM_DEAL_STEP_FORBIDDEN. Прежние deal.StepId/deal.StepGroup фиксируются для payload перехода.

  4. Бизнес-правила и переходы (INV-DEAL-7).

    • идемпотентность: если RosreestrRegisteredAt != null — мутация/событие не выполняются, возвращается текущая карточка (шаг 8, 200);
    • guard порядка (INV-DEAL-7): ContractsPreparedAt != null и (ContractType != Ddu или EscrowStatus == Provided) — иначе CRM_DEAL_STAGE_ORDER_VIOLATION (для ДДУ эскроу обязателен);
    • маппинг: RosreestrRegisteredAt = RegisteredAt ?? now, StepId = целевой этап, StepGroup = Registration (денормализация), UpdatedByUserId = ActorUserId, UpdatedAt = now. Status остаётся SaleCompleted переводит отдельный метод Complete).

    Фиксируется доменный факт DealStageChanged (From: Sale, To: Sale; группа → Registration); подписчик материализует ленту (INV-TLE-2): RosreestrRegistered (TimelineEmptyPayload, Status = Done, OccurredAt = registeredAt, ActorRole = SellManager) и StageChanged (TimelineStageChangedPayload) — дом deal-timeline-event.md. При заданном Document создаётся DealDocument типа RosreestrDocument со статусом Uploaded (INV-DOC-5: сделка нетерминальна); фиксируется факт DocumentUploaded → таймлайн DocumentUploaded.

  5. Что меняется. Сделка получает RosreestrRegisteredAt и продвигается в системный этап группы Registration; при заданном документе появляется запись RosreestrDocument; в ленту добавляются строки RosreestrRegistered (+StageChanged, +DocumentUploaded). Конкурентный доступ — по версии строки: параллельный Complete/MoveToStep → ретрай с перечиткой.

  6. События. Интеграционное DealStageChangedEvent → топик pin.crm.deal-stage-changed.v1 (ContractVersion = 1); payload: FromStepId, ToStepId, FromStepGroup, ToStepGroup = Registration, FromStatus = Sale, ToStatus = Sale, ChangedByUserId = ActorUserId, ChangedAt = now (дом — deal-stage-changed.md). При заданном Document дополнительно — DocumentStatusChangedEvent → топик pin.crm.document-status-changed.v1 (ToStatus = Uploaded, document-status-changed.md). Потребители: notifications (участникам «сделка на регистрации», инвестору — статус документа), messenger (карточка этапа в чате).

  7. Согласованность. Обновление сделки, опциональный документ, строки таймлайна и события фиксируются в одном согласованном изменении (атомарно).

  8. Результат. Свежая карточка DealCardModel (та же проекция, что get-deal.md) → HTTP 200. Идемпотентный повтор (RosreestrRegisteredAt уже стоял) возвращает текущую карточку без мутаций/событий.

Диаграмма

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

ТипДетали
Integration eventsDealStageChangedEventpin.crm.deal-stage-changed.v1 (переход в группу Registration); при DocumentDocumentStatusChangedEventpin.crm.document-status-changed.v1 (ToStatus = Uploaded); оба через outbox
External callsnone (бинарь файла не читается — сохраняются метаданные и FileId storage/CDN)
Cache / projections / searchлента таймлайна получает RosreestrRegistered (+StageChanged, +DocumentUploaded); денормализованный Deal.StepGroup обновлён; проекция сделки (DealSearchDocument) обновляется по StepGroup (Title/ФИО прежние)
Notificationsопосредованно: notifications — консюмер deal-stage-changed (участникам «сделка на регистрации») и document-status-changed (инвестору US-INV-14.3); messenger рисует карточку этапа в чате сделки

Ошибки и failure modes

Ошибки

Два 409 нетривиальны: CRM_DEAL_STAGE_ORDER_VIOLATION — нарушен порядок INV-DEAL-7 (договоры не подготовлены, либо для Ddu эскроу не Provided, либо сделка не в статусе Sale); CRM_DEAL_STEP_FORBIDDEN — у застройщика не настроен системный этап группы Registration (проблема конфигурации воронки, а не действия актора).

КодHTTP statusУсловиеСообщениеRetry
VALIDATION_FAILED400DealId = Guid.Empty, RegisteredAt в будущем, некорректные метаданные Document«Проверьте корректность запроса»no retry
UNAUTHORIZED401нет/просрочен JWT«Требуется авторизация»no retry
CRM_DEALS_FORBIDDEN403нет permission crm.deals.decide«Недостаточно прав»no retry
CRM_DEAL_NOT_FOUND404сделки нет в tenant застройщика (или soft-deleted)«Сделка не найдена»no retry
CRM_DEAL_ALREADY_TERMINAL409Status = Completed/Cancelled«Сделка завершена — действие недоступно»no retry
CRM_DEAL_STAGE_ORDER_VIOLATION409INV-DEAL-7: договоры не подготовлены / для Ddu эскроу не Provided / статус не Sale«Нельзя подтвердить регистрацию: не выполнены предыдущие шаги»no retry
CRM_DEAL_STEP_FORBIDDEN409у застройщика нет системного этапа группы Registration«Этап регистрации не настроен»no retry (конфиг воронки)
Идемпотентность

Повтор при уже проставленном RosreestrRegisteredAt — не ошибка: 200 с текущей карточкой, без мутаций и без повторной публикации событий (естественная идемпотентность по полю состояния, отдельного Idempotency-Key нет).

Предусловия и постусловия

Предусловия (истинны до сценария):

  • актор — менеджер застройщика сделки (или Менеджер ПИН, арбитраж) с permission crm.deals.decide;
  • сделка существует в TenantId = DeveloperCompanyId актора, нетерминальна и в статусе Sale;
  • порядок стадий соблюдён (INV-DEAL-7): договоры подготовлены (ContractsPreparedAt != null) и для Ddu — эскроу Provided;
  • у застройщика настроен системный этап группы Registration.

Постусловия (гарантированы после успеха):

  • проставлен RosreestrRegisteredAt, сделка продвинута в системный этап группы Registration (StepId/StepGroup денормализованы), Status остаётся SaleCompleted переводит Complete);
  • при заданном Document создан DealDocument типа RosreestrDocument в статусе Uploaded (INV-DOC-5);
  • записаны строки ленты RosreestrRegistered (+StageChanged, при документе +DocumentUploaded); в outbox — deal-stage-changed.v1 (+ document-status-changed.v1 при документе);
  • операция идемпотентна: если RosreestrRegisteredAt уже стоял — мутаций и событий нет, возвращается текущая карточка.

Интеграционная последовательность (после коммита)

События обрабатываются асинхронно; актор в ответе 200 видит сделку в группе Registration сразу, эффекты соседей доходят с задержкой (окно eventual-consistency — секунды):

  1. DealStageChangedEvent → pin.crm.deal-stage-changed.v1 (ToStepGroup = Registration): notifications — «сделка на регистрации» участникам; messenger — карточка этапа в чате сделки.
  2. При заданном DocumentDocumentStatusChangedEvent → pin.crm.document-status-changed.v1 (ToStatus = Uploaded): notifications — статус документа инвестору (US-INV-14.3).

Далее по цепочке следует завершение сделки (complete-deal.md) — оно требует именно RosreestrRegisteredAt != null, установленный этой операцией (кросс-операционная предпосылка внутри контекста). Что предоставляет: оба события — additive-only; unknown enum у консюмера трактуется как warning + «как есть».

Acceptance Criteria

  • Happy path (ДДУ с эскроу). Дано: сделка Sale, договоры подготовлены, эскроу Provided. Когда: менеджер подтверждает регистрацию. Тогда: 200, RosreestrRegisteredAt проставлен, группа Registration, опубликовано deal-stage-changed.v1.
  • Happy path (ДКП). Дано: сделка Sale с ContractType = Dkp, договоры подготовлены (эскроу не требуется). Когда: подтверждение. Тогда: 200, продвижение в Registration.
  • ДДУ без эскроу. Дано: ContractType = Ddu, EscrowStatus != Provided. Когда: подтверждение. Тогда: 409 CRM_DEAL_STAGE_ORDER_VIOLATION (INV-DEAL-7).
  • Договоры не подготовлены. Дано: ContractsPreparedAt == null. Когда: подтверждение. Тогда: 409 CRM_DEAL_STAGE_ORDER_VIOLATION.
  • Идемпотентный повтор. Дано: RosreestrRegisteredAt уже стоял. Когда: повтор. Тогда: 200 с текущей карточкой, без мутаций и событий.
  • Терминальная сделка. Дано: Status ∈ {Completed, Cancelled}. Когда: подтверждение. Тогда: 409 CRM_DEAL_ALREADY_TERMINAL.
  • Нет этапа регистрации. Дано: у застройщика нет системного этапа группы Registration. Когда: подтверждение. Тогда: 409 CRM_DEAL_STEP_FORBIDDEN (настроить воронку).
  • С документом. Дано: задан валидный Document. Когда: подтверждение. Тогда: создан RosreestrDocument (Uploaded) и дополнительно опубликовано document-status-changed.v1.
  • Дата в будущем. Дано: RegisteredAt > now. Когда: подтверждение. Тогда: 400 VALIDATION_FAILED.

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

  • RegisteredAt/Document — additive optional (при отсутствии — серверный now и без документа); удаление/ переименование полей или смена Success status — breaking (migration + rollout). Контракты событий — additive-only (deal-stage-changed.md, document-status-changed.md); unknown enum у консюмера — warning + как есть.
  • Logs: deal rosreestr confirmed (DealId, TenantId, ActorUserId, HasDocument; эскроу/ФИО — не пишутся); metrics: crm_deal_stage_confirm_total{stage="rosreestr",result}, latency p95 (< 500 мс, SLO команд); traces — OTel, correlation через traceparent; dashboard pin-crm; runbook — docs/06-services/crm/runbooks/ (будущий путь).

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

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

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