Event Contract: pin.support.ticket-status-changed (TicketStatusChangedKafkaEvent)
Версионируемый контракт интеграционного события «статус обращения изменён». Consumer-документы ссылаются
сюда, не переописывая payload. Единое событие для всех переходов статуса TicketEntity
(ticket.md): взятие в работу, решение, закрытие, реопен. Порядок переходов per-ticket
гарантирован Kafka-ключом (AggregateId = TicketId).
Назначение
Фиксирует переход OldStatus → NewStatus обращения. Публичное в границах платформы: даёт notifications
факт смены статуса без синхронного запроса в support — заявителю уходит уведомление «обращение взято в
работу / решено / закрыто / переоткрыто» (матрица 4.5/4.6). Событие несёт только RequesterUserId
(адресат) и id оператора — ФИО/телефон/email потребитель резолвит из своей реплики identity
(pin.identity.users.v1).
Metadata
| Поле | Значение |
|---|---|
| Event type | pin.support.ticket-status-changed |
| Topic | pin.support.ticket-status-changed.v1 (выделенный топик) |
| Partitions / key | 3 (низкий объём, ADR-0056) / ключ = TicketId (= AggregateId; per-ticket порядок переходов) |
| ContractVersion | 1 |
| Producer | support: take-ticket (New → InWork), assign-ticket (New → InWork при назначении), change-ticket-status (Resolve/Close/Reopen) — ../api/support/change-ticket-status.md, ../api/support/take-ticket.md |
| Статус | draft |
Событие публикуется атомарно с переходом статуса обращения: пока изменение не зафиксировано, событие не отправляется потребителям; после фиксации — доставляется гарантированно (at-least-once). Метаданные события:
MessageId(Guid v7, для дедупликации),AggregateId = TicketId(= Kafka key, per-ticket порядок переходов),AggregateType = "Ticket".
Payload TicketStatusChangedKafkaEvent
Дом полей — entity: TicketEntity (раздел «Доменные и интеграционные события» +
«Инварианты и переходы состояний»); здесь перечислено только то, что реально в событии. Все поля —
скаляры/enum-строки; вложенных моделей нет.
| Поле | Тип | Обязательность | Источник (TicketEntity) | Compatibility | Описание |
|---|---|---|---|---|---|
TicketId | Guid | Да | Id | Immutable | Ключ агрегата (= Kafka key). |
Number | long | Да | Number | Immutable | Номер SUP-NNN для текста уведомления/диплинка. |
TenantId | Guid | Да | TenantId | Immutable | PIN CompanyId (единый tenant платформы). |
RequesterUserId | Guid | Да | RequesterUserId | Immutable | Заявитель — адресат уведомления (id, не PII). |
OldStatus | string (enum TicketStatus) | Да | Status (до перехода) | Additive only (новые значения) | Статус до перехода (New/InWork/Resolved/Closed). |
NewStatus | string (enum TicketStatus) | Да | Status (после) | Additive only | Статус после перехода. |
AssigneeUserId | Guid? | Нет | AssigneeUserId | Additive only | Назначенный оператор после перехода (null — обращение без оператора). |
ChangeReason | string? | Нет | причина перехода | Additive only | Причина/комментарий перехода (напр. при закрытии/реопене), varchar(512); null — не задана. |
ChangedByUserId | Guid? | Нет | actor операции (UpdatedByUserId) | Immutable | Кто инициировал переход (оператор/супервизор); null — системный переход. |
ChangedAt | DateTimeOffset | Да | UpdatedAt (момент перехода) | Immutable | Время смены статуса. |
Enum-значения не дублируются — дом ticket.md → «Enum TicketStatus». Subject,
Category, Priority, QueueId, счётчики (ReopenedCount/MessagesCount) и ФИО/телефон/email заявителя
в payload не входят: потребителю (notifications) для формулировки уведомления достаточно перехода +
Number; display-подпись — из реплики identity.
Когда публикуется
Публикуется при каждом изменении Status тикета, атомарно с мутацией агрегата.
| Условие | Переход состояния | Публикация |
|---|---|---|
take-ticket / assign-ticket | New → InWork (INV-TKT-2/3; AssigneeUserId задан) | атомарно со сменой состояния агрегата |
change-ticket-status (Resolve) | InWork → Resolved (INV-TKT-4; требует AssigneeUserId) | та же |
change-ticket-status (Close) | Resolved → Closed / New → Closed (спам) | та же |
change-ticket-status (Reopen) | Closed → InWork / Resolved → InWork (ReopenedCount++) | та же |
Создание тикета публикуется отдельным событием ticket-created.md (переход [*] → New);
это событие несёт только сам переход статуса, без полного snapshot обращения. Признак нарушения SLA
(SlaBreached) статуса не меняет — его сигнализирует worker
../background/ticket-sla-monitor.md, не это событие.
Потребители (consumers)
| Consumer-контекст | Side effects | Идемпотентность | Задержка (SLA) |
|---|---|---|---|
notifications | Уведомление заявителю о смене статуса (4.5/4.6); каналы получателя — своя реплика identity | дедупликация по MessageId + per-ticket порядок по ключу | < 1 мин |
Порядок переходов одного тикета гарантирован общим ключом TicketId (одна партиция); отображаемое имя
заявителя консюмер берёт из собственной реплики identity, не из этого события.
Доставка и восстановление. At-least-once; дедупликация по MessageId (first-seen). Транзиентный сбой —
ретраи, исчерпание → DLQ с алертом (без блокировки партиции). Порядок переходов гарантирован внутри этого
топика (OldStatus → NewStatus идут по ключу TicketId), но межтопикового порядка с ticket-created/
ticket-message-posted нет; каждое событие несёт полный переход (OldStatus/NewStatus/Number), поэтому
при опережающей доставке консюмер обрабатывает его как самодостаточное, см. README.md («Гарантии
доставки, порядка и восстановления»).
Версионирование и совместимость
ContractVersion = 1; приVersion != ContractVersionconsumer логирует warning и продолжает обработку. Топик кодирует версию суффиксом.v1по конвенции.- Additive-only для minor: новые optional-поля с безопасным default — без bump. Новые значения enum
TicketStatus— additive. - Breaking —
ContractVersion = 2+ новый топикpin.support.ticket-status-changed.v2+ dual-publish + deadline миграции consumers + sunset.v1. - Unknown enum (
OldStatus/NewStatus) — fallback: сохранить строку как есть, warning, не падать (уведомление с нейтральной формулировкой).
Безопасность
Событие несёт только идентификаторы, enum-статусы, опциональную причину и метку времени — прямого PII нет
по построению: ФИО/телефон/email заявителя исключены (консюмер резолвит из своей реплики identity).
| Поле | Класс данных | Правило |
|---|---|---|
TicketId, Number, TenantId, RequesterUserId, AssigneeUserId, ChangedByUserId | Internal | include (id/номер, не PII) |
OldStatus, NewStatus | Internal | include |
ChangeReason | Internal | include; свободный текст — без PII/секретов по контракту (валидируется producer-ом) |
ChangedAt | Internal | include |
Событие НЕ содержит secrets, tokens, ФИО/телефон/email, тела переписки, вложений. Доступ к топику —
m2m ACL Kafka (notifications).
Обратные ссылки
Автоссылки: где используется этот документ.
- Домен (CQRS) (1): Entity / Aggregate: TicketEntity
- API (8): GET /api/public/support/tickets/{id} — Моё обращение и переписка, GET /api/support/tickets/{id} — Карточка обращения (GetTicket), POST /api/public/support/tickets/{id}/messages — Ответ заявителя, POST /api/support/tickets/{id}/assign — Назначить/переназначить оператора, POST /api/support/tickets/{id}/escalate — Эскалация администратору, POST /api/support/tickets/{id}/messages — Ответ оператора / внутренняя заметка, POST /api/support/tickets/{id}/status — Сменить статус (решено/закрыто/реопен), POST /api/support/tickets/{id}/take — Взять тикет в работу
- Use Cases (3): UC-SUP-001. Приём обращения и назначение оператора, UC-SUP-002. Решение обращения, UC-SUP-005. Эскалация администратору
- События (4): События сервиса support: карта топиков и решения об интеграциях, Event Contract: pin.support.arbitrage-resolved (ArbitrageResolvedKafkaEvent), Event Contract: pin.support.ticket-created (TicketCreatedKafkaEvent), Event Contract: pin.support.ticket-message-posted (TicketMessagePostedKafkaEvent)
- Консюмеры (10): Consumer: контроль смены телефона связанного инвестора (pin-investor-owner-identity-users), Consumer: Calendar Identity User Sync (calendar-identity-users), Consumer: CRM Identity User Sync (CrmUserUpsertedConsumer), Consumer: Identity Events → Notifications (notifications-identity-events), Consumer: identity-users-sync (ProfileUserUpsertedConsumer, ProfileUserBlockedConsumer), Consumer: identity.users.v1 → AuthorReplica (news-identity-users), Consumer: Messenger User Replica Sync (MessengerUserUpsertedConsumer), Consumer: ReferralUserUpsertedConsumer (pin.identity.users.v1), Consumer: Support Identity User Sync, Consumer: Wallet Identity Display Sync (wallet-identity-users)
- Фоновые задачи (1): Background Job: TicketSlaMonitorJob (worker ticket-sla-monitor)
- Дашборды (1): Панель Наблюдаемости: support-overview (эксплуатация)
- registries (1): Реестр событий PIN
- index.md (1): Support Service
Связанные документы
- Источник (операции): ../api/support/change-ticket-status.md,
../api/support/take-ticket.md,
../api/support/assign-ticket.md; сущность —
ticket.md (
TicketStatus, INV-TKT-2..7). - Соседние контракты топиков
support: ticket-created.md, arbitrage-resolved.md; карта — README.md.