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

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 typepin.support.ticket-status-changed
Topicpin.support.ticket-status-changed.v1 (выделенный топик)
Partitions / key3 (низкий объём, ADR-0056) / ключ = TicketId (= AggregateId; per-ticket порядок переходов)
ContractVersion1
Producersupport: 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Описание
TicketIdGuidДаIdImmutableКлюч агрегата (= Kafka key).
NumberlongДаNumberImmutableНомер SUP-NNN для текста уведомления/диплинка.
TenantIdGuidДаTenantIdImmutablePIN CompanyId (единый tenant платформы).
RequesterUserIdGuidДаRequesterUserIdImmutableЗаявитель — адресат уведомления (id, не PII).
OldStatusstring (enum TicketStatus)ДаStatus (до перехода)Additive only (новые значения)Статус до перехода (New/InWork/Resolved/Closed).
NewStatusstring (enum TicketStatus)ДаStatus (после)Additive onlyСтатус после перехода.
AssigneeUserIdGuid?НетAssigneeUserIdAdditive onlyНазначенный оператор после перехода (null — обращение без оператора).
ChangeReasonstring?Нетпричина переходаAdditive onlyПричина/комментарий перехода (напр. при закрытии/реопене), varchar(512); null — не задана.
ChangedByUserIdGuid?Нетactor операции (UpdatedByUserId)ImmutableКто инициировал переход (оператор/супервизор); null — системный переход.
ChangedAtDateTimeOffsetДаUpdatedAt (момент перехода)ImmutableВремя смены статуса.

Enum-значения не дублируются — дом ticket.md → «Enum TicketStatus». Subject, Category, Priority, QueueId, счётчики (ReopenedCount/MessagesCount) и ФИО/телефон/email заявителя в payload не входят: потребителю (notifications) для формулировки уведомления достаточно перехода + Number; display-подпись — из реплики identity.

Когда публикуется

Публикуется при каждом изменении Status тикета, атомарно с мутацией агрегата.

УсловиеПереход состоянияПубликация
take-ticket / assign-ticketNew → 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 != ContractVersion consumer логирует warning и продолжает обработку. Топик кодирует версию суффиксом .v1 по конвенции.
  • Additive-only для minor: новые optional-поля с безопасным default — без bump. Новые значения enum TicketStatus — additive.
  • BreakingContractVersion = 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, ChangedByUserIdInternalinclude (id/номер, не PII)
OldStatus, NewStatusInternalinclude
ChangeReasonInternalinclude; свободный текст — без PII/секретов по контракту (валидируется producer-ом)
ChangedAtInternalinclude

Событие НЕ содержит secrets, tokens, ФИО/телефон/email, тела переписки, вложений. Доступ к топику — m2m ACL Kafka (notifications).

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

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

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