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

UC-FIX-002. Застройщик одобряет фиксацию (авторизация работы с клиентом, без создания сделки)

Назначение

Менеджер застройщика рассматривает заявку на фиксацию в статусе Pending (с подсветкой дублей) и одобряет её как уникальную/не уникальную. Одобрение — это авторизация, а не сделка: застройщик подтверждает право агента вести этого клиента по своим объектам (пара «клиент × застройщик»), но сделку не создаёт. Одна одобренная фиксация авторизует агенту завести сколько угодно сделок по объектам этого застройщика (связь фиксация→сделки 1:N, ведётся со стороны сделки). Дом полей — ../domain/fixation-request.md и ../domain/deal.md; переход отклонения — UC-FIX-003.

Metadata

ПолеЗначение
IDUC-FIX-002
Контекстcrm (см. ../README.md, модуль фиксации)
Типосновной
Статусdraft

Актор

ПолеЗначение
РольМенеджер застройщика
Permissions / scopecrm.fixations.decide; scope — организация застройщика (только заявки своей компании)
TenantTenantId = DeveloperCompanyId (ADR-0052)
Record-levelЗаявки с FixationRequestEntity.TenantId = actor.TenantId; кросс-tenant — только Менеджер ПИН (platform-scope)

Предусловие

  • Заявка на фиксацию существует и находится в статусе Pending (создана по UC-FIX-001).
  • FixationRequestEntity.TenantId совпадает с DeveloperCompanyId актора.
  • Авто-дубль-детект выполнен: Uniqueness ∈ {NoDuplicates, HasDuplicates}; при HasDuplicates заполнены DuplicateOfFixationId/DuplicateMatchKind, UI подсвечивает дубль (../domain/fixation-request.md, раздел «Дубль-детект»).

Триггер

ПолеЗначение
ИсточникВызов API (по UI-действию)
Триггерящий элементСтраница фиксации застройщика /crm/fixations/[id], кнопка «Одобрить: уникальный / не уникальный» → POST /api/developer/crm/fixations/{id}/approve

Основной Поток

Модель запроса ApproveFixationRequest { FixationRequestId, Kind: FixationApprovalKind (FixedUnique/FixedNonUnique), ForceUnique: bool }.

  1. Контекст и доступ. Из идентити разрешается actor и TenantId, проверяется permission crm.fixations.decide. Операция не идемпотентна по ключу — защита от повтора обеспечивается guard-ом статуса (шаг 2) и оптимистичной блокировкой.
  2. Проверка входа и предусловий. Kind ∈ {FixedUnique, FixedNonUnique}; ForceUnique применим только к FixedUnique (иначе VALIDATION_ERROR). Заявка существует, иначе CRM_FIXATION_NOT_FOUND (404). Состояние Status = Pending (INV-FIX-3), иначе CRM_FIXATION_INVALID_STATE. Принадлежность fixation.TenantId = actor.TenantId, иначе CRM_FIXATION_FORBIDDEN. Дубль-guard (INV-FIX-5): Kind = FixedUnique допустим только при Uniqueness = NoDuplicates или ForceUnique = true (ручное подтверждение уникальности при дублях фиксируется в audit), иначе CRM_FIXATION_DUPLICATE_CONFLICT.
  3. Что читаем. Заявку (с оптимистичной блокировкой — гонка «агент отменяет / застройщик одобряет»); лид (лента событий, ../domain/lead.md). Этап канбана и сделка не читаются и не создаются — одобрение только авторизует; заведение сделок агент выполняет отдельно (UC-CRM-002).
  4. Бизнес-правила и переходы (INV-FIX-6). Заявка одобряется: переход Pending → FixedUnique|FixedNonUnique, маппинг Status ← Kind, Uniqueness не меняется (результат авто-детекта), DecidedByUserId ← actor, DecidedAt ← now; событие FixationDecided. Сделка не создаётся, ссылки на сделку заявка не хранит (DealId в фиксации отсутствует; связь ведётся со стороны сделки — DealEntity.FixationRequestId, 1:N). Одобрение — авторизация права агента вести клиента по объектам застройщика.
  5. Что меняется. Обновляется только заявка (Status/DecidedByUserId/DecidedAt) и, при необходимости, лента лида (информационное событие «фиксация одобрена»). Новых сущностей (сделки, участников) не появляется.
  6. Какие события фиксируются. В том же коммите через outbox: FixationApprovedEvent (топик pin.crm.fixation-approved.v1, AggregateId = fixation.Id) — payload из fixation.* + CommissionRecipientUserId (= ExecutorAgentUserId на момент одобрения) + Uniqueness, без DealId (../events/fixation-approved.md). Событие deal-created не публикуется (сделки нет).
  7. Согласованность. Переход статуса фиксации + запись решения + лид-событие + outbox-запись фиксируются атомарно в одном согласованном изменении.
  8. Результат. Возвращаются FixationRequestId, Status, DecidedAt. Фронт показывает одобренную заявку; агент теперь может заводить сделки по объектам застройщика (UC-CRM-002).

