finish bills , po , jobcards relational fields creation

This commit is contained in:
2026-04-13 18:48:15 +03:00
parent 3eb471a604
commit 020ffccfd6
41 changed files with 11973 additions and 467 deletions
+75 -2
View File
@@ -16,8 +16,11 @@ import { useResourceForm } from "@/shared/hooks/use-resource-form"
import { useFormMutation } from "@/shared/hooks/use-form-mutation"
import { toId, toRelation } from "@/shared/lib/utils"
import { useAuthApi } from "@/shared/useApi"
import { BillStatus, BILL_ROUTES, DEPARTMENT_ROUTES, JOB_CARD_ROUTES, PAYMENT_TERM_ROUTES, VENDOR_ROUTES } from "@garage/api"
import { BillStatus, BILL_ROUTES, DEPARTMENT_ROUTES, JOB_CARD_ROUTES, PAYMENT_TERM_ROUTES, PURCHASE_ORDER_ROUTES, VENDOR_ROUTES } from "@garage/api"
import { toast } from "sonner"
import { PartsSelectorField } from "@/modules/parts/parts-selector-field"
import { ServicesSelectorField } from "@/modules/services/services-selector-field"
import { ExpenseItemsSelectorField } from "@/modules/expense-items/expense-items-selector-field"
import { billFormSchema, type BillFormValues } from "./bill.schema"
@@ -29,14 +32,19 @@ export type BillFormProps = {
const DEFAULT_VALUES: BillFormValues = {
vendor: null,
purchase_order: null,
job_card: null,
payment_term: null,
department: null,
title: "",
bill_number: "",
bill_date: "",
bill_due_date: "",
status: "draft",
notes: "",
part_items: [],
service_items: [],
expense_items: [],
}
const STATUS_OPTIONS = BillStatus.map((value) => ({
@@ -56,14 +64,37 @@ function mapToFormValues(data: unknown): BillFormValues {
return {
vendor: toRelation(d.vendor_id, d.vendor_name),
purchase_order: toRelation(d.purchase_order_id, d.purchase_order_number ?? d.purchase_order_title),
job_card: toRelation(d.job_card_id, d.job_card_number ?? d.job_card_name),
payment_term: toRelation(d.payment_terms_id, d.payment_terms_name),
department: toRelation(d.department_id, d.department_name),
title: d.title || "",
bill_number: d.bill_number || "",
bill_date: d.bill_date || "",
bill_due_date: d.bill_due_date || "",
status: d.status || "draft",
notes: d.notes || "",
part_items: (d.parts ?? []).map((p: any) => ({
part_id: p.part_id ?? p.id,
title: p.part?.title ?? p.title ?? "",
quantity: Number(p.quantity) || 1,
rate: Number(p.rate) || 0,
description: p.description ?? "",
})),
service_items: (d.services ?? []).map((s: any) => ({
service_id: s.service_id ?? s.id,
title: s.service?.labor_name ?? s.labor_name ?? s.title ?? "",
quantity: Number(s.quantity) || 1,
rate: Number(s.rate) || 0,
description: s.description ?? "",
})),
expense_items: (d.expenses ?? []).map((e: any) => ({
expense_id: e.expense_id ?? e.id,
title: e.expense?.item_name ?? e.item_name ?? e.title ?? "",
quantity: Number(e.quantity) || 1,
rate: Number(e.rate) || 0,
description: e.description ?? "",
})),
}
}
@@ -71,13 +102,33 @@ function mapFormToPayload(values: BillFormValues) {
return {
title: values.title,
vendor_id: toId(values.vendor),
purchase_order_id: toId(values.purchase_order),
job_card_id: toId(values.job_card),
payment_terms_id: toId(values.payment_term),
department_id: toId(values.department),
bill_number: values.bill_number || undefined,
bill_date: values.bill_date || undefined,
bill_due_date: values.bill_due_date || undefined,
status: values.status || undefined,
notes: values.notes || undefined,
part_items: (values.part_items ?? []).map((item) => ({
part_id: item.part_id,
quantity: item.quantity,
rate: item.rate,
description: item.description || undefined,
})),
service_items: (values.service_items ?? []).map((item) => ({
service_id: item.service_id,
quantity: item.quantity,
rate: item.rate,
description: item.description || undefined,
})),
expense_items: (values.expense_items ?? []).map((item) => ({
expense_id: item.expense_id,
quantity: item.quantity,
rate: item.rate,
description: item.description || undefined,
})),
}
}
@@ -126,6 +177,11 @@ export function BillForm({ resourceId, initialData, onSuccess }: BillFormProps)
<FieldGroup>
<RhfTextField name="title" label="Title" placeholder="Enter bill title" required />
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfTextField name="bill_number" label="Bill Number" placeholder="e.g. BILL-001" />
<RhfSelectField name="status" label="Status" options={STATUS_OPTIONS} />
</div>
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
<RhfTextField name="bill_date" label="Bill Date" type="date" />
<RhfTextField name="bill_due_date" label="Due Date" type="date" />
@@ -173,9 +229,25 @@ export function BillForm({ resourceId, initialData, onSuccess }: BillFormProps)
/>
</div>
<RhfSelectField name="status" label="Status" options={STATUS_OPTIONS} />
<RhfAsyncSelectField
name="purchase_order"
label="Purchase Order"
placeholder="Select purchase order"
queryKey={[PURCHASE_ORDER_ROUTES.INDEX]}
listFn={() => api.purchaseOrders.list()}
mapOption={(item: any) => ({
value: String(item.id),
label: item.order_number || item.title || `#${item.id}`,
})}
{...STORE_OBJECT}
/>
<RhfTextareaField name="notes" label="Notes" rows={3} />
<PartsSelectorField<BillFormValues, "part_items"> name="part_items" />
<ServicesSelectorField<BillFormValues, "service_items"> name="service_items" />
<ExpenseItemsSelectorField<BillFormValues, "expense_items"> name="expense_items" />
<Button type="submit" disabled={isPending}>
{isPending ? (
"Saving..."
@@ -195,3 +267,4 @@ export function BillForm({ resourceId, initialData, onSuccess }: BillFormProps)
</Rhform>
)
}
+34 -2
View File
@@ -4,19 +4,51 @@ const relationFieldSchema = z
.object({ value: z.string(), label: z.string() })
.nullable()
const billPartItemSchema = z.object({
part_id: z.number(),
title: z.string(),
quantity: z.number().min(1),
rate: z.number().min(0),
description: z.string().optional(),
})
const billServiceItemSchema = z.object({
service_id: z.number(),
title: z.string(),
quantity: z.number().min(1),
rate: z.number().min(0),
description: z.string().optional(),
})
const billExpenseItemSchema = z.object({
expense_id: z.number(),
title: z.string(),
quantity: z.number().min(1),
rate: z.number().min(0),
description: z.string().optional(),
})
const billFormSchema = z.object({
vendor: relationFieldSchema,
purchase_order: relationFieldSchema,
job_card: relationFieldSchema,
payment_term: relationFieldSchema,
department: relationFieldSchema,
title: z.string().min(1, "Title is required"),
bill_number: z.string().optional(),
bill_date: z.string().optional(),
bill_due_date: z.string().optional(),
status: z.string().optional(),
notes: z.string().optional(),
part_items: z.array(billPartItemSchema).optional(),
service_items: z.array(billServiceItemSchema).optional(),
expense_items: z.array(billExpenseItemSchema).optional(),
})
type BillFormValues = z.infer<typeof billFormSchema>
type BillPartItem = z.infer<typeof billPartItemSchema>
type BillServiceItem = z.infer<typeof billServiceItemSchema>
type BillExpenseItem = z.infer<typeof billExpenseItemSchema>
export { billFormSchema, relationFieldSchema }
export type { BillFormValues }
export { billFormSchema, relationFieldSchema, billPartItemSchema, billServiceItemSchema, billExpenseItemSchema }
export type { BillFormValues, BillPartItem, BillServiceItem, BillExpenseItem }