96 lines
3.4 KiB
TypeScript
96 lines
3.4 KiB
TypeScript
import { z } from "zod"
|
|
import { InvoiceDiscount, InvoiceStatus } from "@garage/api"
|
|
|
|
const relationFieldSchema = z
|
|
.object({ value: z.string(), label: z.string() })
|
|
.nullable()
|
|
|
|
const requiredDateSchema = z
|
|
.string()
|
|
.min(1, "This field is required")
|
|
.date("Enter a valid date")
|
|
|
|
const invoicePartItemSchema = z.object({
|
|
part_id: z.number(),
|
|
title: z.string(),
|
|
quantity: z.number().min(1),
|
|
rate: z.number().min(0),
|
|
discount_amount: z.number().min(0).optional(),
|
|
description: z.string().optional(),
|
|
})
|
|
|
|
const invoiceServiceItemSchema = z.object({
|
|
service_id: z.number(),
|
|
title: z.string(),
|
|
quantity: z.number().min(1),
|
|
rate: z.number().min(0),
|
|
discount_amount: z.number().min(0).optional(),
|
|
description: z.string().optional(),
|
|
})
|
|
|
|
const invoiceExpenseItemSchema = z.object({
|
|
expense_id: z.number(),
|
|
title: z.string(),
|
|
quantity: z.number().min(1),
|
|
rate: z.number().min(0),
|
|
discount_amount: z.number().min(0).optional(),
|
|
description: z.string().optional(),
|
|
})
|
|
|
|
const invoiceFormSchema = z.object({
|
|
// ── Required fields ──
|
|
subject: z.string().trim().min(1, "Subject is required").max(255, "Subject must be at most 255 characters"),
|
|
|
|
// ── Relations ──
|
|
customer: relationFieldSchema,
|
|
vehicle: relationFieldSchema,
|
|
department: relationFieldSchema,
|
|
estimate: relationFieldSchema,
|
|
payment_terms: relationFieldSchema,
|
|
invoice_sequence: relationFieldSchema,
|
|
payment_mode: relationFieldSchema,
|
|
insurer: relationFieldSchema,
|
|
invoice_to: relationFieldSchema,
|
|
|
|
// ── Optional fields ──
|
|
invoice_number: z.string().trim().min(1, "Invoice number is required").max(255, "Invoice number must be at most 255 characters"),
|
|
invoice_title: z.string().optional(),
|
|
invoice_date: requiredDateSchema,
|
|
due_date: requiredDateSchema,
|
|
status: z.enum(InvoiceStatus).optional(),
|
|
kms_in: z.coerce.number().optional(),
|
|
has_insurance: z.boolean().default(false),
|
|
discount: z.enum(InvoiceDiscount).optional(),
|
|
discount_amount: z.coerce.number().min(0).optional(),
|
|
tax: relationFieldSchema,
|
|
deposit_to: z.string().optional(),
|
|
notes: z.string().optional(),
|
|
terms_and_conditions: z.string().optional(),
|
|
|
|
// ── Line items ──
|
|
parts: z.array(invoicePartItemSchema).optional(),
|
|
services: z.array(invoiceServiceItemSchema).optional(),
|
|
expense_items: z.array(invoiceExpenseItemSchema).optional(),
|
|
}).superRefine((values, ctx) => {
|
|
if (values.invoice_date && values.due_date) {
|
|
const invoiceDate = new Date(values.invoice_date)
|
|
const dueDate = new Date(values.due_date)
|
|
|
|
if (!Number.isNaN(invoiceDate.getTime()) && !Number.isNaN(dueDate.getTime()) && dueDate < invoiceDate) {
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
path: ["due_date"],
|
|
message: "Due date must be on or after invoice date",
|
|
})
|
|
}
|
|
}
|
|
})
|
|
|
|
type InvoiceFormValues = z.infer<typeof invoiceFormSchema>
|
|
type InvoicePartItem = z.infer<typeof invoicePartItemSchema>
|
|
type InvoiceServiceItem = z.infer<typeof invoiceServiceItemSchema>
|
|
type InvoiceExpenseItem = z.infer<typeof invoiceExpenseItemSchema>
|
|
|
|
export { invoiceFormSchema, relationFieldSchema, invoicePartItemSchema, invoiceServiceItemSchema, invoiceExpenseItemSchema }
|
|
export type { InvoiceFormValues, InvoicePartItem, InvoiceServiceItem, InvoiceExpenseItem }
|