Add repairs domain with tickets, line items, batches, and service templates
Full-stack implementation of instrument repair tracking: DB schema with repair_ticket, repair_line_item, repair_batch, and repair_service_template tables. Backend services and routes with pagination/search/sort. 20 API tests covering CRUD, status workflow, line items, and batch operations. Admin frontend with ticket list, detail with status progression, line item management, batch list/detail with approval workflow, and new ticket form with searchable account picker and intake photo uploads.
This commit is contained in:
115
packages/shared/src/schemas/repairs.schema.ts
Normal file
115
packages/shared/src/schemas/repairs.schema.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
/** Coerce empty strings to undefined — solves HTML form inputs sending '' for blank optional fields */
|
||||
function opt<T extends z.ZodTypeAny>(schema: T) {
|
||||
return z.preprocess((v) => (v === '' ? undefined : v), schema.optional())
|
||||
}
|
||||
|
||||
// --- Status / Type enums ---
|
||||
|
||||
export const RepairTicketStatus = z.enum([
|
||||
'intake', 'diagnosing', 'pending_approval', 'approved',
|
||||
'in_progress', 'pending_parts', 'ready', 'picked_up', 'delivered', 'cancelled',
|
||||
])
|
||||
export type RepairTicketStatus = z.infer<typeof RepairTicketStatus>
|
||||
|
||||
export const RepairLineItemType = z.enum(['labor', 'part', 'flat_rate', 'misc'])
|
||||
export type RepairLineItemType = z.infer<typeof RepairLineItemType>
|
||||
|
||||
export const RepairConditionIn = z.enum(['excellent', 'good', 'fair', 'poor'])
|
||||
export type RepairConditionIn = z.infer<typeof RepairConditionIn>
|
||||
|
||||
export const RepairBatchStatus = z.enum([
|
||||
'intake', 'in_progress', 'pending_approval', 'approved', 'completed', 'delivered', 'cancelled',
|
||||
])
|
||||
export type RepairBatchStatus = z.infer<typeof RepairBatchStatus>
|
||||
|
||||
export const RepairBatchApproval = z.enum(['pending', 'approved', 'rejected'])
|
||||
export type RepairBatchApproval = z.infer<typeof RepairBatchApproval>
|
||||
|
||||
// --- Repair Ticket schemas ---
|
||||
|
||||
export const RepairTicketCreateSchema = z.object({
|
||||
customerName: z.string().min(1).max(255),
|
||||
customerPhone: opt(z.string().max(50)),
|
||||
accountId: opt(z.string().uuid()),
|
||||
locationId: opt(z.string().uuid()),
|
||||
repairBatchId: opt(z.string().uuid()),
|
||||
inventoryUnitId: opt(z.string().uuid()),
|
||||
instrumentDescription: opt(z.string()),
|
||||
serialNumber: opt(z.string().max(255)),
|
||||
conditionIn: opt(RepairConditionIn),
|
||||
conditionInNotes: opt(z.string()),
|
||||
problemDescription: z.string().min(1),
|
||||
technicianNotes: opt(z.string()),
|
||||
assignedTechnicianId: opt(z.string().uuid()),
|
||||
estimatedCost: z.coerce.number().min(0).optional(),
|
||||
promisedDate: opt(z.string()),
|
||||
})
|
||||
export type RepairTicketCreateInput = z.infer<typeof RepairTicketCreateSchema>
|
||||
|
||||
export const RepairTicketUpdateSchema = RepairTicketCreateSchema.partial()
|
||||
export type RepairTicketUpdateInput = z.infer<typeof RepairTicketUpdateSchema>
|
||||
|
||||
export const RepairTicketStatusUpdateSchema = z.object({
|
||||
status: RepairTicketStatus,
|
||||
})
|
||||
export type RepairTicketStatusUpdateInput = z.infer<typeof RepairTicketStatusUpdateSchema>
|
||||
|
||||
// --- Repair Line Item schemas ---
|
||||
|
||||
export const RepairLineItemCreateSchema = z.object({
|
||||
repairTicketId: z.string().uuid(),
|
||||
itemType: RepairLineItemType,
|
||||
description: z.string().min(1).max(255),
|
||||
productId: opt(z.string().uuid()),
|
||||
qty: z.coerce.number().min(0).default(1),
|
||||
unitPrice: z.coerce.number().min(0).default(0),
|
||||
totalPrice: z.coerce.number().min(0).default(0),
|
||||
cost: z.coerce.number().min(0).optional(),
|
||||
technicianId: opt(z.string().uuid()),
|
||||
})
|
||||
export type RepairLineItemCreateInput = z.infer<typeof RepairLineItemCreateSchema>
|
||||
|
||||
export const RepairLineItemUpdateSchema = RepairLineItemCreateSchema.omit({ repairTicketId: true }).partial()
|
||||
export type RepairLineItemUpdateInput = z.infer<typeof RepairLineItemUpdateSchema>
|
||||
|
||||
// --- Repair Batch schemas ---
|
||||
|
||||
export const RepairBatchCreateSchema = z.object({
|
||||
accountId: z.string().uuid(),
|
||||
locationId: opt(z.string().uuid()),
|
||||
contactName: opt(z.string().max(255)),
|
||||
contactPhone: opt(z.string().max(50)),
|
||||
contactEmail: opt(z.string().email()),
|
||||
pickupDate: opt(z.string()),
|
||||
dueDate: opt(z.string()),
|
||||
instrumentCount: z.coerce.number().int().min(0).default(0),
|
||||
notes: opt(z.string()),
|
||||
})
|
||||
export type RepairBatchCreateInput = z.infer<typeof RepairBatchCreateSchema>
|
||||
|
||||
export const RepairBatchUpdateSchema = RepairBatchCreateSchema.partial()
|
||||
export type RepairBatchUpdateInput = z.infer<typeof RepairBatchUpdateSchema>
|
||||
|
||||
export const RepairBatchStatusUpdateSchema = z.object({
|
||||
status: RepairBatchStatus,
|
||||
})
|
||||
export type RepairBatchStatusUpdateInput = z.infer<typeof RepairBatchStatusUpdateSchema>
|
||||
|
||||
// --- Repair Service Template schemas ---
|
||||
|
||||
export const RepairServiceTemplateCreateSchema = z.object({
|
||||
name: z.string().min(1).max(255),
|
||||
instrumentType: opt(z.string().max(100)),
|
||||
size: opt(z.string().max(50)),
|
||||
description: opt(z.string()),
|
||||
itemType: RepairLineItemType.default('flat_rate'),
|
||||
defaultPrice: z.coerce.number().min(0).default(0),
|
||||
defaultCost: z.coerce.number().min(0).optional(),
|
||||
sortOrder: z.coerce.number().int().default(0),
|
||||
})
|
||||
export type RepairServiceTemplateCreateInput = z.infer<typeof RepairServiceTemplateCreateSchema>
|
||||
|
||||
export const RepairServiceTemplateUpdateSchema = RepairServiceTemplateCreateSchema.partial()
|
||||
export type RepairServiceTemplateUpdateInput = z.infer<typeof RepairServiceTemplateUpdateSchema>
|
||||
Reference in New Issue
Block a user