# Data Model: Agent Ticket Queue ## Modified: `Agent` No new column — `userId`/`user` already exist (010-identity-auth). This feature is the first to actually write `userId` through an endpoint, and adds the supporting index below. ```prisma model Assignment { // ...existing fields unchanged... @@index([ticketId, isCurrent]) @@index([agentId, isCurrent]) // NEW — supports "current assignments for agent X" @@map("assignments") } ``` ## New (response-shape only, no new table): `AssignedTicketSummary` A read projection, not a persisted entity — assembled per-request from `Ticket` joined to its current `Assignment`, `Product`, `CustomerReference`, and (if present) `SLARun`. | Field | Source | Notes | |---|---|---| | `id` | `Ticket.id` | | | `code` | `Ticket.code` | e.g. `ACME-2026-0042` | | `status` | `Ticket.status` | One of the 12 lifecycle states (003's own state machine) | | `priority` | `Ticket.priority` | Opaque string, as already modeled | | `severity` | `Ticket.severity` | Opaque string, as already modeled | | `product` | `Ticket.product` | `{ id, externalProductId, name }` | | `customer` | `Ticket.customer` | `{ externalUserId, externalTenantId }` — no PII beyond what 002's own `CustomerReference` already stores | | `assignedAt` | `Assignment.assignedAt` | The current assignment's start time | | `sla` | `SLARun` (nullable) | `{ status, firstResponseDueAt, resolutionDueAt, breachedAt }` or `null` if no `SLARun` exists yet for this ticket | ## Validation / Business Rules - **Linking** (`PATCH /admin/agents/:agentId`'s new `userId` field): - The target `User` must exist and have role `AGENT` (FR-001). - No other `Agent` row may already have that `userId` (FR-001) — checked proactively before the write (research.md), not left to the database's own `@unique` constraint to reject. - `userId: null` explicitly unlinks (distinct from omitting the field, which leaves it unchanged — the existing `updateAgentSchema` pattern for optional fields). - **Listing** (`GET /agents/me/tickets`, `GET /admin/agents/:agentId/tickets`): - Only `Assignment.isCurrent: true` rows are considered (FR-003). - The agent-self route resolves `agentId` exclusively from `request.user.id` → `Agent.userId` lookup — never from any request input (FR-004). - A session with no linked `Agent` row throws a specific `NotFoundError` ("No agent profile is linked to this account."), never an empty array (FR-006).