Event Contract: pin.crm.document-requested (DocumentRequestedEvent)
Версионируемый контракт интеграционного события «запрошен документ сделки». Consumer-документы ссылаются
сюда, не переописывая payload. Дом полей — entity: DealDocumentEntity;
контекст сделки — entity: DealEntity.
Назначение
Фиксирует запрос документа у участника сделки: менеджер застройщика или система создаёт
DealDocumentEntity в статусе Requested (файла ещё нет). Producer — crm, операция «запрос документа»
(карточка целевого действия «загрузите документы» в чате сделки, US-INV-16; запрос документов для
формирования договора ДДУ/ДКП). Публичное: messenger рисует карточку целевого действия адресату без
синхронного запроса в crm; notifications шлёт уведомление «требуется загрузка». Прямая загрузка документа
без предварительного запроса (переход [*] → Uploaded) это событие не публикует — её фиксирует
document-status-changed.
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.crm.document-requested |
| Topic | pin.crm.document-requested.v1 |
| Partitions / key | 3 (низкообъёмная сущность, ADR-0056) / key = AggregateId = DocumentId |
| ContractVersion | 1 |
| Producer | crm: запрос документа (менеджер застройщика / система) — вкладка «Документы» карточки сделки |
| Статус | draft |
Событие публикуется атомарно с созданием DealDocumentEntity: пока запись не зафиксирована, событие не
отправляется; после фиксации — доставляется гарантированно (at-least-once), ключ = DocumentId.
Payload DocumentRequestedEvent
Дом полей — entity: DealDocumentEntity (раздел «Поля» и «Доменные и
интеграционные события»); здесь перечислено только то, что реально в событии.
| Поле | Тип | Обязательность | Источник (поле агрегата) | Compatibility | Описание |
|---|---|---|---|---|---|
DocumentId | Guid | Да | DealDocumentEntity.Id | Immutable | Ключ агрегата (= AggregateId = Kafka key). |
DealId | Guid | Да | DealDocumentEntity.DealId | Immutable | Сделка-контекст (deal.md). |
TenantId | Guid | Да | DealDocumentEntity.TenantId | Immutable | DeveloperCompanyId застройщика сделки (ADR-0052). |
DocumentType | string (enum DealDocumentType) | Да | DealDocumentEntity.Type | Additive only (новые значения) | Тип документа: ClientPassport / SignedBookingContract / BookingPaymentProof / ContractDraftDocument / SignedSaleContract / PaymentProof / Other и др. (полный перечень — deal-document.md). |
Title | string | Да | DealDocumentEntity.Title | Additive only | Отображаемое название запрошенного документа («Скан паспорта, стр. 1»). |
RequestComment | string? | Нет | DealDocumentEntity.RequestComment | Additive only | Текст карточки «загрузите документы» — что именно нужно (US-INV-16), ≤ 1000. |
RequestedByUserId | Guid? | Нет | DealDocumentEntity.RequestedByUserId | Additive only | Кто запросил (менеджер застройщика); null — системный/автоматический запрос. |
TargetParticipantUserId | Guid? | Нет | активный DealMemberEntity.UserId (Role = Investor) | Additive only | Кому адресован запрос — активный инвестор сделки (INV-DEAL-3); null — общий запрос без явного адресата (карточка видна всем участникам). |
RequestedAt | DateTimeOffset | Да | DealDocumentEntity.CreatedAt | Immutable | Момент запроса (создания записи в статусе Requested). |
DueAt | DateTimeOffset? | Нет | DealDocumentEntity.DueAt | Additive only | Срок предоставления (индикация «просрочено» в карточке/таймлайне). |
FileId, FileName, ContentType, SizeBytes, RejectReason, ReviewedByUserId в payload не входят:
на момент запроса файла нет (статус Requested), поля появляются позже и фиксируются событием
document-status-changed. RequisiteSetId не публикуется (внутренняя
ссылка на profile).
Когда публикуется
| Условие | Переход состояния | Публикация |
|---|---|---|
Менеджер застройщика или система создаёт DealDocumentEntity со статусом Requested у сделки в нетерминальном статусе (US-INV-16; запрос для ДДУ/ДКП) | [*] → Requested (stateDiagram deal-document.md) | атомарно со сменой состояния агрегата |
Гварды инвариантов запроса — INV-DOC-5 (сделка не терминальна) и record-level scope участника; см.
deal-document.md. Прямая загрузка ([*] → Uploaded) и смена статуса
(Uploaded/Accepted/Rejected) публикуют другое событие — document-status-changed.
Потребители (consumers)
Сверено с README («Events И Consumers») и deal-document.md.
| Consumer-контекст | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
messenger | Карточка целевого действия «загрузите документы» в чат сделки (DealId → чат сделки, US-INV-16); адресат — TargetParticipantUserId | MessageId (де-дуп карточки) | < 1 мин |
notifications | Пуш/СМС адресату «требуется загрузка документа» (по TargetParticipantUserId) | MessageId | < 5 мин |
Оба консюмера идемпотентны по MessageId (повторная доставка не создаёт вторую карточку/уведомление).
Доставка, порядок и обработка сбоев
- Гарантия публикации. Событие уходит только после фиксации создания
DealDocumentEntityв статусеRequested; при откате не публикуется. Доставка — at-least-once, ключDocumentId. - Порядок. События одного документа упорядочены ключом
DocumentId;document-requestedпредшествует последующимdocument-status-changedтого жеDocumentId. - Идемпотентность потребителя.
messenger/notificationsдедуплицируют поMessageId(повторная доставка не создаёт вторую карточку/уведомление). - Retry. Транзиентный сбой потребителя → повтор обработки с backoff; событие доступно до успешной обработки.
- DLQ. Неустранимый сбой после исчерпания попыток → dead-letter c алертом; проигрывание безопасно
(идемпотентно по
MessageId). При неизвестномDocumentTypeкарточка нейтральная (fail-safe), в DLQ не уходит.
Версионирование и совместимость
ContractVersion = 1; при несовпадении версии контракта потребитель логирует предупреждение и продолжает обработку. Топик кодирует версию суффиксом.v1по конвенции.- Additive-only для minor: новые поля — optional с безопасным default, без bump (
DueAt,RequestComment,TargetParticipantUserIdуже nullable). - Breaking (удаление/переименование/смена типа/смысла) —
ContractVersion = 2+ топикpin.crm.document-requested.v2+ dual-publish на период миграции + deadline миграции consumers + sunset.v1. - Unknown enum
DocumentType— consumer сохраняет строку как есть и рендерит нейтральную карточку «запрошен документ» + warning (fail-safe), не роняет обработку.
Безопасность
| Поле | Класс данных | Правило |
|---|---|---|
DocumentId, DealId, TenantId, RequestedAt, DueAt | Internal | include |
DocumentType | Internal | include |
Title, RequestComment | Internal | include — свободный текст менеджера; producer гарантирует отсутствие реквизитов/секретов/полных PII |
RequestedByUserId, TargetParticipantUserId | Internal | include (Guid-идентификаторы участников) |
Событие не содержит ФИО, телефонов, номеров эскроу/счетов, FileId/бинарных вложений, secrets и
токенов — только Guid-идентификаторы участников. ФИО/телефон адресата consumer резолвит из собственной
реплики identity (user-upserted); при рендере карточки/логировании
ФИО/телефон маскируются на стороне consumer (+7***1234, инициалы), в логах producer/consumer — только
DocumentId/DealId.
Наблюдаемость
- Логи (без PII): факт публикации
document-requestedсDocumentId/DealId/DocumentType/RequestedAt(идентификаторы, не PII);Title/RequestComment(свободный текст) в логах не печатать целиком; ФИО/телефон адресата в логах не фигурируют (в payload их нет). - Метрики: число запросов документов по топику в разрезе
DocumentType; сквозная задержка доmessenger(< 1 мин — карточка «загрузите документы») иnotifications(< 5 мин); глубина DLQ (порог алерта > 0); доля повторных доставок. - Доступ: топик — m2m ACL Kafka (
messenger/notifications);FileId/бинарь иRequisiteSetIdв событие не входят; ФИО/телефон адресата consumer резолвит из своей реплики и маскирует в логах.
Acceptance Criteria
- Given сделка в нетерминальном статусе (guard INV-DOC-5), When менеджер застройщика или система
запрашивает документ, Then создаётся
DealDocumentEntityв статусеRequested, ровно одноdocument-requestedпубликуется сDocumentType,Titleи (если задан)TargetParticipantUserId. - Given прямая загрузка документа без предварительного запроса (
[*] → Uploaded), When файл загружен, Thendocument-requestedне публикуется — фиксируетdocument-status-changed. - Given повторная доставка события, When
messengerобрабатывает, Then карточка целевого действия создаётся один раз (дедуп поMessageId). - Given запрос без явного адресата, When событие обрабатывается, Then
TargetParticipantUserId = nullи карточка видна всем участникам сделки. - Given неизвестное значение
DocumentType, When потребитель обрабатывает, Then нейтральная карточка «запрошен документ» + warning (без падения). - Given breaking-изменение payload, When новый контракт, Then
pin.crm.document-requested.v2+ dual-publish + sunset.v1.
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: NotificationEntity
- API (5): GET /api/investor/crm/deals/{dealId}/timeline — Таймлайн сделки (инвестор), POST /api/developer/crm/deals/{dealId}/documents/{documentId}/confirm — Подтвердить документы ДДУ/ДКП, POST /api/developer/crm/deals/{dealId}/documents/request — Запросить документы ДДУ/ДКП, POST /api/investor/crm/deals/{dealId}/documents — Загрузить документ в сделку, POST /api/pin-manager/crm/deals/{dealId}/timeline-note — Ручная запись в таймлайн
- Use Cases (1): UC-CRM-004. Документы сделки: запрос → загрузка → проверка → статусы
- События (1): Event Contract: pin.crm.document-status-changed (DocumentStatusChangedEvent)
- Консюмеры (1): Consumer: Document Request Card (MessengerDocumentRequestedConsumer)
- scenarios (1): UC-OVR-006. Сделка: от брони до регистрации (инвестор-инициатор)
- registries (1): Реестр событий PIN
Связанные документы
- Дом сущности: deal-document.md; контекст сделки: deal.md; соседнее событие статуса: document-status-changed.md.
- Реплика адресата (ФИО/телефон): identity/events/user-upserted.md.
- Обзор событий контекста: README («Events И Consumers»).