| 03-operational-modules |
03 |
api |
| prisma |
| postgresql |
| tickets |
| support |
| lifecycle |
| tenant-scoping |
| vitest |
|
| phase |
provides |
| 03-01 |
Zone and ZoneAssignment models already in schema (no conflicts) |
|
| phase |
provides |
| 02-03 |
Subscriber model (tickets reference subscribers) |
|
| phase |
provides |
| 01-03 |
withTenantContext() extension pattern for tenant scoping |
|
| phase |
provides |
| 01-04 |
withPermission() HOF for API route auth |
|
|
| Ticket model with status lifecycle (OPEN->ASSIGNED->RESOLVED->CLOSED) |
| TicketCategory model with isActive soft-deactivation |
| ticket-service.ts |
| CRUD, sequential numbering (TKT-NNNN), guard-map transitions, idempotent resolve |
|
| ticket-category-service.ts |
| CRUD with deactivated category validation |
|
| 5 ticket API routes and 2 category API routes |
| Default 6 ISP categories seeded at tenant creation |
|
| 03-04 (job orders link to tickets, trigger OPEN->ASSIGNED and auto-resolve) |
| 05 (subscriber portal uses TicketSource.SUBSCRIBER) |
|
| added |
patterns |
|
|
| Guard map for status transitions: VALID_TICKET_TRANSITIONS maps each status to allowed next states |
| Idempotent resolveTicket: checks current status before transition, no-ops if already RESOLVED |
| Sequential TKT-NNNN numbering: same pattern as INV-YYYY-NNNN and JE-YYYY-NNNN |
| Deactivated category validation: ticket creation checks isActive=true before proceeding |
| Default category seeding: 6 ISP categories seeded inside createTenant $transaction |
|
|
| created |
modified |
| src/lib/services/ticket-service.ts |
| src/lib/services/ticket-category-service.ts |
| src/app/api/tickets/route.ts |
| src/app/api/tickets/[id]/route.ts |
| src/app/api/tickets/[id]/status/route.ts |
| src/app/api/ticket-categories/route.ts |
| src/app/api/ticket-categories/[id]/route.ts |
| src/lib/__tests__/ticket-service.test.ts |
| prisma/migrations/20260304233528_add_tickets/migration.sql |
|
| prisma/schema.prisma |
| src/lib/prisma-tenant.ts |
| src/lib/tenant.ts |
|
|
| VALID_TICKET_TRANSITIONS guard map: OPEN->[ASSIGNED,CLOSED], ASSIGNED->[OPEN,RESOLVED], RESOLVED->[CLOSED,OPEN], CLOSED->[] (terminal) |
| resolveTicket is idempotent: checks if already RESOLVED and returns silently — prevents race conditions when multiple job completions trigger auto-resolve |
| TicketSource.SUBSCRIBER added for Phase 5 subscriber portal compatibility — forward-compatible enum |
| Deactivated categories rejected at createTicket (not just at category level) — enforced at service layer |
| Default 6 categories seeded in createTenant $transaction: No Connection, Slow Speed, Billing Inquiry, New Installation, Equipment Issue, Other |
| transitionTicketStatus is the single gateway for status changes — updateTicket explicitly excludes status field |
| Ticket cleanup order in tests: tickets -> ticketCategories -> subscribers -> ... (categories seeded by createTenant must be deleted on teardown) |
|
| Guard map pattern: export const VALID_TRANSITIONS: Record<Status, Status[]> for validating state machine transitions |
| Idempotent convenience wrappers: resolve/close/etc. that check current state and no-op if already in target state |
|
6min |
2026-03-05 |