Альтернативные Потоки

A1. Одобрение «не уникальный» (FixedNonUnique)

  1. Менеджер видит подсвеченный дубль (Uniqueness = HasDuplicates) и выбирает «не уникальный».
  2. Kind = FixedNonUnique — дубль-guard INV-FIX-5 не применяется; переход Pending → FixedNonUnique.
  3. Далее как основной поток (шаги 4–8). В FixationApprovedEvent.Uniqueness = NonUnique (условия комиссии решает застройщик/rewards).
Возврат к основному потоку

Продолжение — шаги 4–8 основного потока.

A2. Уникальность вручную при дублях (ForceUnique)

  1. Есть дубли (HasDuplicates), но менеджер подтверждает уникальность вручную (Kind = FixedUnique, ForceUnique = true).
  2. INV-FIX-5 пропускает переход; факт ручного подтверждения фиксируется в audit.
  3. Далее основной поток; Uniqueness = Unique в событии.
Возврат к основному потоку

Продолжение — шаги 4–8 основного потока; факт ForceUnique зафиксирован в audit.

A3. Отклонение вместо одобрения

Менеджер выбирает «Отклонить» → отдельный сценарий UC-FIX-003 (Pending → Rejected, FixationRejectedEvent). Возврат к основному потоку невозможен.

Не возврат к основному потоку

Заявка уходит в отдельный сценарий отклонения (UC-FIX-003); к основному потоку одобрения не возвращается.

Постусловие

  • Заявка: Status ∈ {FixedUnique, FixedNonUnique}, заполнены DecidedByUserId/DecidedAt. Ссылки на сделку заявка не содержит.
  • Сделка не создаётся. Одобренная фиксация авторизует агенту заводить сделки по объектам застройщика (пара «клиент × застройщик», связь 1:N со стороны сделки).
  • В outbox — FixationApprovedEvent без DealId (доставится rewards/notifications).

Возможные Ошибки

КодHTTPУсловиеПоведение клиента
CRM_FIXATION_NOT_FOUND404Заявки нетОбновить список; заявка удалена/недоступна
CRM_FIXATION_FORBIDDEN403fixation.TenantId ≠ actor.TenantId или нет crm.fixations.decideСкрыть действие; заявка не этой компании
CRM_FIXATION_INVALID_STATE409Status ≠ Pending (уже решена/отменена, INV-FIX-3)Перечитать карточку — решение уже принято
CRM_FIXATION_DUPLICATE_CONFLICT422Kind = FixedUnique при HasDuplicates без ForceUnique (INV-FIX-5)Показать дубль; выбрать «не уникальный» или подтвердить ForceUnique
VALIDATION_ERROR400Kind вне enum / ForceUnique не с FixedUniqueИсправить параметры запроса
CRM_CONCURRENCY_CONFLICT409Проигрыш гонки конкурентной правки с Cancel/параллельным ApproveПеречитать состояние и повторить

Доменные События

СобытиеКогда поднимаетсяSubscribers
FixationDecidedОдобрение (Pending → Fixed*)Внутренний обработчик — сборка FixationApprovedEvent в outbox

Kafka Интеграционные События

СобытиеTopicКогда публикуетсяConsumers
FixationApprovedEventpin.crm.fixation-approved.v1Тот же коммит одобрения (INV-FIX-6), без DealIdrewards (право/eligibility исполнителя; комиссия — при завершении сделки), notifications (агенту «фиксация одобрена») — ../events/fixation-approved.md

Событие deal-created в этом сценарии не публикуется — сделки при одобрении нет. Сделки агент создаёт отдельно (UC-CRM-002), каждая несёт свой DealCreatedEvent.

Acceptance Criteria

