Extends PATCH /admin/agents/:agentId with an optional userId to finally wire Agent.userId (added in 010-identity-auth as schema-only, never consumed by any workflow), with proactive role/duplicate-link checks mirroring UsersService.create's own pre-check style. Adds GET /agents/me/tickets and GET /admin/agents/:agentId/tickets, sharing one TicketsService.listAssignedTo method, returning a dashboard- ready summary (product, customer, priority, severity, status, SLA state) of every ticket currently assigned to an agent — no such query existed anywhere in the ticketing or orchestration modules before this. Backed by a new Assignment @@index([agentId, isCurrent]). Discovered while starting supporthub-web's 001-agent-admin-ui: its agent- dashboard user story had no backend data source without this. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
120 lines
5.5 KiB
Markdown
120 lines
5.5 KiB
Markdown
---
|
|
description: "Task list for 011-agent-ticket-queue"
|
|
---
|
|
|
|
# Tasks: Agent Ticket Queue
|
|
|
|
**Input**: Design documents from `specs/011-agent-ticket-queue/`
|
|
|
|
**Prerequisites**: [plan.md](./plan.md), [spec.md](./spec.md), [research.md](./research.md),
|
|
[data-model.md](./data-model.md),
|
|
[contracts/agent-ticket-queue-contract.md](./contracts/agent-ticket-queue-contract.md),
|
|
[quickstart.md](./quickstart.md)
|
|
|
|
**Organization**: Tasks are grouped by user story (US1 = P1 linking, US2 = P1 ticket listing).
|
|
US2 depends on a helper US1 also needs, so despite being nominally independent, build US1 first.
|
|
|
|
## Format: `[ID] [P?] [Story] Description`
|
|
|
|
All file paths are relative to `supporthub-api/` (repo root).
|
|
|
|
---
|
|
|
|
## Phase 1: Foundational (Blocking Prerequisites)
|
|
|
|
- [x] T001 Add `Assignment @@index([agentId, isCurrent])` to `prisma/schema.prisma`; generate
|
|
the migration (`prisma migrate diff` → hand-write `migration.sql` → `prisma migrate
|
|
deploy`, this session's established non-interactive workaround) and run
|
|
`npm run prisma:generate`
|
|
|
|
**Checkpoint**: Index in place. Both user stories can now be built.
|
|
|
|
---
|
|
|
|
## Phase 2: User Story 1 - An admin links a staff account to its agent roster entry (Priority: P1)
|
|
|
|
**Goal**: `Agent.userId` becomes settable through the existing update endpoint, with the
|
|
rejection rules FR-001 requires.
|
|
|
|
**Independent Test**: Quickstart Scenario 1.
|
|
|
|
### Tests for User Story 1
|
|
|
|
- [x] T002 [P] [US1] Integration test covering Quickstart Scenario 1 (link succeeds; non-AGENT
|
|
role rejected 400; already-linked-elsewhere rejected 409) in
|
|
`tests/integration/agent-ticket-queue.test.ts` (depends on T001)
|
|
|
|
### Implementation for User Story 1
|
|
|
|
- [x] T003 [US1] Add `AgentsRepository.findByUserId(userId)` in
|
|
`src/modules/identity/agents/repository/agents.repository.ts` — shared by this story's
|
|
own duplicate-link check and by User Story 2's agent-self route (T010)
|
|
- [x] T004 [US1] Add `userId: z.string().min(1).nullable().optional()` to `updateAgentSchema` in
|
|
`src/modules/identity/agents/schema/agents.schema.ts`
|
|
- [x] T005 [US1] In `AgentsService.update` (`src/modules/identity/agents/service/agents.service.ts`),
|
|
when `data.userId !== undefined`: if non-null, look up the target `User` (via a small
|
|
`UsersRepository.findById`) — 404 if missing, reject with a `ValidationError` if its role
|
|
isn't `AGENT`; look up any `Agent` already linked to that `userId` (T003's
|
|
`findByUserId`) — `ConflictError` if it's a different agent than `agentId` (depends on
|
|
T003, T004)
|
|
- [x] T006 [US1] Run Quickstart Scenario 1 locally and confirm all 3 steps pass
|
|
|
|
**Checkpoint**: An agent's login can now be resolved to its roster row.
|
|
|
|
---
|
|
|
|
## Phase 3: User Story 2 - An agent retrieves their own currently-assigned tickets (Priority: P1)
|
|
|
|
**Goal**: Both list endpoints return the same summarized projection, correctly scoped per
|
|
caller.
|
|
|
|
**Independent Test**: Quickstart Scenarios 2-3.
|
|
|
|
### Tests for User Story 2
|
|
|
|
- [x] T007 [P] [US2] Unit test: given a `User` id with no linked `Agent`, the service throws the
|
|
specific `NotFoundError` — in `tests/unit/identity/agent-ticket-queue-guard.test.ts`
|
|
- [x] T008 [US2] Integration test covering Quickstart Scenarios 2-3 (agent sees exactly their
|
|
own current assignments; list updates after a reassignment; no-linked-agent session gets
|
|
404 not `[]`; admin route returns the same shape for an explicit `agentId`; non-admin
|
|
calling the admin route for another agent gets 403) in
|
|
`tests/integration/agent-ticket-queue.test.ts` (depends on T006)
|
|
|
|
### Implementation for User Story 2
|
|
|
|
- [x] T009 [US2] Add `TicketsRepository.findAssignedToAgent(agentId)` in
|
|
`src/modules/ticketing/tickets/repository/tickets.repository.ts` — one query joining
|
|
current `Assignment` (via orchestration's public repository/service surface) to `Ticket`
|
|
with `product`/`customer`/`sLARun` relations (depends on T001)
|
|
- [x] T010 [US2] Add `TicketsService.listAssignedTo(agentId)` mapping each row to the
|
|
`AssignedTicketSummary` shape (data-model.md) in
|
|
`src/modules/ticketing/tickets/service/tickets.service.ts` (depends on T009)
|
|
- [x] T011 [US2] Add `GET /agents/me/tickets` (`fastify.authenticate` only; resolves `agentId`
|
|
via `agentsService`'s `findByUserId` (T003) against `request.user.id`, throwing the
|
|
FR-006 `NotFoundError` if none) and `GET /admin/agents/:agentId/tickets`
|
|
(`fastify.authenticate` + `requireRole('ADMIN')`) in `src/modules/ticketing/tickets/
|
|
controller/` + `routes/`, registered from `src/api/routes.ts` (depends on T003, T010)
|
|
- [x] T012 [US2] Run Quickstart Scenarios 2-3 locally and confirm all steps pass
|
|
|
|
**Checkpoint**: supporthub-web's agent dashboard now has a real data source.
|
|
|
|
---
|
|
|
|
## Phase 4: Polish & Cross-Cutting Concerns
|
|
|
|
- [x] T013 [P] Update `specs/011-agent-ticket-queue/checklists/requirements.md` Notes with any
|
|
implementation-time findings
|
|
- [x] T014 Run `npx tsx scripts/check-architecture.ts` and `npm run lint`/`npm run typecheck`
|
|
- [x] T015 Full regression: `npm run test:unit` then the full integration suite against real
|
|
Docker-provisioned Postgres/Redis, confirming nothing outside this feature regressed
|
|
|
|
---
|
|
|
|
## Dependencies & Execution Order
|
|
|
|
- **Foundational (Phase 1)**: No dependencies — BLOCKS both user stories
|
|
- **User Story 1 (Phase 2)**: Depends on Foundational
|
|
- **User Story 2 (Phase 3)**: Depends on Foundational and on T003 (built in Phase 2) — build
|
|
Phase 2 before Phase 3 despite the two stories being otherwise independent
|
|
- **Polish (Phase 4)**: Depends on both user stories
|