|
|
@@ -1,12 +1,18 @@
|
|
|
import { z } from "zod";
|
|
|
|
|
|
+// Coerce to number but pass through null/undefined instead of coercing null to 0
|
|
|
+const coerceNumberNullish = z.preprocess(
|
|
|
+ (v) => (v === null || v === undefined || v === "" ? undefined : v),
|
|
|
+ z.coerce.number().optional(),
|
|
|
+);
|
|
|
+
|
|
|
export const servicePartSchema = z.object({
|
|
|
- partNumber: z.string().optional(),
|
|
|
+ partNumber: z.string().nullish(),
|
|
|
name: z.string().min(1, "Part name is required"),
|
|
|
quantity: z.coerce.number().min(0).default(1),
|
|
|
unitPrice: z.coerce.number().min(0).default(0),
|
|
|
total: z.coerce.number().min(0).default(0),
|
|
|
- inventoryPartId: z.string().optional(),
|
|
|
+ inventoryPartId: z.string().nullish(),
|
|
|
});
|
|
|
|
|
|
export const serviceLaborSchema = z.object({
|
|
|
@@ -22,36 +28,36 @@ export const serviceAttachmentSchema = z.object({
|
|
|
fileType: z.string(),
|
|
|
fileSize: z.number(),
|
|
|
category: z.enum(["image", "diagnostic", "document", "video"]).default("diagnostic"),
|
|
|
- description: z.string().optional(),
|
|
|
+ description: z.string().nullish(),
|
|
|
includeInInvoice: z.boolean().default(true),
|
|
|
});
|
|
|
|
|
|
export const createServiceSchema = z.object({
|
|
|
vehicleId: z.string(),
|
|
|
title: z.string().min(1, "Title is required"),
|
|
|
- description: z.string().optional(),
|
|
|
+ description: z.string().nullish(),
|
|
|
type: z.enum(["maintenance", "repair", "upgrade", "inspection"]).default("maintenance"),
|
|
|
status: z.enum(["pending", "in-progress", "waiting-parts", "completed"]).default("pending"),
|
|
|
cost: z.coerce.number().min(0).default(0),
|
|
|
- mileage: z.coerce.number().optional(),
|
|
|
+ mileage: coerceNumberNullish,
|
|
|
serviceDate: z.string().default(() => new Date().toISOString()),
|
|
|
- shopName: z.string().optional(),
|
|
|
- techName: z.string().optional(),
|
|
|
- parts: z.string().optional(),
|
|
|
- laborHours: z.coerce.number().optional(),
|
|
|
- diagnosticNotes: z.string().optional(),
|
|
|
- invoiceNotes: z.string().optional(),
|
|
|
- partItems: z.array(servicePartSchema).optional(),
|
|
|
- laborItems: z.array(serviceLaborSchema).optional(),
|
|
|
- attachments: z.array(serviceAttachmentSchema).optional(),
|
|
|
+ shopName: z.string().nullish(),
|
|
|
+ techName: z.string().nullish(),
|
|
|
+ parts: z.string().nullish(),
|
|
|
+ laborHours: coerceNumberNullish,
|
|
|
+ diagnosticNotes: z.string().nullish(),
|
|
|
+ invoiceNotes: z.string().nullish(),
|
|
|
+ partItems: z.array(servicePartSchema).nullish(),
|
|
|
+ laborItems: z.array(serviceLaborSchema).nullish(),
|
|
|
+ attachments: z.array(serviceAttachmentSchema).nullish(),
|
|
|
subtotal: z.coerce.number().min(0).default(0),
|
|
|
taxRate: z.coerce.number().min(0).default(0),
|
|
|
taxAmount: z.coerce.number().min(0).default(0),
|
|
|
totalAmount: z.coerce.number().min(0).default(0),
|
|
|
- discountType: z.enum(["none", "percentage", "fixed"]).optional(),
|
|
|
+ discountType: z.enum(["none", "percentage", "fixed"]).nullish(),
|
|
|
discountValue: z.coerce.number().min(0).default(0),
|
|
|
discountAmount: z.coerce.number().min(0).default(0),
|
|
|
- invoiceNumber: z.string().optional(),
|
|
|
+ invoiceNumber: z.string().nullish(),
|
|
|
});
|
|
|
|
|
|
export const updateServiceSchema = createServiceSchema.partial().extend({
|