Event Contract: pin.crm.document-status-changed (DocumentStatusChangedEvent)
Версионируемый контракт интеграционного события «статус проверки документа сделки изменён». Consumer-документы
ссылаются сюда, не переописывая payload. Дом полей — entity: DealDocumentEntity.
Назначение
Фиксирует смену статуса проверки документа сделки: файл загружен, взят на проверку, принят или отклонён
(DealDocumentStatus: Uploaded → UnderReview → Accepted/Rejected, US-INV-14 — «инвестор видит: загружено,
на проверке, принято, отклонено; если отклонён — указана причина»). Producer — crm (загрузка/проверка
документа, инвариант INV-DOC-6). Публичное: даёт notifications snapshot перехода без синхронного запроса в
crm, чтобы уведомить инвестора о статусе его документа. Первичный запрос документа ([*] → Requested)
публикуется отдельным событием document-requested, а не этим.
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.crm.document-status-changed |
| Topic | pin.crm.document-status-changed.v1 |
| Partitions / key | 3 (crm-нагрузка 1–10k активных, ADR-0056) / key = AggregateId = DocumentId (порядок статусов одного документа) |
| ContractVersion | 1 |
| Producer | crm: загрузка документа, взятие в проверку, решение принять/отклонить — deal-document.md |
| Статус | draft |
Событие публикуется атомарно с мутацией DealDocumentEntity (INV-DOC-6): пока переход не зафиксирован,
событие не отправляется; после фиксации — доставляется гарантированно (at-least-once), ключ = DocumentId.
Payload DocumentStatusChangedEvent
Дом полей — entity: DealDocumentEntity (раздел «Доменные и интеграционные
события»); здесь перечислено только то, что реально в событии.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
DocumentId | Guid | Да | DealDocumentEntity.Id | Immutable | Ключ агрегата (= Kafka key). |
DealId | Guid | Да | DealDocumentEntity.DealId | Immutable | Сделка (deal.md); recipient-routing у consumer-а. |
TenantId | Guid | Да | DealDocumentEntity.TenantId | Immutable | Tenant boundary (= DeveloperCompanyId сделки, ADR-0052). |
DocumentType | string (enum DealDocumentType) | Да | DealDocumentEntity.Type | Additive only (новые значения) | ClientPassport / BookingContract / SignedBookingContract / BookingPaymentProof / ContractDraftDocument / SaleContract / SignedSaleContract / PaymentProof / RosreestrDocument / Other. |
FromStatus | string (enum DealDocumentStatus) | Да | статус до перехода | Additive only | Предыдущий статус (Requested/Uploaded/UnderReview). |
ToStatus | string (enum DealDocumentStatus) | Да | DealDocumentEntity.Status | Additive only | Новый статус (Uploaded/UnderReview/Accepted/Rejected). |
RejectReason | string? | Нет (обязателен при ToStatus = Rejected) | DealDocumentEntity.RejectReason | Additive only | Причина отклонения (US-INV-14; varchar(1000)). |
ChangedByUserId | Guid | Да | UploadedByUserId (переход в Uploaded) / ReviewedByUserId (в Accepted/Rejected) / actor взятия в проверку | Immutable semantics | Кто выполнил переход. |
ChangedAt | DateTimeOffset | Да | UploadedAt / ReviewedAt / UpdatedAt соответствующего перехода | Immutable | Момент перехода. |
Значения enum DealDocumentStatus/DealDocumentType — источник истины deal-document.md.
FileId, FileName, ContentType, SizeBytes в payload не входят: бинарь и метаданные файла остаются в
crm/storage, consumer-у для уведомления не нужны (скачивание — по пресайн-ссылке storage с проверкой участия).
Когда публикуется
| Условие | Переход состояния | Публикация |
|---|---|---|
| Сторона-владелец типа загрузила файл | Requested → Uploaded или [*] → Uploaded | атомарно со сменой состояния агрегата |
| Получатель взял на проверку (типы с проверкой) | Uploaded → UnderReview | та же |
| Решение принять | UnderReview → Accepted (терминальный) | та же |
| Решение отклонить | UnderReview → Rejected (терминальный; RejectReason задан) | та же |
Переход [*] → Requested не публикуется этим событием (см. document-requested).
Повторная загрузка после Rejected — новая запись DealDocumentEntity (INV-DOC-4), поэтому даёт новое
событие с FromStatus = Requested/Uploaded и своим DocumentId.
Потребители (consumers)
| Consumer-контекст | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
notifications | Уведомление инвестору о статусе его документа (US-INV-14.3); recipient (Investor сделки) резолвится из DealId по собственной deal-проекции consumer-а (питается deal-created/deal-members-changed); текст включает DocumentType, ToStatus, при Rejected — RejectReason | MessageId + монотонный guard по (DocumentId, ChangedAt) | < 5 мин |
Доставка, порядок и обработка сбоев
- Гарантия публикации. Событие уходит только после фиксации перехода статуса
DealDocumentEntity(INV-DOC-6); при откате не публикуется. Доставка — at-least-once, ключDocumentId. - Порядок и guard. Статусы одного документа упорядочены ключом
DocumentId; потребитель держит монотонный guard по (DocumentId,ChangedAt) — устаревшая доставка пропускается. - Идемпотентность потребителя.
notifications— дедуп поMessageId+ guard поChangedAt; повторная доставка не шлёт второе уведомление. - Retry. Транзиентный сбой потребителя → повтор обработки с backoff; событие доступно до успешной обработки.
- DLQ. Неустранимый сбой после исчерпания попыток → dead-letter c алертом; проигрывание безопасно (идемпотентно). Неизвестный статус — нейтральное уведомление (fail-safe), в DLQ не уходит.
Версионирование и совместимость
ContractVersion = 1; при несовпадении версии контракта потребитель логирует предупреждение и продолжает обработку.- Additive-only для minor: новые optional-поля с безопасным default — без bump топика.
- Breaking (удаление/переименование/смена типа/смысла) —
ContractVersion = 2+ новый топикpin.crm.document-status-changed.v2+ dual-publish на период миграции + deadline миграции consumer-ов + sunset.v1. - Порядок статусов одного документа гарантирован key-ом (
DocumentId); consumer держит guard поChangedAt.
Unknown enum (DocumentType/FromStatus/ToStatus) — fallback: сохранить строку как есть, warning; для
уведомления — нейтральный текст «статус документа обновлён» (fail safe, без падения консюмера).
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
DocumentId, DealId, TenantId, DocumentType, FromStatus, ToStatus, ChangedByUserId | Internal | include |
ChangedAt | Internal | include |
RejectReason | Internal (свободный текст) | include; в логах не печатать целиком (может содержать ФИО/детали) — Rejected(reason=***) |
- Actor type: система (producer —
crm; consumer —notifications). - Permission/policy: publish — внутренняя мутация под
crm.deal-documents.manage/.review; consume — m2m ACL топика (толькоnotifications); trusted/untrusted boundary — событие только из доверенногоcrm. - Tenant/scope:
TenantIdв payload; consumer уведомляет только участника-инвестора сделки (record-level).
Событие НЕ несёт ФИО, телефонов, файла и платёжных реквизитов — только идентификаторы, enum-статусы и
причина отклонения. ФИО/телефон получателя consumer берёт из своей реплики (маска в логах +7***1234,
ФИО — инициалы). Секретов, токенов, raw credentials, unbounded вложений событие не содержит.
Наблюдаемость
- Логи (без PII): факт публикации
document-status-changedсDocumentId/DealId/DocumentType/FromStatus→ToStatus/ChangedByUserId/ChangedAt;RejectReasonцеликом не печатать —Rejected(reason=***)(может содержать ФИО/детали). - Метрики: число переходов по топику в разрезе
ToStatus; доляRejected; сквозная задержка доnotifications(< 5 мин); глубина DLQ (порог алерта > 0); число срабатываний guard поChangedAt. - Доступ: топик — m2m ACL Kafka (только
notifications);FileId/бинарь/метаданные файла в событие не входят; ФИО/телефон получателя consumer резолвит из своей реплики и маскирует в логах (+7***1234).
Acceptance Criteria
- Given документ в статусе
Requested, When сторона-владелец типа загружает файл, Then ровно одноdocument-status-changedсRequested → Uploaded. - Given документ в
UnderReview, When проверяющий отклоняет, Then событиеUnderReview → Rejectedс непустымRejectReason(US-INV-14). - Given документ в
UnderReview, When проверяющий принимает, Then событиеUnderReview → Accepted(терминальный). - Given повторная загрузка после
Rejected, When создаётся новая запись документа (INV-DOC-4), Then событие несёт новыйDocumentId(старый терминален и не переиспользуется). - Given устаревшая/повторная доставка, When
notificationsобрабатывает, Then уведомление не задваивается (guard поChangedAt+MessageId). - Given переход
[*] → Requested, When документ запрошен, Then это событие не публикуется — фиксируетdocument-requested. - Given неизвестное значение статуса/типа, When потребитель обрабатывает, Then нейтральный текст «статус документа обновлён» + warning (без падения).
- Given breaking-изменение payload, When новый контракт, Then
pin.crm.document-status-changed.v2+ dual-publish + sunset.v1.
Обратные ссылки
Автоссылки: где используется этот документ.
- API (14): GET /api/developer/crm/deals/{dealId} — Карточка сделки (застройщик), GET /api/investor/crm/deals/{dealId} — Карточка сделки (инвестор), GET /api/investor/crm/deals/{dealId}/documents/{documentId}/download — Скачать документ сделки, GET /api/investor/crm/deals/{dealId}/timeline — Таймлайн сделки (инвестор), GET /api/pin-manager/crm/deals/{dealId} — Карточка любой сделки (Менеджер ПИН), POST /api/developer/crm/bookings/{bookingId}/confirm-documents — Подтвердить документы брони, POST /api/developer/crm/bookings/{bookingId}/contract — Загрузить договор брони, POST /api/developer/crm/deals/{dealId}/contracts-prepared — Отметить «Договоры подготовлены», POST /api/developer/crm/deals/{dealId}/documents/{documentId}/confirm — Подтвердить документы ДДУ/ДКП, POST /api/developer/crm/deals/{dealId}/documents/request — Запросить документы ДДУ/ДКП, POST /api/developer/crm/deals/{dealId}/rosreestr — Подтвердить регистрацию Росреестра, POST /api/developer/crm/deals/{dealId}/sale-contract — Загрузить договор ДДУ/ДКП (+2)
- Use Cases (4): UC-BKG-002. Бронирование Резерв → Бронь, UC-BKG-004. Оплата платного бронирования (чек + подтверждение), UC-CRM-004. Документы сделки: запрос → загрузка → проверка → статусы, UC-CRM-005. Оформление сделки: договоры → эскроу → Росреестр → завершение
- События (1): Event Contract: pin.crm.document-requested (DocumentRequestedEvent)
- registries (1): Реестр событий PIN
Связанные документы
- Сущность (дом полей/статусов): deal-document.md; сделка: deal.md; таймлайн: deal-timeline-event.md.
- Соседнее событие того же агрегата:
document-requested.md; карта событий сервиса — crm/README.md. - Consumer-контракт: notifications
(
consumers/document-status-changed.md— будущий путь по конвенции).