fix invoice informations
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
import { useState } from "react"
|
||||
import { FileText } from "lucide-react"
|
||||
import { useQuery } from "@tanstack/react-query"
|
||||
import { Button } from "@/shared/components/ui/button"
|
||||
import {
|
||||
Dialog,
|
||||
@@ -12,37 +13,107 @@ import {
|
||||
import { ScrollArea } from "@/shared/components/ui/scroll-area"
|
||||
import { InvoiceForm } from "@/modules/invoices/invoice-form"
|
||||
import { toRelation } from "@/shared/lib/utils"
|
||||
import { useAuthApi } from "@/shared/useApi"
|
||||
import { ESTIMATE_ROUTES } from "@garage/api"
|
||||
import { useEstimate } from "./estimate-context"
|
||||
|
||||
/**
|
||||
* Maps an Estimate data object to an InvoiceFormValues shape so that
|
||||
* useResourceForm's shallow spread correctly pre-fills all relational fields.
|
||||
*/
|
||||
function mapEstimateToInvoiceInitialData(estimate: Record<string, any>) {
|
||||
function mapEstimateToInvoiceInitialData(
|
||||
estimate: Record<string, any>,
|
||||
services: any[],
|
||||
parts: any[],
|
||||
expenseItems: any[],
|
||||
) {
|
||||
return {
|
||||
subject: estimate.title ?? "",
|
||||
notes: estimate.footer ?? "",
|
||||
|
||||
// Relation fields — must be { value, label } objects for RhfAsyncSelectField
|
||||
customer: toRelation(estimate.customer_id, estimate.customer_name),
|
||||
vehicle: toRelation(estimate.vehicle_id, estimate.vehicle_name),
|
||||
department: toRelation(estimate.department_id, estimate.department_name),
|
||||
estimate: toRelation(estimate.id, estimate.title ?? `#${estimate.estimate_number ?? estimate.id}`),
|
||||
customer: toRelation(
|
||||
estimate.customer_id,
|
||||
estimate.customer
|
||||
? `${estimate.customer.first_name ?? ""} ${estimate.customer.last_name ?? ""}`.trim()
|
||||
: undefined
|
||||
),
|
||||
vehicle: toRelation(
|
||||
estimate.vehicle_id,
|
||||
estimate.vehicle
|
||||
? `${estimate.vehicle.make ?? ""} ${estimate.vehicle.model ?? ""} (${estimate.vehicle.registration_number ?? ""})`.trim()
|
||||
: undefined
|
||||
),
|
||||
department: toRelation(estimate.department_id, estimate.department?.name),
|
||||
|
||||
invoice_number: "",
|
||||
invoice_date: estimate.date ?? "",
|
||||
due_date: "",
|
||||
status: "draft" as const,
|
||||
|
||||
services: services.map((s: any) => ({
|
||||
service_id: s.service_id ?? s.id,
|
||||
title: s.service.labor_name ?? s.title ?? "",
|
||||
quantity: Number(s.quantity) || 1,
|
||||
rate: Number(s.rate) || 0,
|
||||
description: s.description ?? "",
|
||||
})),
|
||||
parts: parts.map((p: any) => ({
|
||||
part_id: p.part_id ?? p.id,
|
||||
title: p.part.title ?? "",
|
||||
quantity: Number(p.quantity) || 1,
|
||||
rate: Number(p.rate) || 0,
|
||||
description: p.description ?? "",
|
||||
})),
|
||||
expense_items: expenseItems.map((e: any) => ({
|
||||
expense_id: e.expense_item_id ?? e.id,
|
||||
title: e.expense_item.item_name ?? e.title ?? "",
|
||||
quantity: Number(e.quantity) || 1,
|
||||
rate: Number(e.rate) || 0,
|
||||
description: e.description ?? "",
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export function CreateInvoiceFromEstimateButton() {
|
||||
const [open, setOpen] = useState(false)
|
||||
const estimateContext = useEstimate()
|
||||
const api = useAuthApi()
|
||||
|
||||
const estimateId = estimateContext?.id ?? ""
|
||||
|
||||
const { data: servicesData } = useQuery({
|
||||
queryKey: [ESTIMATE_ROUTES.SERVICES, estimateId, "for-invoice"],
|
||||
queryFn: async () => {
|
||||
const res = await api.estimates.listServices(estimateId)
|
||||
return ((res as any)?.data ?? []) as any[]
|
||||
},
|
||||
enabled: open && !!estimateId,
|
||||
})
|
||||
|
||||
const { data: partsData } = useQuery({
|
||||
queryKey: [ESTIMATE_ROUTES.PARTS, estimateId, "for-invoice"],
|
||||
queryFn: async () => {
|
||||
const res = await api.estimates.listParts(estimateId)
|
||||
return ((res as any)?.data ?? []) as any[]
|
||||
},
|
||||
enabled: open && !!estimateId,
|
||||
})
|
||||
|
||||
const { data: expenseItemsData } = useQuery({
|
||||
queryKey: [ESTIMATE_ROUTES.EXPENSE_ITEMS, estimateId, "for-invoice"],
|
||||
queryFn: async () => {
|
||||
const res = await api.estimates.listExpenseItems(estimateId)
|
||||
return ((res as any)?.data ?? []) as any[]
|
||||
},
|
||||
enabled: open && !!estimateId,
|
||||
})
|
||||
|
||||
if (!estimateContext) return null
|
||||
|
||||
const initialData = estimateContext.data
|
||||
? mapEstimateToInvoiceInitialData(estimateContext.data)
|
||||
? mapEstimateToInvoiceInitialData(
|
||||
estimateContext.data,
|
||||
servicesData ?? [],
|
||||
partsData ?? [],
|
||||
expenseItemsData ?? [],
|
||||
)
|
||||
: undefined
|
||||
|
||||
return (
|
||||
|
||||
@@ -37,6 +37,7 @@ type ExpenseLine = {
|
||||
quantity: number
|
||||
rate: number | string
|
||||
description?: string
|
||||
expense_item: any
|
||||
}
|
||||
|
||||
type SelectedExpenseItem = {
|
||||
@@ -92,7 +93,7 @@ export function EstimateExpenseItemsSection({ estimateId }: { estimateId: string
|
||||
}
|
||||
|
||||
const getDisplayName = (item: ExpenseLine) =>
|
||||
item.item_name ?? item.title ?? `Item #${item.expense_item_id ?? item.id}`
|
||||
item.expense_item.item_name ?? item.expense_item.title ?? `Item #${item.expense_item_id ?? item.id}`
|
||||
|
||||
return (
|
||||
<Card>
|
||||
@@ -112,7 +113,7 @@ export function EstimateExpenseItemsSection({ estimateId }: { estimateId: string
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Item</TableHead>
|
||||
<TableHead className="w-100">Item</TableHead>
|
||||
<TableHead className="w-24">Qty</TableHead>
|
||||
<TableHead className="w-28">Rate</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
CardTitle,
|
||||
} from "@/shared/components/ui/card"
|
||||
import { Badge } from "@/shared/components/ui/badge"
|
||||
import { formatDate } from "@/shared/utils/formatters"
|
||||
|
||||
type EstimateData = {
|
||||
id?: number
|
||||
@@ -43,10 +44,13 @@ type EstimateData = {
|
||||
remark?: string
|
||||
created_at?: string
|
||||
}[]
|
||||
// Joined fields that may come from the API
|
||||
customer_name?: string
|
||||
vehicle_name?: string
|
||||
department_name?: string
|
||||
// Nested relation objects from the API
|
||||
customer?: { id?: number; first_name?: string; last_name?: string }
|
||||
vehicle?: { id?: number; registration_number?: string; make?: string; model?: string }
|
||||
department?: { id?: number; name?: string }
|
||||
insurance_type?: { id?: number; title?: string }
|
||||
insurer?: { id?: number; first_name?: string; last_name?: string }
|
||||
service_writer?: { id?: number; first_name?: string; last_name?: string }
|
||||
}
|
||||
|
||||
type EstimateGeneralInfoProps = {
|
||||
@@ -92,7 +96,7 @@ export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
||||
<InfoItem icon={Hash} label="Estimate #" value={estimate.estimate_number} />
|
||||
</div>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<InfoItem icon={Calendar} label="Date" value={estimate.date} />
|
||||
<InfoItem icon={Calendar} label="Date" value={formatDate(estimate.date)} />
|
||||
<InfoItem
|
||||
icon={Shield}
|
||||
label="Insurance"
|
||||
@@ -121,17 +125,39 @@ export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
||||
<InfoItem
|
||||
icon={User}
|
||||
label="Customer"
|
||||
value={estimate.customer_name || (estimate.customer_id ? `#${estimate.customer_id}` : null)}
|
||||
value={
|
||||
estimate.customer
|
||||
? `${estimate.customer.first_name ?? ""} ${estimate.customer.last_name ?? ""}`.trim()
|
||||
: estimate.customer_id ? `#${estimate.customer_id}` : null
|
||||
}
|
||||
/>
|
||||
<InfoItem
|
||||
icon={Car}
|
||||
label="Vehicle"
|
||||
value={estimate.vehicle_name || (estimate.vehicle_id ? `#${estimate.vehicle_id}` : null)}
|
||||
value={
|
||||
estimate.vehicle
|
||||
? `${estimate.vehicle.make ?? ""} ${estimate.vehicle.model ?? ""} (${estimate.vehicle.registration_number ?? ""})`.trim()
|
||||
: estimate.vehicle_id ? `#${estimate.vehicle_id}` : null
|
||||
}
|
||||
/>
|
||||
<InfoItem
|
||||
icon={Building2}
|
||||
label="Department"
|
||||
value={estimate.department_name || (estimate.department_id ? `#${estimate.department_id}` : null)}
|
||||
value={estimate.department?.name ?? (estimate.department_id ? `#${estimate.department_id}` : null)}
|
||||
/>
|
||||
<InfoItem
|
||||
icon={Shield}
|
||||
label="Insurance Type"
|
||||
value={estimate.insurance_type?.title ?? null}
|
||||
/>
|
||||
<InfoItem
|
||||
icon={User}
|
||||
label="Service Writer"
|
||||
value={
|
||||
estimate.service_writer
|
||||
? `${estimate.service_writer.first_name ?? ""} ${estimate.service_writer.last_name ?? ""}`.trim()
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -36,6 +36,7 @@ type PartLine = {
|
||||
quantity: number
|
||||
rate: number | string
|
||||
description?: string
|
||||
part:any
|
||||
}
|
||||
|
||||
type SelectedPart = {
|
||||
@@ -91,7 +92,7 @@ export function EstimatePartsSection({ estimateId }: { estimateId: string }) {
|
||||
}
|
||||
|
||||
const getDisplayName = (item: PartLine) =>
|
||||
item.title ?? `Part #${item.part_id ?? item.id}`
|
||||
item.part.title ?? `Part #${item.part_id ?? item.id}`
|
||||
|
||||
return (
|
||||
<Card>
|
||||
@@ -111,7 +112,7 @@ export function EstimatePartsSection({ estimateId }: { estimateId: string }) {
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Part</TableHead>
|
||||
<TableHead className="w-100">Part</TableHead>
|
||||
<TableHead className="w-24">Qty</TableHead>
|
||||
<TableHead className="w-28">Rate</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
|
||||
@@ -37,6 +37,7 @@ type ServiceLine = {
|
||||
quantity: number
|
||||
rate: number | string
|
||||
description?: string
|
||||
service:any
|
||||
}
|
||||
|
||||
type SelectedService = {
|
||||
@@ -92,7 +93,7 @@ export function EstimateServicesSection({ estimateId }: { estimateId: string })
|
||||
}
|
||||
|
||||
const getDisplayName = (item: ServiceLine) =>
|
||||
item.labor_name ?? item.title ?? `Service #${item.service_id ?? item.id}`
|
||||
item.service.labor_name ?? item.service.title ?? `Service #${item.service_id ?? item.id}`
|
||||
|
||||
return (
|
||||
<Card>
|
||||
@@ -112,7 +113,7 @@ export function EstimateServicesSection({ estimateId }: { estimateId: string })
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>Service</TableHead>
|
||||
<TableHead className="w-100">Service</TableHead>
|
||||
<TableHead className="w-24">Qty</TableHead>
|
||||
<TableHead className="w-28">Rate</TableHead>
|
||||
<TableHead>Description</TableHead>
|
||||
|
||||
Reference in New Issue
Block a user