Files
NetForge/.planning/phases/03-operational-modules/03-03-PLAN.md
kevin-asprec ef0150654b docs(03): create phase plan
Phase 03: Operational Modules
- 5 plans in 3 waves
- Wave 1: 03-01 (zones), 03-03 (tickets) — parallel
- Wave 2: 03-02 (collector collections), 03-04 (job orders) — parallel
- Wave 3: 03-05 (technician compensation) — sequential
- Ready for execution

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 06:05:55 +08:00

11 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous, must_haves
phase plan type wave depends_on files_modified autonomous must_haves
03-operational-modules 03 execute 1
prisma/schema.prisma
src/lib/prisma-tenant.ts
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
true
truths artifacts key_links
Staff can create a support ticket from a client call with issue description, priority, and category
Tickets follow a lifecycle: OPEN -> ASSIGNED -> RESOLVED -> CLOSED
Admin can configure ticket categories per tenant (create, edit, deactivate)
Default ticket categories are seeded at tenant creation
Ticket model supports both staff and subscriber as source types from the start
path provides contains
prisma/schema.prisma Ticket and TicketCategory models model Ticket
path provides exports
src/lib/services/ticket-service.ts Ticket CRUD, status transitions, search/filter
TicketService
path provides exports
src/lib/services/ticket-category-service.ts TicketCategory CRUD with default seeding
TicketCategoryService
path provides min_lines
src/lib/__tests__/ticket-service.test.ts Integration tests for ticket lifecycle and category management 100
from to via pattern
src/lib/services/ticket-service.ts prisma/schema.prisma Prisma queries on Ticket model prisma.ticket.
from to via pattern
src/lib/services/ticket-category-service.ts src/lib/tenant.ts Categories seeded during tenant creation seedTicketCategories|createTenant
Build the ticketing system for tracking customer support issues.

Purpose: Tickets are how customer issues enter the system — staff creates a ticket from a client call, the ticket flows through a lifecycle, and in 03-04 tickets get converted to job orders. The model also supports subscriber-created tickets (Phase 5 portal) from the start to avoid rework.

Output: Ticket and TicketCategory Prisma models, ticket CRUD service with lifecycle management, admin-configurable categories with default seeds, API routes, integration tests.

<execution_context> @C:\Users\KevinAsprec.claude/get-shit-done/workflows/execute-plan.md @C:\Users\KevinAsprec.claude/get-shit-done/templates/summary.md </execution_context>

@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/03-operational-modules/03-CONTEXT.md @prisma/schema.prisma @src/lib/tenant.ts @src/lib/services/subscriber-service.ts Task 1: Ticket and TicketCategory Prisma models + category seeding prisma/schema.prisma, src/lib/prisma-tenant.ts, src/lib/tenant.ts, src/lib/services/ticket-category-service.ts **New enums:** - `TicketStatus { OPEN, ASSIGNED, RESOLVED, CLOSED }` - `TicketPriority { LOW, MEDIUM, HIGH, URGENT }` - `TicketSource { STAFF, SUBSCRIBER }` — supports both sources from day one

TicketCategory model:

  • id (uuid PK), tenantId
  • name (String) — e.g., "No Connection", "Slow Speed"
  • description (String?)
  • isActive (Boolean default true) — soft delete for deactivation
  • createdAt, updatedAt
  • @@unique([tenantId, name])
  • @@index([tenantId])
  • Relation: tickets Ticket[]

Ticket model:

  • id (uuid PK), tenantId
  • ticketNumber (String) — auto-generated sequential per tenant, e.g., "TKT-0001"
  • subscriberId (FK to Subscriber) — the affected subscriber
  • categoryId (FK to TicketCategory)
  • source (TicketSource default STAFF)
  • createdById (FK to User) — staff who created, or subscriber user in Phase 5
  • assignedToId (String? FK to User) — assigned staff member (set when status -> ASSIGNED)
  • subject (String) — brief issue summary
  • description (String) — detailed issue description
  • priority (TicketPriority default MEDIUM)
  • status (TicketStatus default OPEN)
  • resolvedAt (DateTime?) — when auto-resolved (all job orders completed)
  • closedAt (DateTime?) — when staff manually closes after confirming resolution
  • closedById (String? FK to User)
  • notes (String?) — internal notes
  • createdAt, updatedAt
  • @@unique([tenantId, ticketNumber])
  • @@index([tenantId]), @@index([tenantId, status]), @@index([tenantId, subscriberId]), @@index([tenantId, categoryId])

Update relations:

  • Subscriber: add tickets Ticket[]
  • User: add appropriate ticket relations (createdTickets, assignedTickets, closedTickets)

Add to TENANT_SCOPED_MODELS: "ticket", "ticketCategory"

TicketCategoryService (src/lib/services/ticket-category-service.ts):

  • seedDefaultCategories(db, tenantId) — creates default categories: No Connection, Slow Speed, Billing Inquiry, New Installation, Equipment Issue, Other
  • createCategory(db, { name, description }) — admin creates custom category
  • updateCategory(db, categoryId, { name?, description?, isActive? }) — admin edits/deactivates
  • listCategories(db, { includeInactive? }) — list categories

