Extends the existing PATCH /admin/agents/:agentId with an optional userId to finish wiring 010's Agent.userId link, and adds GET /agents/me/tickets + GET /admin/agents/:agentId/tickets sharing one ticketing/tickets service method, backed by a new Assignment @@index([agentId, isCurrent]). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
49 lines
2.4 KiB
Markdown
49 lines
2.4 KiB
Markdown
# 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).
|