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

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 typepin.crm.fixation-rejected
Topicpin.crm.fixation-rejected.v1
Partitions / key3 (ADR-0056, целевой объём 1–10k активных) / key = AggregateId (= FixationRequestId)
ContractVersion1
Producercrm: отклонение заявки на фиксацию менеджером застройщика (INV-FIX-3/INV-FIX-5)
Статусdraft

Событие публикуется атомарно с переходом статуса агрегата: пока отклонение не зафиксировано, событие не отправляется; после фиксации — доставляется гарантированно (at-least-once), ключ = FixationRequestId (порядок per-fixation).

Payload FixationRejectedEvent

Snapshot решения по заявке. Все поля — из entity: FixationRequestEntity; здесь перечислено только то, что реально в событии.

Без PII клиента

Payload не содержит ФИО/телефона клиента (ClientName/ClientPhoneNumber) и хотелок — только идентификаторы сторон, причину и момент решения.

ПолеТипОбязательностьИсточник (поле агрегата)CompatibilityОписание
FixationRequestIdGuidДаFixationRequestEntity.IdImmutableКлюч агрегата (= AggregateId = Kafka key).
TenantIdGuidДаFixationRequestEntity.TenantIdImmutableTenant boundary = DeveloperCompanyId (застройщик, вынесший решение; ADR-0052).
OriginAgencyCompanyIdGuidДаFixationRequestEntity.OriginAgencyCompanyIdImmutableАгентство-основатель заявки (маршрутизация уведомления на сторону агентства).
OriginAgentUserIdGuidДаFixationRequestEntity.OriginAgentUserIdImmutableАгент-инициатор на момент подачи (историческая атрибуция); контекст уведомления.
ExecutorAgentUserIdGuidДаFixationRequestEntity.ExecutorAgentUserIdAdditive onlyТекущий участник-исполнитель заявки — получатель уведомления «заявка отклонена».
InvestorUserIdGuidДаFixationRequestEntity.InvestorUserIdImmutableИнвестор (клиент) заявки. Инвестор о фиксациях не уведомляется — поле для контекста агента.
RejectedByUserIdGuidДаFixationRequestEntity.DecidedByUserIdImmutableМенеджер застройщика, отклонивший заявку (на момент Rejected — непусто).
ReasonstringДаFixationRequestEntity.RejectReasonAdditive onlyПричина отклонения (varchar(1000)). Гарантированно непуста при Rejected (check ck_fixation_reject_reason, INV-FIX-5). Free-text — правило логирования ниже.
RejectedAtDateTimeOffsetДаFixationRequestEntity.DecidedAtImmutableМомент решения (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, RejectedByUserIdInternalinclude
RejectedAtInternalinclude
ReasonInternal (free-text менеджера)include; в логах producer/consumer — не логировать тело, только FixationRequestId (комментарий может содержать ФИО/телефон/иные PII, введённые вручную)
Reason — free-text, не логировать тело

Reason — свободный текст менеджера и может содержать ФИО/телефон/иные PII, введённые вручную. В логах producer/consumer записывать только FixationRequestId — тело Reason в логи не выводить.

Без PII клиента

Событие не содержит ФИО/телефона клиента (ClientName/ClientPhoneNumber) — маскирование PII на уровне payload не требуется, т.к. эти поля в него не входят; notifications берёт display-данные из собственной реплики (pin.identity.users.v1), а не из этого события.

  • Доступ к топику — m2m ACL Kafka: продюсер crm, единственный consumer notifications.
  • Событие НЕ содержит 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 обрабатывается, Then fixation-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.

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

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

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