Update tenant creation in src/lib/tenant.ts:

  • After seedChartOfAccounts in the createTenant $transaction, call seedDefaultCategories to provision default ticket categories for new tenants.

Run npx prisma migrate dev --name add-tickets - npx prisma migrate dev completes without errors - npx prisma generate succeeds - Schema has Ticket and TicketCategory models - Creating a new tenant seeds 6 default ticket categories Ticket and TicketCategory models exist, default categories seeded at tenant creation, TENANT_SCOPED_MODELS updated, migration applied.

Task 2: TicketService + API routes + integration tests src/lib/services/ticket-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 **TicketService** (`src/lib/services/ticket-service.ts`): - `createTicket(db, { subscriberId, categoryId, subject, description, priority, source, createdById })`: 1. Auto-generate ticketNumber (pattern: TKT-NNNN, sequential per tenant — same approach as INV/JE numbers) 2. Create ticket with status OPEN 3. Return ticket with subscriber and category info - `updateTicket(db, ticketId, { subject?, description?, priority?, categoryId?, notes? })` — update editable fields (not status — status has dedicated transitions) - `assignTicket(db, ticketId, assignedToId)` — set assignedToId, transition status OPEN -> ASSIGNED - `resolveTicket(db, ticketId)` — transition to RESOLVED (called by job order completion sync in 03-04, or manually). Set resolvedAt. - `closeTicket(db, ticketId, closedById)` — transition RESOLVED -> CLOSED. Set closedAt, closedById. This is the manual confirmation step. - `reopenTicket(db, ticketId)` — RESOLVED -> OPEN (if issue not actually fixed). Clear resolvedAt. - `getTicket(db, ticketId)` — get ticket with subscriber, category, creator, assignee, and job orders (empty array until 03-04) - `listTickets(db, filters)` — list with filters: status, priority, categoryId, subscriberId, assignedToId, dateFrom, dateTo. Pagination (skip/take). Sort by createdAt DESC.

Status transition rules (enforce in service):

  • OPEN -> ASSIGNED (requires assignedToId)
  • OPEN -> CLOSED (cancel without resolving)
  • ASSIGNED -> OPEN (unassign)
  • ASSIGNED -> RESOLVED (direct resolve without job order)
  • RESOLVED -> CLOSED (staff confirmation)
  • RESOLVED -> OPEN (reopen)
  • All other transitions: throw error

API Routes:

  • GET /api/tickets — list tickets with filters. ADMIN, OFFICE_STAFF see all. TECHNICIAN sees assigned only.
  • POST /api/tickets — create ticket. ADMIN, OFFICE_STAFF. Body: { subscriberId, categoryId, subject, description, priority? }
  • GET /api/tickets/[id] — get ticket detail
  • PUT /api/tickets/[id] — update ticket fields. ADMIN, OFFICE_STAFF.
  • POST /api/tickets/[id]/status — change ticket status. Body: { status, assignedToId? }. ADMIN, OFFICE_STAFF.
  • GET /api/ticket-categories — list categories. All authenticated users.
  • POST /api/ticket-categories — create category. ADMIN only.
  • PUT /api/ticket-categories/[id] — update/deactivate category. ADMIN only.

Integration Tests (src/lib/__tests__/ticket-service.test.ts):

  • Create ticket with auto-generated ticket number
  • Ticket number sequential within tenant (TKT-0001, TKT-0002...)
  • Status transitions: OPEN -> ASSIGNED -> RESOLVED -> CLOSED (happy path)
  • Invalid transition rejected (e.g., OPEN -> RESOLVED without assignment — actually allowed per rules above, test the invalid ones: CLOSED -> OPEN)
  • Reopen ticket (RESOLVED -> OPEN)
  • List tickets with filters (status, priority, category)
  • Category CRUD (create, update, deactivate)
  • Deactivated category cannot be used for new tickets
  • Default categories seeded on tenant creation
  • Cross-tenant isolation
    • npx vitest run src/lib/__tests__/ticket-service.test.ts — all tests pass
    • npx vitest run — full suite passes (no regressions) TicketService handles ticket lifecycle (OPEN -> ASSIGNED -> RESOLVED -> CLOSED) with proper status transition enforcement. Ticket categories are admin-configurable with ISP defaults seeded at tenant creation. API routes enforce RBAC. Integration tests prove lifecycle and tenant isolation.
- Ticket CRUD: create, update, get, list with filters - Status lifecycle: OPEN -> ASSIGNED -> RESOLVED -> CLOSED with proper guards - Reopen: RESOLVED -> OPEN works - Category management: CRUD with deactivation - Default categories seeded at tenant creation (6 ISP-relevant categories) - Ticket numbers are sequential per tenant - Source field supports STAFF and SUBSCRIBER (Phase 5 ready) - All existing tests pass (no regressions)

<success_criteria>

  • Ticket and TicketCategory models with migration applied
  • TicketService handles full ticket lifecycle with enforced state transitions
  • Admin-configurable categories with 6 defaults seeded at tenant creation
  • Ticket model supports both staff and subscriber source types
  • API routes enforce RBAC (staff creates, technician views assigned)
  • Integration tests prove lifecycle, filtering, and tenant isolation </success_criteria>
After completion, create `.planning/phases/03-operational-modules/03-03-SUMMARY.md`