Files
baya-monorepo/docs/integration/domains/tickets.md
T
2026-07-30 12:49:46 +03:30

5.4 KiB

tickets — coordination, support, emergency

Client seam client/src/services/tickets/ · USE_TICKETS_MOCK = false (real) · 11 server ops Last verified: 2026-07-30 against commit d3ec723 and swagger.v1.json (2026-07-29).

Threaded messaging between customer, nurse and staff. Also the emergency channel. The in-app notification feed is a separate domain — notifications.md.

Endpoints

Method Path Verdict
GET /api/v1/tickets wired · paginated · bookingId filter
POST /api/v1/tickets wired
GET /api/v1/tickets/{id} wired — stamps last_read_at
POST /api/v1/tickets/{id}/messages wired · optional clientMessageId
POST /api/v1/tickets/{id}/close wired
POST /api/v1/tickets/{id}/reopen wired
POST /api/v1/tickets/emergency unwired — no emergency entry point in the UI yet
POST /api/v1/tickets/{id}/participants unwired
DELETE /api/v1/tickets/{id}/participants/{userId} unwired
GET /api/v1/admin/tickets wired · paginated — the staff queue
GET /api/v1/admin/tickets/{id} wired — the staff thread, internal notes visible

All [Authorize]; the two admin/tickets routes are DynamicPermission. Note the hardcoded nested admin/ route segment — see index.md.

Phantom — 1

Client call REQ Live?
POST /api/v1/tickets/{id}/assign REQ-063 Yes — this domain's mock is off. Gated behind TICKET_LIFECYCLE_ENABLED, default off, so nothing calls it today

tickets/constants.ts is stale on this. It says the gate is off because "the backend has no close/reopen/assign routes yet (REQ-063)". close and reopen exist and are wired. Only assign is missing. The gate could be turned on for close/reopen alone.

isInternal is a query-layer boundary

The hardest rule in this domain, and the easiest to get wrong in a UI:

  • TicketMessageDto carries isInternal, so the field is on the wire shape.
  • The filtering is not. A non-staff caller's thread query never returns an internal row, and a non-staff caller can never set one. Both are enforced at the query layer, in the handler — never in the UI.
  • Consequence for the client: it must not model internal notes as "rows to hide". They do not arrive. A client-side filter would be a second, weaker gate that hides a leak rather than preventing one.
  • Consequence for the server: any new ticket read must repeat the filter. There is no global interceptor doing it.

Shape rules the JSON does not express

  • Unread is computed server-side against last_read_at, which is stamped when the participant fetches the user-facing thread (GET /tickets/{id}) — not by a separate mark-read call. So opening a thread is what clears its badge. unreadCount counts non-internal messages from others after that stamp, and is 0 on the admin queue by definition.
  • clientMessageId is optimistic-send idempotency (REQ-028): a retried send with the same key returns the original message and echoes the key back on PostMessageResult. This is what makes the client's retry-in-place send safe. It is not the Idempotency-Key header — it is a body field, and it is the only place in the API that works this way.
  • The message author is a role label, not a name — confirmed intentional, for privacy. TicketMessageDto carries senderId only; the client derives the author label from the participant role. No raw identity is exposed, and none should be added.
  • referenceCode is the human-facing id shown to users and quoted in support. Treat it as opaque.
  • bookingId and refundId on the summary link a ticket to what it is about; the bookingId query filter is how the client jumps from a booking to its coordination thread.
  • Ticket bodies are encrypted at rest (refinement-phase-9).

Enums

Vocabulary Values Note
TicketStatus open closed matches Entities/Messaging/TicketCodes.cs
TicketCategory coordination support refund emergency matches
TicketAuthorRole customer nurse admin system the one cross-side mismatch in the API
MessageSendStatus (client-only) sent sending failed optimistic-send UI state, never on the wire

system is client-only. TicketCodes defines customer, nurse, admin. The client's TicketAuthorRole adds system for platform-generated messages. Widening a union on the reading side is safe — the client can render a value the server never sends — but it means a reader of the client types would wrongly conclude the server emits system. Either the server should define it or the client should drop it; today it is a documented asymmetry.

Open REQs

REQ Status Effect
REQ-028 delivered unreadCount + lastMessageAt on the summary, bookingId filter, clientMessageId dedupe, role-label authors — all present
REQ-059 open No last-message preview and no author role on the summary, and no unread-total read. The real inbox shows subject + status + time with no preview
REQ-060 deferred No message photo attachments. The affordance is designed and gated off
REQ-063 open, narrower than filed close and reopen are delivered and wired. Only assign is missing → 1 phantom