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>
207 lines
9.7 KiB
Markdown
207 lines
9.7 KiB
Markdown
---
|
|
phase: 03-operational-modules
|
|
plan: "04"
|
|
type: execute
|
|
wave: 2
|
|
depends_on: ["03-03"]
|
|
files_modified:
|
|
- prisma/schema.prisma
|
|
- src/lib/prisma-tenant.ts
|
|
- src/lib/services/job-order-service.ts
|
|
- src/app/api/job-orders/route.ts
|
|
- src/app/api/job-orders/[id]/route.ts
|
|
- src/app/api/job-orders/[id]/status/route.ts
|
|
- src/app/api/tickets/[id]/job-orders/route.ts
|
|
- src/lib/__tests__/job-order-service.test.ts
|
|
autonomous: true
|
|
|
|
must_haves:
|
|
truths:
|
|
- "Staff can convert a ticket into a job order assigned to a technician"
|
|
- "One ticket can have multiple job orders (1:many)"
|
|
- "Technician can view their assigned job orders and update status (PENDING -> IN_PROGRESS -> COMPLETED)"
|
|
- "Job completion includes outcome notes, completion date"
|
|
- "When ALL job orders on a ticket are completed, ticket auto-moves to RESOLVED"
|
|
- "Staff manually closes ticket after confirming resolution (two-step: auto-resolve then close)"
|
|
artifacts:
|
|
- path: "prisma/schema.prisma"
|
|
provides: "JobOrder model with status lifecycle and ticket relation"
|
|
contains: "model JobOrder"
|
|
- path: "src/lib/services/job-order-service.ts"
|
|
provides: "Job order CRUD, status transitions, ticket-job synchronization"
|
|
exports: ["JobOrderService"]
|
|
- path: "src/lib/__tests__/job-order-service.test.ts"
|
|
provides: "Integration tests for job order lifecycle and ticket sync"
|
|
min_lines: 100
|
|
key_links:
|
|
- from: "src/lib/services/job-order-service.ts"
|
|
to: "src/lib/services/ticket-service.ts"
|
|
via: "Auto-resolves ticket when all job orders completed"
|
|
pattern: "TicketService|resolveTicket"
|
|
- from: "src/app/api/tickets/[id]/job-orders/route.ts"
|
|
to: "src/lib/services/job-order-service.ts"
|
|
via: "POST creates job order from ticket"
|
|
pattern: "createJobOrder"
|
|
---
|
|
|
|
<objective>
|
|
Build the job order workflow that converts tickets into assignable technician work.
|
|
|
|
Purpose: Job orders are how tickets become actionable work for technicians. A ticket can spawn multiple job orders (different visits or skill types). The critical synchronization rule: when all job orders on a ticket complete, the ticket auto-resolves, and staff then manually closes after confirming with the subscriber.
|
|
|
|
Output: JobOrder Prisma model, job order CRUD with status lifecycle, ticket-to-job conversion, auto-resolution sync, technician self-service status updates, integration tests.
|
|
</objective>
|
|
|
|
<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>
|
|
|
|
<context>
|
|
@.planning/PROJECT.md
|
|
@.planning/ROADMAP.md
|
|
@.planning/STATE.md
|
|
@.planning/phases/03-operational-modules/03-CONTEXT.md
|
|
@.planning/phases/03-operational-modules/03-03-SUMMARY.md
|
|
@prisma/schema.prisma
|
|
@src/lib/services/ticket-service.ts
|
|
</context>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: JobOrder Prisma model + migration</name>
|
|
<files>prisma/schema.prisma, src/lib/prisma-tenant.ts</files>
|
|
<action>
|
|
**New enums:**
|
|
- `JobOrderStatus { PENDING, IN_PROGRESS, COMPLETED, CANCELLED }`
|
|
- `JobType { INSTALLATION, REPAIR, MAINTENANCE, RELOCATION, DISCONNECTION, OTHER }`
|
|
|
|
**JobOrder model:**
|
|
- id (uuid PK), tenantId
|
|
- orderNumber (String) — auto-generated sequential per tenant, e.g., "JO-0001"
|
|
- ticketId (FK to Ticket) — parent ticket
|
|
- assignedToId (FK to User) — the technician
|
|
- jobType (JobType)
|
|
- description (String) — what needs to be done
|
|
- status (JobOrderStatus default PENDING)
|
|
- scheduledDate (DateTime?) — optional scheduled date
|
|
- startedAt (DateTime?) — when technician started work
|
|
- completedAt (DateTime?) — when work was completed
|
|
- outcomeNotes (String?) — technician fills in on completion
|
|
- cancelledAt (DateTime?)
|
|
- cancelReason (String?)
|
|
- createdById (FK to User) — staff who created the job order
|
|
- createdAt, updatedAt
|
|
- @@unique([tenantId, orderNumber])
|
|
- @@index([tenantId]), @@index([tenantId, assignedToId]), @@index([tenantId, status]), @@index([ticketId])
|
|
|
|
**Update relations:**
|
|
- Ticket: add `jobOrders JobOrder[]`
|
|
- User: add `assignedJobOrders JobOrder[] @relation("JobOrderAssignedTo")`, `createdJobOrders JobOrder[] @relation("JobOrderCreatedBy")`
|
|
|
|
**Add to TENANT_SCOPED_MODELS:** "jobOrder"
|
|
|
|
Run `npx prisma migrate dev --name add-job-orders`
|
|
</action>
|
|
<verify>
|
|
- `npx prisma migrate dev` completes without errors
|
|
- `npx prisma generate` succeeds
|
|
- Schema has JobOrder model with correct enums and relations
|
|
</verify>
|
|
<done>JobOrder model exists with status lifecycle, ticket relation (1:many), technician assignment, and job type classification. Migration applied.</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: JobOrderService + API routes + integration tests</name>
|
|
<files>src/lib/services/job-order-service.ts, src/app/api/job-orders/route.ts, src/app/api/job-orders/[id]/route.ts, src/app/api/job-orders/[id]/status/route.ts, src/app/api/tickets/[id]/job-orders/route.ts, src/lib/__tests__/job-order-service.test.ts</files>
|
|
<action>
|
|
**JobOrderService** (`src/lib/services/job-order-service.ts`):
|
|
- `createJobOrder(db, { ticketId, assignedToId, jobType, description, scheduledDate?, createdById })`:
|
|
1. Validate ticket exists and is not CLOSED
|
|
2. Validate assignedToId is a user with TECHNICIAN role
|
|
3. Auto-generate orderNumber (JO-NNNN per tenant)
|
|
4. Create job order with status PENDING
|
|
5. If ticket status is OPEN, auto-transition ticket to ASSIGNED (via TicketService.assignTicket with the first technician)
|
|
6. Return job order with ticket and technician info
|
|
|
|
- `updateJobOrder(db, jobOrderId, { description?, scheduledDate?, jobType? })` — update editable fields
|
|
|
|
- `updateStatus(db, jobOrderId, { status, outcomeNotes?, cancelReason? })`:
|
|
Status transitions:
|
|
- PENDING -> IN_PROGRESS: set startedAt
|
|
- PENDING -> CANCELLED: set cancelledAt, cancelReason
|
|
- IN_PROGRESS -> COMPLETED: set completedAt, outcomeNotes (required). Then call `checkTicketAutoResolve`.
|
|
- IN_PROGRESS -> CANCELLED: set cancelledAt, cancelReason
|
|
- All other transitions: throw error
|
|
|
|
- `checkTicketAutoResolve(db, ticketId)`:
|
|
1. Load all job orders for this ticket
|
|
2. If ALL non-cancelled job orders have status COMPLETED, auto-resolve the ticket via TicketService.resolveTicket
|
|
3. If there are only cancelled job orders (no completed ones), do NOT auto-resolve
|
|
|
|
- `reassignJobOrder(db, jobOrderId, newAssignedToId)` — reassign to different technician (only if PENDING or IN_PROGRESS)
|
|
|
|
- `getJobOrder(db, jobOrderId)` — get detail with ticket, subscriber, technician info
|
|
|
|
- `listJobOrders(db, filters)` — list with filters: assignedToId, status, jobType, ticketId, dateFrom, dateTo. Pagination. Sort by createdAt DESC.
|
|
|
|
- `getTechnicianJobOrders(db, technicianId, filters)` — convenience wrapper for technician self-service view
|
|
|
|
**API Routes:**
|
|
- `POST /api/tickets/[id]/job-orders` — create job order from ticket. ADMIN, OFFICE_STAFF. Body: { assignedToId, jobType, description, scheduledDate? }
|
|
- `GET /api/job-orders` — list job orders with filters. ADMIN, OFFICE_STAFF see all. TECHNICIAN sees assigned only.
|
|
- `GET /api/job-orders/[id]` — get job order detail
|
|
- `PUT /api/job-orders/[id]` — update job order fields. ADMIN, OFFICE_STAFF.
|
|
- `POST /api/job-orders/[id]/status` — update status. TECHNICIAN can update own (PENDING->IN_PROGRESS, IN_PROGRESS->COMPLETED). ADMIN, OFFICE_STAFF can do any valid transition.
|
|
Body: { status, outcomeNotes?, cancelReason? }
|
|
|
|
**Integration Tests** (`src/lib/__tests__/job-order-service.test.ts`):
|
|
- Create job order from ticket (auto-assigns ticket to ASSIGNED status)
|
|
- One ticket can have multiple job orders
|
|
- Status transitions: PENDING -> IN_PROGRESS -> COMPLETED (happy path)
|
|
- Completing last job order auto-resolves parent ticket
|
|
- Completing one of two job orders does NOT resolve ticket
|
|
- All job orders completed -> ticket auto-resolved -> staff closes ticket
|
|
- Cancelled job orders are excluded from auto-resolve check
|
|
- Cannot complete job order without outcomeNotes
|
|
- Invalid transitions rejected (e.g., COMPLETED -> IN_PROGRESS)
|
|
- Reassign job order to different technician
|
|
- Technician filter returns only their assigned orders
|
|
- Job order number sequential per tenant (JO-0001, JO-0002...)
|
|
- Cross-tenant isolation
|
|
</action>
|
|
<verify>
|
|
- `npx vitest run src/lib/__tests__/job-order-service.test.ts` — all tests pass
|
|
- `npx vitest run` — full suite passes (no regressions)
|
|
</verify>
|
|
<done>JobOrderService handles job order lifecycle with ticket auto-resolution sync. Technicians update their own work, staff manages assignments. All status transitions enforced. Integration tests prove the ticket-to-job-order-to-resolution flow.</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<verification>
|
|
- Create job order from ticket: ticket auto-transitions to ASSIGNED
|
|
- One ticket, multiple job orders: each tracks independently
|
|
- Status lifecycle: PENDING -> IN_PROGRESS -> COMPLETED with proper guards
|
|
- Completion requires outcomeNotes
|
|
- Auto-resolution: all non-cancelled job orders COMPLETED -> ticket RESOLVED
|
|
- Staff closes ticket (RESOLVED -> CLOSED) as separate manual step
|
|
- Technician sees only their assigned job orders
|
|
- Job order numbers sequential per tenant
|
|
- All existing tests pass (no regressions)
|
|
</verification>
|
|
|
|
<success_criteria>
|
|
- JobOrder model with status lifecycle, ticket 1:many relation, and technician assignment
|
|
- JobOrderService handles creation from ticket, status transitions, auto-resolution sync
|
|
- Technicians can update their own job orders (view assigned, update status)
|
|
- Ticket auto-resolves when all non-cancelled job orders complete
|
|
- API routes enforce RBAC (staff creates, technician updates own)
|
|
- Integration tests prove full ticket-to-job-to-resolution workflow
|
|
</success_criteria>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/03-operational-modules/03-04-SUMMARY.md`
|
|
</output>
|