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>
5.5 KiB
description
| description |
|---|
| Task list for 011-agent-ticket-queue |
Tasks: Agent Ticket Queue
Input: Design documents from specs/011-agent-ticket-queue/
Prerequisites: plan.md, spec.md, research.md, data-model.md, contracts/agent-ticket-queue-contract.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)
- T001 Add
Assignment @@index([agentId, isCurrent])toprisma/schema.prisma; generate the migration (prisma migrate diff→ hand-writemigration.sql→prisma migrate deploy, this session's established non-interactive workaround) and runnpm 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
- 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
- T003 [US1] Add
AgentsRepository.findByUserId(userId)insrc/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) - T004 [US1] Add
userId: z.string().min(1).nullable().optional()toupdateAgentSchemainsrc/modules/identity/agents/schema/agents.schema.ts - T005 [US1] In
AgentsService.update(src/modules/identity/agents/service/agents.service.ts), whendata.userId !== undefined: if non-null, look up the targetUser(via a smallUsersRepository.findById) — 404 if missing, reject with aValidationErrorif its role isn'tAGENT; look up anyAgentalready linked to thatuserId(T003'sfindByUserId) —ConflictErrorif it's a different agent thanagentId(depends on T003, T004) - 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
- T007 [P] [US2] Unit test: given a
Userid with no linkedAgent, the service throws the specificNotFoundError— intests/unit/identity/agent-ticket-queue-guard.test.ts - 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 explicitagentId; non-admin calling the admin route for another agent gets 403) intests/integration/agent-ticket-queue.test.ts(depends on T006)
Implementation for User Story 2
- T009 [US2] Add
TicketsRepository.findAssignedToAgent(agentId)insrc/modules/ticketing/tickets/repository/tickets.repository.ts— one query joining currentAssignment(via orchestration's public repository/service surface) toTicketwithproduct/customer/sLARunrelations (depends on T001) - T010 [US2] Add
TicketsService.listAssignedTo(agentId)mapping each row to theAssignedTicketSummaryshape (data-model.md) insrc/modules/ticketing/tickets/service/tickets.service.ts(depends on T009) - T011 [US2] Add
GET /agents/me/tickets(fastify.authenticateonly; resolvesagentIdviaagentsService'sfindByUserId(T003) againstrequest.user.id, throwing the FR-006NotFoundErrorif none) andGET /admin/agents/:agentId/tickets(fastify.authenticate+requireRole('ADMIN')) insrc/modules/ticketing/tickets/ controller/+routes/, registered fromsrc/api/routes.ts(depends on T003, T010) - 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
- T013 [P] Update
specs/011-agent-ticket-queue/checklists/requirements.mdNotes with any implementation-time findings - T014 Run
npx tsx scripts/check-architecture.tsandnpm run lint/npm run typecheck - T015 Full regression:
npm run test:unitthen 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