UC-CRM-006. Вмешательство Менеджера ПИН
Назначение
Ручное вмешательство Менеджера ПИН в сделку любого застройщика платформы: сквозной просмотр сделок/лидов/фиксаций, ручная смена статуса и этапа при конфликтах, ручное подтверждение этапа с записью в таймлайн, проверка и скачивание всех документов (включая PII-типы) и участие в чате сделки. Агрегат — DealEntity. Покрывает US-MGR-01..06.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-CRM-006 |
| Контекст | crm (см. docs/06-services/crm/) |
| Тип | основной (+ альтернативные потоки A1–A3) |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Менеджер ПИН (ParticipantRole.PinManager — авто-участник всех сделок, INV-DEAL-2) |
| Permissions / scope | crm.deals.view/crm.deals.manage/crm.deals.decide, crm.deal-documents.view, crm.pipeline.manage — platform-scope (кросс-tenant, ADR-0052); ручной таймлайн — INV-TLE-5 |
| Tenant | null (сквозной доступ ко всем TenantId; ADR-0052 «кросс-tenant — только platform-scope») |
| Record-level | не применяется (сквозная видимость поверх record-level предикатов сторон) |
Предусловие
- Actor аутентифицирован; несёт platform-scope permission Менеджера ПИН (
identity). - Целевая сделка существует (DealEntity); для ручного статуса/этапа — не терминальная (
Status ∉ {Completed, Cancelled}, INV-DEAL-9). - Менеджер ПИН — активный участник сделки (
DealMemberEntityс рольюPinManager,RemovedAt is null).
Триггер
| Поле | Значение |
|---|---|
| Источник | UI-действие / вызов API |
| Триггерящий элемент | Кабинет Менеджера ПИН: сквозной список/канбан и карточка сделки (/pin-manager/deals, /pin-manager/deals/[id]) |
Основной Поток
- Менеджер открывает сквозной список сделок платформы (фильтры: агент/девелопер/статус/объект) →
GET /api/pin-manager/crm/deals(pin-manager-list-deals.md); выборка — без tenant-предиката (platform-scope). - Открывает карточку конфликтной сделки →
GET /api/pin-manager/crm/deals/{id}(pin-manager-get-deal.md) с составом участников, таймлайном, документами, бронью. - Ручная смена статуса (US-MGR-03). Менеджер выбирает целевой статус (
Заявка/Бронь/Продажа/Отказ)- комментарий →
POST /api/pin-manager/crm/deals/{id}/override-status(pin-manager-override-deal-status.md).
- Проверка входа: статус ∈ {
Application,Booking,Sale,Cancelled};Commentне пуст; приCancelled— заданCancelReason. - Сделка загружается; guard INV-DEAL-9 (не терминальна) — иначе
CRM_DEAL_ALREADY_TERMINAL. - Целевой
DealStatusмаппится вPipelineStepGroup(pipeline-step.md), выбирается живой этап группы (default поOrder, INV-STEP-3); проставляютсяStatus/StepId/StepGroup; событиеDealStatusChanged. - Подписчик пишет таймлайн
StatusChanged(Reason = comment). - Интеграционное
DealStageChangedEvent(FromStatus/ToStatus,FromStepId/ToStepId,ChangedByUserId = actor) — в outbox. - Агрегат + таймлайн + outbox фиксируются атомарно в одном согласованном изменении.
- комментарий →
- Перевод этапа (US-MGR-05). Менеджер перетаскивает карточку →
POST /api/pin-manager/crm/deals/{id}/move-step(pin-manager-move-deal-step.md); перевод на этап (INV-DEAL-5: целевой этап того же резолвнутогоTenantId, не архивирован; денормализуетStepGroup); таймлайнStageChanged; тот жеDealStageChangedEvent(FromStepId → ToStepId). - Ручное подтверждение этапа + запись таймлайна (US-MGR-05). Менеджер добавляет заметку («Документы
приняты вручную») →
POST /api/pin-manager/crm/deals/{id}/timeline-notes(pin-manager-add-timeline-note.md); создаётсяDealTimelineEventEntity{ Type = ManualNote, Payload = TimelineManualNotePayload{Text}, Status = Done, ActorRole = PinManager }(INV-TLE-5); поднимаетсяTimelineItemAdded→ push/email черезnotifications(US-INV-11). Kafka-события таймлайн не публикует. - Проверка и скачивание документов (US-MGR-04). Вкладка «Документы» карточки (шаг 2) отдаёт весь список
включая PII-типы (
ClientPassportи др.); скачивание →GET /api/pin-manager/crm/deals/{id}/documents/{documentId}/download(pin-manager-download-deal-document.md, будущий документ) — пресайн-ссылка storage. Эскроу в таймлайне остаётся маской****NNNN. - Чат сделки (US-MGR-06). Менеджер уже авто-участник (
PinManager); чат живёт вmessenger(ADR-0055), CRM только поставляет события состава (deal-members-changed.md). - Фронт инвалидирует данные карточки/списка; участники получают уведомление и системную карточку в чате.
Альтернативные Потоки
Отказ)- Менеджер выбирает
Откази указываетCancelReason(обязателен, INV-DEAL-8). - Отказ:
* → Cancelled, активная бронь отменяется тем же изменением; параллельно публикуется deal-cancelled.md (объект недвижимости освобождает консюмерchessboard) вместе сDealStageChangedEvent.
Завершается терминальным статусом.
- Если у сделки нет активного участника
PinManager, участие устанавливается операцией добавления участника (Role = PinManager, actor = system; INV-DEAL-3). - Публикуется deal-members-changed.md →
messengerсинхронизирует состав чата.
Возвращается к основному потоку (шаг 1).
- При
move-stepцелевой этап архивирован/чужого tenant →CRM_DEAL_STEP_FORBIDDEN(INV-DEAL-5), мутаций нет.
Клиент показывает ошибку, состояние сделки не меняется.
Постусловие
DealEntity.Status/StepId/StepGroupобновлены;UpdatedAt/UpdatedByUserId= момент/actor.- В
crm.deal_timeline_eventsдобавлены строки (StatusChangedс комментарием,StageChanged,ManualNote) — append-only (INV-TLE-1). - В outbox —
DealStageChangedEvent(иDealCancelledEventпри отказе); участники уведомлены, карточка в чате обновлена. - Скачанные документы/эскроу наружу не логируются в открытом виде (Sensitive — маска).
Возможные Ошибки
| Код | HTTP | Условие | Поведение клиента |
|---|---|---|---|
FORBIDDEN | 403 | Actor без platform-scope permission | Скрыть действия вмешательства |
CRM_DEAL_NOT_FOUND | 404 | Сделка по {id} не найдена | Обновить список |
CRM_DEAL_ALREADY_TERMINAL | 409 | Override/move на Completed/Cancelled (INV-DEAL-9) | Показать «сделка завершена/отменена» |
CRM_DEAL_CANCEL_REASON_REQUIRED | 422 | Статус Отказ без CancelReason (INV-DEAL-8) | Запросить причину |
CRM_DEAL_STEP_FORBIDDEN | 409 | Этап архивирован/чужой tenant (INV-DEAL-5) | Ошибка, не менять этап |
CRM_TIMELINE_MANUAL_FORBIDDEN | 403 | ManualNote не от PinManager/Службы заботы (INV-TLE-5) | Скрыть добавление заметки |
CONCURRENCY_CONFLICT | 409 | Гонка конкурентной правки (оптимистичная блокировка) | Перечитать карточку и повторить |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
DealStatusChanged | Ручная смена статуса / отказ | подписчик таймлайна (StatusChanged), сборщик DealStageChangedEvent |
DealMovedToStep | Перевод на этап | подписчик таймлайна (StageChanged), сборщик DealStageChangedEvent |
TimelineItemAdded | Добавление строки ManualNote/любой строки | notifications (push/email «новое событие», US-INV-11) |
DealMemberAdded | Добавление участника (A2) | сборщик DealMembersChangedEvent |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
DealStageChangedEvent | pin.crm.deal-stage-changed.v1 | Ручная смена статуса/этапа (шаги 3–4) — контракт | messenger (карточка в чат), notifications (участникам) |
DealCancelledEvent | pin.crm.deal-cancelled.v1 | Отказ (A1) — контракт | chessboard (освобождение объекта недвижимости), notifications, rewards |
DealMembersChangedEvent | pin.crm.deal-members-changed.v1 | Добавление участия (A2) — контракт | messenger (синк состава чата) |
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Менеджер видит сделку чужого TenantId через list/get (кросс-tenant) | Integration: сделка tenant A видна актору без tenant → 200 |
| AC2 | Override Бронь → Заявка с комментарием: Status обновлён, таймлайн StatusChanged{Reason}, DealStageChangedEvent в outbox, участники уведомлены | Тест операции + проверка outbox/таймлайна |
| AC3 | Override Отказ без CancelReason → CRM_DEAL_CANCEL_REASON_REQUIRED | Валидатор-тест |
| AC4 | Move-step в этап другой группы: StepGroup денормализован, событие несёт From/To этап и группу | Проверка payload DealStageChangedEvent |
| AC5 | add-timeline-note создаёт строку Type = ManualNote, поднимает TimelineItemAdded, Kafka не публикуется | Проверка типа строки + отсутствия outbox-записи |
| AC6 | Менеджер скачивает PII-документ (ClientPassport); эскроу в таймлайне — маска ****NNNN | Тест доступа + проверка маскирования |
| AC7 | Override/move терминальной сделки → CRM_DEAL_ALREADY_TERMINAL, мутаций нет | Guard-тест INV-DEAL-9 |
| AC8 | Менеджер — авто-участник чата; добавление участия PinManager → DealMembersChangedEvent | Тест A2 + проверка события |
Кросс-сервисные ожидания и гарантии
Вмешательство — синхронные HTTP-команды платформенного актора; эффекты для чата/уведомлений и освобождения объекта — асинхронные событийные цепочки.
Что crm предоставляет (исходящие гарантии):
| Контракт | Ключ / версия | Когда | Кому |
|---|---|---|---|
pin.crm.deal-stage-changed.v1 | key = DealId, ContractVersion = 1 | ручная смена статуса (шаг 3) / перевод этапа (шаг 4) | messenger (карточка в чат), notifications |
pin.crm.deal-cancelled.v1 | key = DealId, v1 | отказ A1, один раз на * → Cancelled | chessboard (освобождение объекта), notifications, rewards |
pin.crm.deal-members-changed.v1 | key = DealId, v1 | добавление участия PinManager (A2) | messenger (синк состава чата) |
Ручная запись таймлайна (ManualNote, шаг 5) собственного Kafka-события не публикует — поднимает только
доменное TimelineItemAdded, которое доставляет push/email консюмер notifications.
Что crm ожидает от соседей:
identity— platform-scope permission Менеджера ПИН (кросс-tenant видимость, ADR-0052); без него —403.messenger— владелец чата (ADR-0055): Менеджер ПИН уже авто-участник; приdeal-members-changed/deal-createdсинхронизирует состав.crmпоставляет только события состава.chessboard— поdeal-cancelledосвобождает объект недвижимости (first-seen поDealId).- storage — пресайн-ссылки на скачивание документов (шаг 6), включая PII-типы; ссылка выдаётся синхронно.
Промежуточное состояние: после override-status/move-step менеджер видит обновлённые Status/StepId
сразу; карточка в чате и push/email участникам приходят с задержкой (минуты); при отказе объект освобождается
консюмером chessboard асинхронно (окно — см. НФТ). Фронт инвалидирует список/карточку немедленно.
Идемпотентность, конкурентность и восстановление
- Идемпотентность.
override-status/move-stepне идемпотентны по ключу; повтор на терминальную сделку —CRM_DEAL_ALREADY_TERMINAL(INV-DEAL-9); повтор перевода на тот же этап — фактический no-op.ManualNoteappend-only: повторная отправка добавит новую строку (дублирование заметок — на усмотрение актора, не ошибка). - Конкурентность. Ручная правка Менеджера ПИН конкурирует с решениями сторон (агент/застройщик/инвестор):
оптимистичная блокировка, проигравший получает
CONCURRENCY_CONFLICT(409) и перечитывает карточку. Кросс- tenant доступ не снимает guard-ы INV-DEAL-5/8/9. - Доставка downstream (retry/DLQ).
deal-stage-changed/deal-cancelled/deal-members-changedидут через outbox (at-least-once); консюмеры идемпотентны (MessageId, для терминальных — first-seenDealId). Транзиентная ошибка — ретрай; после исчерпания — DLQ и ручной разбор. Само вмешательство вcrmиз-за падения downstream не откатывается.
Наблюдаемость и НФТ
- Логи (аудит вмешательства): каждое ручное действие —
DealId,TenantIdцелевой сделки, actor (Менеджер ПИН), тип операции,From → Toстатус/этап и комментарий/причина; факт скачивания PII-документа (documentId, тип, actor) логируется как доступ. Содержимое документов, полныйEscrowAccountNumberи raw- PII в логи не пишутся (эскроу — маска****NNNN). - Метрики: число ручных override/move-step (в разрезе
TenantId/менеджера); доляCRM_DEAL_ALREADY_TERMINAL/CRM_DEAL_STEP_FORBIDDEN/CONCURRENCY_CONFLICT; число скачиваний PII-документов (контроль доступа); лаг синхронизации состава чата. - Латентность/нагрузка: интерактивные команды — P95 в пределах сотен мс (ADR-0056); список платформы (кросс-tenant) — пагинированная выборка, целевая латентность как у обычных списков.
- Доступ/безопасность: только platform-scope permission (
crm.deals.*,crm.deal-documents.view,crm.pipeline.manage); ручной таймлайн — толькоPinManager/Служба заботы (INV-TLE-5); эскроу наружу — всегда маска.
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (7): UC-CRM-003. Движение сделки по канбан-доске, UC-CRM-005. Оформление сделки: договоры → эскроу → Росреестр → завершение, UC-CRM-007. Добавление участника в сделку, UC-CRM-008. Снятие участника со сделки, UC-SUP-004. Ручное вмешательство через permissions, US-реестр «Участники и владение процессами» (US-CRM-PART / US-AG-ADMIN / US-AGT-MULTI / US-INV-CO), US-реестр блока «Сделка» (US-INV / US-AGT / US-DEV / US-MGR)
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API методы (будущие пути по конвенции): pin-manager-list-deals.md, pin-manager-get-deal.md, pin-manager-override-deal-status.md, pin-manager-move-deal-step.md, pin-manager-add-timeline-note.md, pin-manager-download-deal-document.md.
- Domain: deal.md (статусы/инварианты INV-DEAL-5..9, роль
PinManager), deal-timeline-event.md (ManualNote, INV-TLE-5), pipeline-step.md (PipelineStepGroup). - Kafka-контракты: deal-stage-changed.md, deal-cancelled.md, deal-members-changed.md, deal-created.md.
- Реестр US и роль Менеджера ПИН: user-stories-deal.md; контекст: README.md.
- Чат сделки —
docs/06-services/messenger/(ADR-0055); tenancy — ADR-0052.