feat(03-04): JobOrder schema, migration, and tenant scoping

- Added JobOrderStatus enum (PENDING, IN_PROGRESS, COMPLETED, CANCELLED)
- Added JobOrder model with ticket FK, assignedTo/createdBy user FKs
- Added orderNumber (JO-NNNN), scheduledDate, timestamps, outcomeNotes
- Added jobOrders reverse relation on Ticket model
- Added assignedJobOrders/createdJobOrders reverse relations on User model
- Applied migration: 20260304235859_add_job_orders
- Added jobOrder to TENANT_SCOPED_MODELS with full 12-operation extension block
This commit is contained in:
kevin-asprec
2026-03-05 07:59:56 +08:00
parent 0373810d9e
commit d59f1d55ea
3 changed files with 204 additions and 1 deletions

View File

@@ -129,6 +129,13 @@ enum RemittanceStatus {
VERIFIED
}
enum JobOrderStatus {
PENDING
IN_PROGRESS
COMPLETED
CANCELLED
}
// =============================================================================
// MODELS
// =============================================================================
@@ -381,6 +388,10 @@ model User {
remittances Remittance[] @relation("RemittanceByCollector")
/// Remittances verified by this user (office staff)
verifiedRemittances Remittance[] @relation("RemittanceVerifiedBy")
/// Job orders assigned to this technician
assignedJobOrders JobOrder[] @relation("JobOrderAssignedTo")
/// Job orders created by this user (staff)
createdJobOrders JobOrder[] @relation("JobOrderCreatedBy")
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@ -626,6 +637,8 @@ model Ticket {
@@index([tenantId, status])
@@index([tenantId, categoryId])
@@index([subscriberId])
jobOrders JobOrder[]
}
/// A Collection records cash received from a subscriber by a field collector.
@@ -722,6 +735,51 @@ model Remittance {
@@index([tenantId, status])
}
/// A JobOrder is the execution unit created from a support ticket.
/// One ticket can have many job orders (1:many).
/// A technician is assigned to each job order and updates its status.
/// When ALL non-cancelled job orders on a ticket are COMPLETED, the ticket auto-resolves.
/// When ALL job orders on a ticket are CANCELLED, the ticket reverts to OPEN.
model JobOrder {
id String @id @default(uuid())
tenantId String
/// Auto-generated sequential identifier per tenant (e.g., "JO-0001")
orderNumber String
/// The ticket this job order is for
ticketId String
ticket Ticket @relation(fields: [ticketId], references: [id])
/// Type of work (e.g., "Installation", "Repair", "Maintenance")
jobType String
description String?
/// The technician assigned to this job order
assignedToId String
assignedTo User @relation("JobOrderAssignedTo", fields: [assignedToId], references: [id])
status JobOrderStatus @default(PENDING)
scheduledDate DateTime?
/// Timestamp when the technician started working
startedAt DateTime?
/// Timestamp when the job was completed
completedAt DateTime?
/// Notes on outcome, required when marking COMPLETED
outcomeNotes String?
/// Timestamp when the job was cancelled
cancelledAt DateTime?
cancelReason String?
/// The staff member who created this job order
createdById String
createdBy User @relation("JobOrderCreatedBy", fields: [createdById], references: [id])
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
/// Order numbers must be unique within a tenant
@@unique([tenantId, orderNumber])
/// RLS-ready index — always present on tenant-scoped models
@@index([tenantId])
@@index([tenantId, ticketId])
@@index([tenantId, assignedToId])
@@index([tenantId, status])
}
/// A single line item on an Invoice (e.g., "50 Mbps Monthly Service — $49.99").
model InvoiceLine {
id String @id @default(uuid())