Event Contract: pin.crm.fixation-rejected (FixationRejectedEvent)
Версионируемый контракт интеграционного события «Фиксация отклонена». Consumer-документы (notifications)
ссылаются сюда, не переописывая payload. Дом полей агрегата —
entity: FixationRequestEntity.
Назначение
Фиксирует отклонение заявки на фиксацию менеджером застройщика: переход FixationRequestEntity
Pending → Rejected (INV-FIX-3). Публичное: даёт notifications факт решения без синхронного запроса в
crm, чтобы уведомить агента-инициатора («заявка на фиксацию клиента отклонена», с причиной). В отличие от
одобрения (fixation-approved.md), сделка не создаётся и права на комиссию не
возникает — событие потребляет только notifications.
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.crm.fixation-rejected |
| Topic | pin.crm.fixation-rejected.v1 |
| Partitions / key | 3 (ADR-0056, целевой объём 1–10k активных) / key = AggregateId (= FixationRequestId) |
| ContractVersion | 1 |
| Producer | crm: отклонение заявки на фиксацию менеджером застройщика (INV-FIX-3/INV-FIX-5) |
| Статус | draft |
Событие публикуется атомарно с переходом статуса агрегата: пока отклонение не зафиксировано, событие не
отправляется; после фиксации — доставляется гарантированно (at-least-once), ключ = FixationRequestId
(порядок per-fixation).
Payload FixationRejectedEvent
Snapshot решения по заявке. Все поля — из entity: FixationRequestEntity;
здесь перечислено только то, что реально в событии.
Payload не содержит ФИО/телефона клиента (ClientName/ClientPhoneNumber) и хотелок — только
идентификаторы сторон, причину и момент решения.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
FixationRequestId | Guid | Да | FixationRequestEntity.Id | Immutable | Ключ агрегата (= AggregateId = Kafka key). |
TenantId | Guid | Да | FixationRequestEntity.TenantId | Immutable | Tenant boundary = DeveloperCompanyId (застройщик, вынесший решение; ADR-0052). |
OriginAgencyCompanyId | Guid | Да | FixationRequestEntity.OriginAgencyCompanyId | Immutable | Агентство-основатель заявки (маршрутизация уведомления на сторону агентства). |
OriginAgentUserId | Guid | Да | FixationRequestEntity.OriginAgentUserId | Immutable | Агент-инициатор на момент подачи (историческая атрибуция); контекст уведомления. |
ExecutorAgentUserId | Guid | Да | FixationRequestEntity.ExecutorAgentUserId | Additive only | Текущий участник-исполнитель заявки — получатель уведомления «заявка отклонена». |
InvestorUserId | Guid | Да | FixationRequestEntity.InvestorUserId | Immutable | Инвестор (клиент) заявки. Инвестор о фиксациях не уведомляется — поле для контекста агента. |
RejectedByUserId | Guid | Да | FixationRequestEntity.DecidedByUserId | Immutable | Менеджер застройщика, отклонивший заявку (на момент Rejected — непусто). |
Reason | string | Да | FixationRequestEntity.RejectReason | Additive only | Причина отклонения (varchar(1000)). Гарантированно непуста при Rejected (check ck_fixation_reject_reason, INV-FIX-5). Free-text — правило логирования ниже. |
RejectedAt | DateTimeOffset | Да | FixationRequestEntity.DecidedAt | Immutable | Момент решения (UTC-offset). Непусто при Rejected. |
Вложенных моделей нет: событие несёт только скалярные идентификаторы и причину.
Когда публикуется
| Условие | Переход состояния | Публикация |
|---|---|---|
Менеджер застройщика отклоняет заявку из Pending (актор — менеджер компании TenantId, INV-FIX-3; RejectReason задан, INV-FIX-5) | Pending → Rejected | атомарно со сменой состояния агрегата |
Одобрение (Pending → FixedUnique/FixedNonUnique) и отмена агентом (Pending → Cancelled) —
отдельные события/без события: одобрение публикует fixation-approved.md,
отмена агентом (Cancel) события notifications не порождает.
Событие публикуется один раз (переход терминальный, Rejected → [*]).
Потребители (consumers)
| Consumer-контекст | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
notifications | Уведомление исполнителю (ExecutorAgentUserId) «Заявка на фиксацию отклонена» с Reason; канал — по настройкам получателя | MessageId (first-seen) | < 5 мин |
Доставка, порядок и обработка сбоев
- Гарантия публикации. Событие уходит только после фиксации перехода
Pending → Rejected(INV-FIX-3/5); при откате не публикуется. Доставка — at-least-once, ключFixationRequestId; переход терминален — событие эмитится один раз. - Порядок. События одной заявки упорядочены ключом
FixationRequestId. - Идемпотентность потребителя.
notifications— first-seen поMessageId: повторная доставка не шлёт второе уведомление агенту. - Retry. Транзиентный сбой потребителя → повтор обработки с backoff; событие доступно до успешной обработки.
- DLQ. Неустранимый сбой после исчерпания попыток → dead-letter c алертом; проигрывание безопасно
(first-seen по
MessageId).
Версионирование и совместимость
- Версия — один
ContractVersion= 1; при несовпадении версии контракта потребитель логирует предупреждение и продолжает обработку. Топик кодирует версию по конвенции — суффикс.v1. - Additive-only для minor: новые поля добавляются как optional с безопасным default — без bump.
- Breaking (удаление/переименование/смена типа или смысла) — новый
ContractVersion = 2+ новый топикpin.crm.fixation-rejected.v2+ параллельная публикация (dual-publish) на период миграции + deadline миграцииnotifications+ sunset.v1. - Unknown-поля игнорируются потребителем (forward-compatible); enum в payload отсутствуют.
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
FixationRequestId, TenantId, OriginAgencyCompanyId, OriginAgentUserId, ExecutorAgentUserId, InvestorUserId, RejectedByUserId | Internal | include |
RejectedAt | Internal | include |
Reason | Internal (free-text менеджера) | include; в логах producer/consumer — не логировать тело, только FixationRequestId (комментарий может содержать ФИО/телефон/иные PII, введённые вручную) |
Reason — свободный текст менеджера и может содержать ФИО/телефон/иные PII, введённые вручную. В логах
producer/consumer записывать только FixationRequestId — тело Reason в логи не выводить.
Событие не содержит ФИО/телефона клиента (ClientName/ClientPhoneNumber) — маскирование PII на
уровне payload не требуется, т.к. эти поля в него не входят; notifications берёт display-данные из
собственной реплики (pin.identity.users.v1), а не из этого события.
- Доступ к топику — m2m ACL Kafka: продюсер
crm, единственный consumernotifications. - Событие НЕ содержит secrets, tokens, raw credentials, платёжных реквизитов, документов, unbounded вложений.
- Actor-производитель — менеджер застройщика (permission
crm.fixations.decide, tenant =DeveloperCompanyId); правило записи решения — fixation-request.md (Безопасность, INV-FIX-3).
Наблюдаемость
- Логи (без PII и без тела причины): факт публикации
fixation-rejectedсFixationRequestId/ExecutorAgentUserId/OriginAgentUserId/OriginAgencyCompanyId/RejectedByUserId/RejectedAt;Reason(free-text) не логировать — толькоFixationRequestId(может содержать ФИО/телефон, введённые вручную). - Метрики: число отклонений по топику; сквозная задержка до
notifications(< 5 мин); глубина DLQ (порог алерта > 0); доля повторных доставок. - Доступ: топик — m2m ACL Kafka (единственный consumer
notifications); ФИО/телефон клиента в payload отсутствуют.
Acceptance Criteria
- Given заявка на фиксацию в
Pending, When менеджер застройщика отклоняет её с указанием причины (INV-FIX-5), Then переходPending → Rejected, ровно одноfixation-rejectedпубликуется с непустымReasonиRejectedAt; сделка не создаётся, право на комиссию не возникает. - Given отклонение без причины, When валидация, Then операция отклоняется (check
ck_fixation_reject_reason, INV-FIX-5) — событие с пустымReasonневозможно. - Given решение
Approveили отзыв агентомCancel, When обрабатывается, Thenfixation-rejectedне публикуется (одобрение — fixation-approved;Cancel— без события). - Given повторная доставка события, When
notificationsобрабатывает, Then уведомление агенту уходит один раз (first-seen поMessageId). - Given breaking-изменение payload, When новый контракт, Then
pin.crm.fixation-rejected.v2+ dual-publish + sunset.v1.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (7): GET /api/agent/crm/fixations — Список фиксаций агентства (+ поиск), GET /api/developer/crm/fixations — Список фиксаций застройщика (+ FTS), GET /api/pin-manager/crm/fixations — Сквозной список всех фиксаций платформы, GET /api/pin-manager/crm/fixations/{fixationId} — Карточка любой фиксации (Менеджер ПИН), POST /api/agent/crm/fixations — Отправить заявку на фиксацию, POST /api/agent/crm/fixations/{fixationId}/cancel — Отозвать фиксацию, POST /api/developer/crm/fixations/{fixationId}/reject — Отклонить фиксацию
- Use Cases (2): UC-FIX-001. Агент отправляет заявку на фиксацию, UC-FIX-003. Застройщик отклоняет фиксацию
- События (1): Event Contract: pin.crm.fixation-approved (FixationApprovedEvent)
- scenarios (1): UC-OVR-005. От лида до сделки (4 фазы)
- registries (1): Реестр событий PIN
Связанные документы
- Дом агрегата и переходы: fixation-request.md (INV-FIX-3/5, поля решения).
- Соседний контракт решения: fixation-approved.md; карта событий контекста: crm/README.md.
- ADR: 0052 tenancy двухсторонних сущностей, 0056 simplicity-first.
- Consumer:
notifications—../../notifications/consumers/fixation-rejected.md(будущий путь по конвенции).