@@ -302,7 +302,7 @@ export function InvoiceForm({ resourceId, initialData, onSuccess }: InvoiceFormP
|
||||
<RhfTextField name="subject" label="Subject" placeholder="Invoice subject" required />
|
||||
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||
<RhfAutoGenerateField autoFetch table="invoices" name="invoice_number" label="Invoice Number" placeholder="INV-0001" />
|
||||
<RhfAutoGenerateField autoFetch table="invoices" name="invoice_number" label="Invoice Number" placeholder="INV-0001" required />
|
||||
<RhfTextField name="invoice_title" label="Invoice Title" placeholder="e.g. Tax Invoice" />
|
||||
</div>
|
||||
|
||||
@@ -338,8 +338,8 @@ export function InvoiceForm({ resourceId, initialData, onSuccess }: InvoiceFormP
|
||||
<CardContent>
|
||||
<FieldGroup>
|
||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-1">
|
||||
<RhfDateField name="invoice_date" label="Invoice Date" />
|
||||
<RhfDateField name="due_date" label="Due Date" />
|
||||
<RhfDateField name="invoice_date" label="Invoice Date" required />
|
||||
<RhfDateField name="due_date" label="Due Date" required />
|
||||
</div>
|
||||
|
||||
<RhfCustomerSelectField<InvoiceFormValues, "customer"> name="customer" />
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
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(),
|
||||
@@ -33,7 +39,7 @@ const invoiceExpenseItemSchema = z.object({
|
||||
|
||||
const invoiceFormSchema = z.object({
|
||||
// ── Required fields ──
|
||||
subject: z.string().min(1, "Subject is required"),
|
||||
subject: z.string().trim().min(1, "Subject is required").max(255, "Subject must be at most 255 characters"),
|
||||
|
||||
// ── Relations ──
|
||||
customer: relationFieldSchema,
|
||||
@@ -47,14 +53,14 @@ const invoiceFormSchema = z.object({
|
||||
invoice_to: relationFieldSchema,
|
||||
|
||||
// ── Optional fields ──
|
||||
invoice_number: z.string().optional(),
|
||||
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: z.string().optional(),
|
||||
due_date: z.string().optional(),
|
||||
status: 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.string().optional(),
|
||||
discount: z.enum(InvoiceDiscount).optional(),
|
||||
discount_amount: z.coerce.number().min(0).optional(),
|
||||
tax: relationFieldSchema,
|
||||
deposit_to: z.string().optional(),
|
||||
@@ -65,6 +71,19 @@ const invoiceFormSchema = z.object({
|
||||
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>
|
||||
|
||||
Reference in New Issue
Block a user