UC-FIX-002. Застройщик одобряет фиксацию (авторизация работы с клиентом, без создания сделки)
Назначение
Менеджер застройщика рассматривает заявку на фиксацию в статусе Pending (с подсветкой дублей) и одобряет
её как уникальную/не уникальную. Одобрение — это авторизация, а не сделка: застройщик подтверждает право
агента вести этого клиента по своим объектам (пара «клиент × застройщик»), но сделку не создаёт. Одна
одобренная фиксация авторизует агенту завести сколько угодно сделок по объектам этого застройщика (связь
фиксация→сделки 1:N, ведётся со стороны сделки). Дом полей —
../domain/fixation-request.md и ../domain/deal.md;
переход отклонения — UC-FIX-003.
Metadata
| Поле | Значение |
|---|---|
| ID | UC-FIX-002 |
| Контекст | crm (см. ../README.md, модуль фиксации) |
| Тип | основной |
| Статус | draft |
Актор
| Поле | Значение |
|---|---|
| Роль | Менеджер застройщика |
| Permissions / scope | crm.fixations.decide; scope — организация застройщика (только заявки своей компании) |
| Tenant | TenantId = 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 }.
- Контекст и доступ. Из идентити разрешается actor и
TenantId, проверяется permissioncrm.fixations.decide. Операция не идемпотентна по ключу — защита от повтора обеспечивается guard-ом статуса (шаг 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. - Что читаем. Заявку (с оптимистичной блокировкой — гонка «агент отменяет / застройщик одобряет»); лид (лента событий, ../domain/lead.md). Этап канбана и сделка не читаются и не создаются — одобрение только авторизует; заведение сделок агент выполняет отдельно (UC-CRM-002).
- Бизнес-правила и переходы (INV-FIX-6). Заявка одобряется: переход
Pending → FixedUnique|FixedNonUnique, маппингStatus ← Kind,Uniquenessне меняется (результат авто-детекта),DecidedByUserId ← actor,DecidedAt ← now; событиеFixationDecided. Сделка не создаётся, ссылки на сделку заявка не хранит (DealIdв фиксации отсутствует; связь ведётся со стороны сделки —DealEntity.FixationRequestId, 1:N). Одобрение — авторизация права агента вести клиента по объектам застройщика. - Что меняется. Обновляется только заявка (
Status/DecidedByUserId/DecidedAt) и, при необходимости, лента лида (информационное событие «фиксация одобрена»). Новых сущностей (сделки, участников) не появляется. - Какие события фиксируются. В том же коммите через outbox:
FixationApprovedEvent(топикpin.crm.fixation-approved.v1,AggregateId = fixation.Id) — payload изfixation.*+CommissionRecipientUserId(=ExecutorAgentUserIdна момент одобрения) +Uniqueness, безDealId(../events/fixation-approved.md). Событиеdeal-createdне публикуется (сделки нет). - Согласованность. Переход статуса фиксации + запись решения + лид-событие + outbox-запись фиксируются атомарно в одном согласованном изменении.
- Результат. Возвращаются
FixationRequestId,Status,DecidedAt. Фронт показывает одобренную заявку; агент теперь может заводить сделки по объектам застройщика (UC-CRM-002).
Альтернативные Потоки
A1. Одобрение «не уникальный» (FixedNonUnique)
- Менеджер видит подсвеченный дубль (
Uniqueness = HasDuplicates) и выбирает «не уникальный». Kind = FixedNonUnique— дубль-guard INV-FIX-5 не применяется; переходPending → FixedNonUnique.- Далее как основной поток (шаги 4–8). В
FixationApprovedEvent.Uniqueness = NonUnique(условия комиссии решает застройщик/rewards).
Продолжение — шаги 4–8 основного потока.
A2. Уникальность вручную при дублях (ForceUnique)
- Есть дубли (
HasDuplicates), но менеджер подтверждает уникальность вручную (Kind = FixedUnique,ForceUnique = true). - INV-FIX-5 пропускает переход; факт ручного подтверждения фиксируется в audit.
- Далее основной поток;
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_FOUND | 404 | Заявки нет | Обновить список; заявка удалена/недоступна |
CRM_FIXATION_FORBIDDEN | 403 | fixation.TenantId ≠ actor.TenantId или нет crm.fixations.decide | Скрыть действие; заявка не этой компании |
CRM_FIXATION_INVALID_STATE | 409 | Status ≠ Pending (уже решена/отменена, INV-FIX-3) | Перечитать карточку — решение уже принято |
CRM_FIXATION_DUPLICATE_CONFLICT | 422 | Kind = FixedUnique при HasDuplicates без ForceUnique (INV-FIX-5) | Показать дубль; выбрать «не уникальный» или подтвердить ForceUnique |
VALIDATION_ERROR | 400 | Kind вне enum / ForceUnique не с FixedUnique | Исправить параметры запроса |
CRM_CONCURRENCY_CONFLICT | 409 | Проигрыш гонки конкурентной правки с Cancel/параллельным Approve | Перечитать состояние и повторить |
Доменные События
| Событие | Когда поднимается | Subscribers |
|---|---|---|
FixationDecided | Одобрение (Pending → Fixed*) | Внутренний обработчик — сборка FixationApprovedEvent в outbox |
Kafka Интеграционные События
| Событие | Topic | Когда публикуется | Consumers |
|---|---|---|---|
FixationApprovedEvent | pin.crm.fixation-approved.v1 | Тот же коммит одобрения (INV-FIX-6), без DealId | rewards (право/eligibility исполнителя; комиссия — при завершении сделки), notifications (агенту «фиксация одобрена») — ../events/fixation-approved.md |
Событие deal-created в этом сценарии не публикуется — сделки при одобрении нет. Сделки агент создаёт
отдельно (UC-CRM-002), каждая несёт свой DealCreatedEvent.
Acceptance Criteria
| # | Критерий | Проверка |
|---|---|---|
| AC1 | Одобрение переводит заявку Pending → FixedUnique/FixedNonUnique с DecidedByUserId/DecidedAt | Integration-тест: до/после статус и поля решения |
| AC2 | При одобрении сделка не создаётся, заявка ссылки на сделку не хранит | Тест: Count(deals) не изменился; в заявке нет DealId |
| AC3 | Одна одобренная фиксация авторизует несколько сделок: после одобрения агент заводит N сделок по разным объектам застройщика, у всех FixationRequestId = одной фиксации (не unique) | Тест: N сделок с одним FixationRequestId создаются успешно |
| AC4 | FixedUnique при HasDuplicates без ForceUnique → CRM_FIXATION_DUPLICATE_CONFLICT; с ForceUnique → успех + audit | Тест INV-FIX-5 (оба ветвления) |
| AC5 | Менеджер другого тенанта / без crm.fixations.decide → CRM_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.v1 | key = FixationRequestId, ContractVersion = 1 | FixationRequestId, 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.
Обратные ссылки
Автоссылки: где используется этот документ.
- Use Cases (4): UC-CRM-002. Агент создаёт сделку из лида (выбор объекта, гейт вовлечения застройщика), UC-CRM-017. Flow B: агент предлагает объект → Draft-сделка → подтверждение инвестором → вовлечение застройщика, UC-CRM-018. Несколько сделок из одной одобренной фиксации (по разным объектам застройщика), UC-FIX-003. Застройщик отклоняет фиксацию
- scenarios (2): UC-OVR-004. От реферальной ссылки до объекта у собственника, UC-OVR-005. От лида до сделки (4 фазы)
- registries (1): Реестр use cases и user stories PIN
Связанные Документы
- API метод (будущий путь):
../api/developer/approve-fixation.md. - Domain entity: ../domain/fixation-request.md (INV-FIX-3/5/6,
FixationStatus, одобрение = авторизация без сделки), ../domain/deal.md (INV-DEAL-1/2/11,DealStatus,FixationRequestId— 1 фиксация → N сделок), ../domain/lead.md (лента событий), ../domain/lead.md (намерение по объекту — дедуп сделок). - События: ../events/fixation-approved.md (без
DealId). - Смежные UC: UC-FIX-001 (агент отправляет фиксацию), UC-FIX-003 (отклонение), UC-CRM-002 (создание сделки из авторизованного клиента). Реестр US — user-stories-deal.md.
- ADR: 0052 tenancy, 0055 мини-мессенджер, 0056 simplicity-first, 0071 company-ownership и участники.