#КритерийПроверка
AC1Одобрение переводит заявку Pending → FixedUnique/FixedNonUnique с DecidedByUserId/DecidedAtIntegration-тест: до/после статус и поля решения
AC2При одобрении сделка не создаётся, заявка ссылки на сделку не хранитТест: Count(deals) не изменился; в заявке нет DealId
AC3Одна одобренная фиксация авторизует несколько сделок: после одобрения агент заводит N сделок по разным объектам застройщика, у всех FixationRequestId = одной фиксации (не unique)Тест: N сделок с одним FixationRequestId создаются успешно
AC4FixedUnique при HasDuplicates без ForceUniqueCRM_FIXATION_DUPLICATE_CONFLICT; с ForceUnique → успех + auditТест INV-FIX-5 (оба ветвления)
AC5Менеджер другого тенанта / без crm.fixations.decideCRM_FIXATION_FORBIDDEN; повторное одобрение решённой → CRM_FIXATION_INVALID_STATEТесты изоляции и guard статуса
AC6После коммита в outbox ровно одна запись fixation-approved.v1 без DealId; события deal-created.v1 нет; при откате транзакции — нольТест атомарности (outbox + rollback)

Кросс-сервисные ожидания и гарантии

Одобрение — синхронная HTTP-команда авторизации. Оно порождает одну асинхронную цепочку: право/eligibility исполнителя и уведомление агенту. Чат/сделка здесь не создаются — они появляются позже, при заведении агентом сделок (UC-CRM-002).

Что crm предоставляет (исходящие гарантии):

КонтрактКлюч / версияPayload (ключевое)Кому
pin.crm.fixation-approved.v1key = FixationRequestId, ContractVersion = 1FixationRequestId, TenantId, OriginAgencyCompanyId, OriginAgentUserId, ExecutorAgentUserId, CommissionRecipientUserId (= ExecutorAgentUserId на момент одобрения), InvestorUserId, Status, Uniqueness, DecidedByUserId, DecidedAt (DealId не передаётся — сделки нет)rewards (право/eligibility текущего исполнителя; основание — по Origin), notifications (агенту)

Событие публикуется через outbox атомарно с одобрением: пока изменение не зафиксировано — не отправлено; при откате транзакции — ноль записей (AC6). Uniqueness в fixation-approved — публичный вид одобрения (Unique/NonUnique), не доменный авто-детект.

Что операция ожидает от соседей:

  • rewards — на fixation-approved фиксирует право/eligibility исполнителя (first-seen по FixationRequestId); начисление комиссии возникает не здесь, а при deal-completed конкретной сделки.
  • notifications — резолвят ФИО/контакты по собственной реплике identity; raw-PII в событиях нет. Инвестор о факте фиксации не уведомляется (о фиксациях не знает).

Промежуточное состояние (eventual-consistency): после ответа заявка сразу в одобренном статусе (данные crm); право (eligibility) в rewards и уведомление агенту приходят с задержкой (минуты). Сделок в этот момент нет — они появятся, когда агент их заведёт.

Идемпотентность, конкурентность и восстановление

  • Идемпотентность. Ключа идемпотентности нет; защита от повтора — guard статуса Pending (INV-FIX-3): повторное одобрение уже решённой заявки → CRM_FIXATION_INVALID_STATE. Так гарантируется единственность решения; поскольку сделка не создаётся, риска двойной сделки при повторе нет в принципе.
  • Конкурентность. Гонка «застройщик одобряет / агент отзывает» и параллельные Approve разрешаются оптимистичной блокировкой: победитель коммитит, проигравший — CRM_CONCURRENCY_CONFLICT (409) и перечитывает состояние (заявка окажется Fixed* либо Cancelled).
  • Доставка downstream (retry/DLQ). FixationApprovedEvent — at-least-once через outbox; консюмер rewards идемпотентен (MessageId + FixationRequestId). Транзиентная ошибка — ретрай; при исчерпании — DLQ (eligibility/уведомление не доставлено до ручного разбора). Одобрение в crm при этом не откатывается.
  • Компенсаций нет. Одобрение — атомарный шаг авторизации; отзыв авторизации моделируется бизнес-правилами фиксации/сделок, не компенсацией одобрения.

Наблюдаемость и НФТ

  • Логи (без PII): FixationRequestId, TenantId, Kind/Uniqueness, ForceUnique, actor, исход; факт ручного подтверждения уникальности при дублях (ForceUnique = true) — отдельная audit-запись. ФИО/телефон клиента в логи не пишутся.
  • Метрики: число одобрений (в разрезе Unique/NonUnique); доля ForceUnique; доля CRM_FIXATION_DUPLICATE_CONFLICT/CRM_FIXATION_INVALID_STATE.
  • Латентность/нагрузка: интерактивная команда — P95 в пределах сотен мс; низкообъёмная сущность (ADR-0056); downstream-окно — минуты (SLA — в контрактах событий).
  • Доступ: crm.fixations.decide в scope застройщика; только заявки своего TenantId (кросс-tenant — Менеджер ПИН); нарушение — CRM_FIXATION_FORBIDDEN.

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

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

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