fix invoice informations
This commit is contained in:
parent
973149e974
commit
f17dd1486c
@ -1,5 +1,8 @@
|
|||||||
import { getServerApi } from '@garage/api/server'
|
import { getServerApi } from '@garage/api/server'
|
||||||
import { InvoiceGeneralInfo } from '@/modules/invoices/invoice-general-info'
|
import { InvoiceGeneralInfo } from '@/modules/invoices/invoice-general-info'
|
||||||
|
import { InvoicePartsSection } from '@/modules/invoices/invoice-parts-section'
|
||||||
|
import { InvoiceServicesSection } from '@/modules/invoices/invoice-services-section'
|
||||||
|
import { InvoiceExpensesSection } from '@/modules/invoices/invoice-expenses-section'
|
||||||
import DashboardPage from '@/base/components/layout/dashboard/dashboard-page'
|
import DashboardPage from '@/base/components/layout/dashboard/dashboard-page'
|
||||||
|
|
||||||
export default async function InvoiceDetailPage(props: { params: Promise<{ id: string }> }) {
|
export default async function InvoiceDetailPage(props: { params: Promise<{ id: string }> }) {
|
||||||
@ -14,7 +17,12 @@ export default async function InvoiceDetailPage(props: { params: Promise<{ id: s
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DashboardPage header={null}>
|
<DashboardPage header={null}>
|
||||||
|
<div className="grid gap-6">
|
||||||
<InvoiceGeneralInfo invoice={data} />
|
<InvoiceGeneralInfo invoice={data} />
|
||||||
|
<InvoicePartsSection parts={data.invoice_parts} />
|
||||||
|
<InvoiceServicesSection services={data.invoice_services} />
|
||||||
|
<InvoiceExpensesSection expenses={data.invoice_expenses} />
|
||||||
|
</div>
|
||||||
</DashboardPage>
|
</DashboardPage>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { FileText } from "lucide-react"
|
import { FileText } from "lucide-react"
|
||||||
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { Button } from "@/shared/components/ui/button"
|
import { Button } from "@/shared/components/ui/button"
|
||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -12,37 +13,107 @@ import {
|
|||||||
import { ScrollArea } from "@/shared/components/ui/scroll-area"
|
import { ScrollArea } from "@/shared/components/ui/scroll-area"
|
||||||
import { InvoiceForm } from "@/modules/invoices/invoice-form"
|
import { InvoiceForm } from "@/modules/invoices/invoice-form"
|
||||||
import { toRelation } from "@/shared/lib/utils"
|
import { toRelation } from "@/shared/lib/utils"
|
||||||
|
import { useAuthApi } from "@/shared/useApi"
|
||||||
|
import { ESTIMATE_ROUTES } from "@garage/api"
|
||||||
import { useEstimate } from "./estimate-context"
|
import { useEstimate } from "./estimate-context"
|
||||||
|
|
||||||
/**
|
function mapEstimateToInvoiceInitialData(
|
||||||
* Maps an Estimate data object to an InvoiceFormValues shape so that
|
estimate: Record<string, any>,
|
||||||
* useResourceForm's shallow spread correctly pre-fills all relational fields.
|
services: any[],
|
||||||
*/
|
parts: any[],
|
||||||
function mapEstimateToInvoiceInitialData(estimate: Record<string, any>) {
|
expenseItems: any[],
|
||||||
|
) {
|
||||||
return {
|
return {
|
||||||
subject: estimate.title ?? "",
|
subject: estimate.title ?? "",
|
||||||
notes: estimate.footer ?? "",
|
notes: estimate.footer ?? "",
|
||||||
|
|
||||||
// Relation fields — must be { value, label } objects for RhfAsyncSelectField
|
estimate: toRelation(estimate.id, estimate.title ?? `#${estimate.estimate_number ?? estimate.id}`),
|
||||||
customer: toRelation(estimate.customer_id, estimate.customer_name),
|
customer: toRelation(
|
||||||
vehicle: toRelation(estimate.vehicle_id, estimate.vehicle_name),
|
estimate.customer_id,
|
||||||
department: toRelation(estimate.department_id, estimate.department_name),
|
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_number: "",
|
||||||
invoice_date: estimate.date ?? "",
|
invoice_date: estimate.date ?? "",
|
||||||
due_date: "",
|
due_date: "",
|
||||||
status: "draft" as const,
|
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() {
|
export function CreateInvoiceFromEstimateButton() {
|
||||||
const [open, setOpen] = useState(false)
|
const [open, setOpen] = useState(false)
|
||||||
const estimateContext = useEstimate()
|
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
|
if (!estimateContext) return null
|
||||||
|
|
||||||
const initialData = estimateContext.data
|
const initialData = estimateContext.data
|
||||||
? mapEstimateToInvoiceInitialData(estimateContext.data)
|
? mapEstimateToInvoiceInitialData(
|
||||||
|
estimateContext.data,
|
||||||
|
servicesData ?? [],
|
||||||
|
partsData ?? [],
|
||||||
|
expenseItemsData ?? [],
|
||||||
|
)
|
||||||
: undefined
|
: undefined
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -37,6 +37,7 @@ type ExpenseLine = {
|
|||||||
quantity: number
|
quantity: number
|
||||||
rate: number | string
|
rate: number | string
|
||||||
description?: string
|
description?: string
|
||||||
|
expense_item: any
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectedExpenseItem = {
|
type SelectedExpenseItem = {
|
||||||
@ -92,7 +93,7 @@ export function EstimateExpenseItemsSection({ estimateId }: { estimateId: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getDisplayName = (item: ExpenseLine) =>
|
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 (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@ -112,7 +113,7 @@ export function EstimateExpenseItemsSection({ estimateId }: { estimateId: string
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Item</TableHead>
|
<TableHead className="w-100">Item</TableHead>
|
||||||
<TableHead className="w-24">Qty</TableHead>
|
<TableHead className="w-24">Qty</TableHead>
|
||||||
<TableHead className="w-28">Rate</TableHead>
|
<TableHead className="w-28">Rate</TableHead>
|
||||||
<TableHead>Description</TableHead>
|
<TableHead>Description</TableHead>
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import {
|
|||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/shared/components/ui/card"
|
} from "@/shared/components/ui/card"
|
||||||
import { Badge } from "@/shared/components/ui/badge"
|
import { Badge } from "@/shared/components/ui/badge"
|
||||||
|
import { formatDate } from "@/shared/utils/formatters"
|
||||||
|
|
||||||
type EstimateData = {
|
type EstimateData = {
|
||||||
id?: number
|
id?: number
|
||||||
@ -43,10 +44,13 @@ type EstimateData = {
|
|||||||
remark?: string
|
remark?: string
|
||||||
created_at?: string
|
created_at?: string
|
||||||
}[]
|
}[]
|
||||||
// Joined fields that may come from the API
|
// Nested relation objects from the API
|
||||||
customer_name?: string
|
customer?: { id?: number; first_name?: string; last_name?: string }
|
||||||
vehicle_name?: string
|
vehicle?: { id?: number; registration_number?: string; make?: string; model?: string }
|
||||||
department_name?: 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 = {
|
type EstimateGeneralInfoProps = {
|
||||||
@ -92,7 +96,7 @@ export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
|||||||
<InfoItem icon={Hash} label="Estimate #" value={estimate.estimate_number} />
|
<InfoItem icon={Hash} label="Estimate #" value={estimate.estimate_number} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<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
|
<InfoItem
|
||||||
icon={Shield}
|
icon={Shield}
|
||||||
label="Insurance"
|
label="Insurance"
|
||||||
@ -121,17 +125,39 @@ export function EstimateGeneralInfo({ estimate }: EstimateGeneralInfoProps) {
|
|||||||
<InfoItem
|
<InfoItem
|
||||||
icon={User}
|
icon={User}
|
||||||
label="Customer"
|
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
|
<InfoItem
|
||||||
icon={Car}
|
icon={Car}
|
||||||
label="Vehicle"
|
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
|
<InfoItem
|
||||||
icon={Building2}
|
icon={Building2}
|
||||||
label="Department"
|
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>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -36,6 +36,7 @@ type PartLine = {
|
|||||||
quantity: number
|
quantity: number
|
||||||
rate: number | string
|
rate: number | string
|
||||||
description?: string
|
description?: string
|
||||||
|
part:any
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectedPart = {
|
type SelectedPart = {
|
||||||
@ -91,7 +92,7 @@ export function EstimatePartsSection({ estimateId }: { estimateId: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getDisplayName = (item: PartLine) =>
|
const getDisplayName = (item: PartLine) =>
|
||||||
item.title ?? `Part #${item.part_id ?? item.id}`
|
item.part.title ?? `Part #${item.part_id ?? item.id}`
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@ -111,7 +112,7 @@ export function EstimatePartsSection({ estimateId }: { estimateId: string }) {
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Part</TableHead>
|
<TableHead className="w-100">Part</TableHead>
|
||||||
<TableHead className="w-24">Qty</TableHead>
|
<TableHead className="w-24">Qty</TableHead>
|
||||||
<TableHead className="w-28">Rate</TableHead>
|
<TableHead className="w-28">Rate</TableHead>
|
||||||
<TableHead>Description</TableHead>
|
<TableHead>Description</TableHead>
|
||||||
|
|||||||
@ -37,6 +37,7 @@ type ServiceLine = {
|
|||||||
quantity: number
|
quantity: number
|
||||||
rate: number | string
|
rate: number | string
|
||||||
description?: string
|
description?: string
|
||||||
|
service:any
|
||||||
}
|
}
|
||||||
|
|
||||||
type SelectedService = {
|
type SelectedService = {
|
||||||
@ -92,7 +93,7 @@ export function EstimateServicesSection({ estimateId }: { estimateId: string })
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getDisplayName = (item: ServiceLine) =>
|
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 (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
@ -112,7 +113,7 @@ export function EstimateServicesSection({ estimateId }: { estimateId: string })
|
|||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableHead>Service</TableHead>
|
<TableHead className="w-100">Service</TableHead>
|
||||||
<TableHead className="w-24">Qty</TableHead>
|
<TableHead className="w-24">Qty</TableHead>
|
||||||
<TableHead className="w-28">Rate</TableHead>
|
<TableHead className="w-28">Rate</TableHead>
|
||||||
<TableHead>Description</TableHead>
|
<TableHead>Description</TableHead>
|
||||||
|
|||||||
103
apps/dashboard/modules/invoices/invoice-expenses-section.tsx
Normal file
103
apps/dashboard/modules/invoices/invoice-expenses-section.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Receipt } from "lucide-react"
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/shared/components/ui/card"
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/shared/components/ui/table"
|
||||||
|
import { formatCurrency, formatNumber } from "@/shared/utils/formatters"
|
||||||
|
|
||||||
|
type InvoiceExpense = {
|
||||||
|
id: number
|
||||||
|
invoice_id: number
|
||||||
|
expense_item_id: number
|
||||||
|
quantity: string | number
|
||||||
|
rate: string | number
|
||||||
|
description?: string
|
||||||
|
chart_of_account?: string
|
||||||
|
department_id?: number
|
||||||
|
created_at?: string
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvoiceExpensesSectionProps = {
|
||||||
|
expenses?: InvoiceExpense[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InvoiceExpensesSection({ expenses = [] }: InvoiceExpensesSectionProps) {
|
||||||
|
if (!expenses || expenses.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const subtotal = expenses.reduce((sum, expense) => {
|
||||||
|
const qty = parseFloat(String(expense.quantity))
|
||||||
|
const rate = parseFloat(String(expense.rate))
|
||||||
|
return sum + (qty * rate)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Receipt className="size-4" />
|
||||||
|
Expenses
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Description</TableHead>
|
||||||
|
<TableHead className="text-right">Quantity</TableHead>
|
||||||
|
<TableHead className="text-right">Rate</TableHead>
|
||||||
|
<TableHead className="text-right">Amount</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{expenses.map((expense) => {
|
||||||
|
const qty = parseFloat(String(expense.quantity))
|
||||||
|
const rate = parseFloat(String(expense.rate))
|
||||||
|
const amount = qty * rate
|
||||||
|
return (
|
||||||
|
<TableRow key={expense.id}>
|
||||||
|
<TableCell className="max-w-xs truncate">
|
||||||
|
{expense.description || `Expense #${expense.expense_item_id}`}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatNumber(qty)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatCurrency(rate)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right font-medium">
|
||||||
|
{formatCurrency(amount)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<TableRow className="bg-muted/50 font-medium">
|
||||||
|
<TableCell colSpan={3} className="text-right">
|
||||||
|
Subtotal
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatCurrency(subtotal)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -12,20 +12,36 @@ import {
|
|||||||
RhfTextareaField,
|
RhfTextareaField,
|
||||||
RhfAsyncSelectField,
|
RhfAsyncSelectField,
|
||||||
RhfAutoGenerateField,
|
RhfAutoGenerateField,
|
||||||
|
RhfCheckboxField,
|
||||||
|
RhfDateField,
|
||||||
} from "@/shared/components/form"
|
} from "@/shared/components/form"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import { useAuthApi } from "@/shared/useApi"
|
import { useAuthApi } from "@/shared/useApi"
|
||||||
import { useResourceForm } from "@/shared/hooks/use-resource-form"
|
import { useResourceForm } from "@/shared/hooks/use-resource-form"
|
||||||
import { useFormMutation } from "@/shared/hooks/use-form-mutation"
|
import { useFormMutation } from "@/shared/hooks/use-form-mutation"
|
||||||
import { toRelation, toId } from "@/shared/lib/utils"
|
import { toRelation, toId } from "@/shared/lib/utils"
|
||||||
|
import { useFormContext } from "react-hook-form"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
invoiceFormSchema,
|
invoiceFormSchema,
|
||||||
type InvoiceFormValues,
|
type InvoiceFormValues,
|
||||||
} from "./invoice.schema"
|
} from "./invoice.schema"
|
||||||
import { INVOICE_ROUTES, DEPARTMENT_ROUTES, InvoiceStatus } from "@garage/api"
|
import {
|
||||||
|
INVOICE_ROUTES,
|
||||||
|
DEPARTMENT_ROUTES,
|
||||||
|
ESTIMATE_ROUTES,
|
||||||
|
PAYMENT_TERM_ROUTES,
|
||||||
|
INVOICE_SEQUENCE_ROUTES,
|
||||||
|
PAYMENT_MODE_ROUTES,
|
||||||
|
CUSTOMER_ROUTES,
|
||||||
|
InvoiceStatus,
|
||||||
|
InvoiceDiscount,
|
||||||
|
} from "@garage/api"
|
||||||
import { RhfCustomerSelectField } from "@/modules/customers/rhf-customer-select-field"
|
import { RhfCustomerSelectField } from "@/modules/customers/rhf-customer-select-field"
|
||||||
import { RhfVehicleSelectField } from "@/modules/vehicles/rhf-vehicle-select-field"
|
import { RhfVehicleSelectField } from "@/modules/vehicles/rhf-vehicle-select-field"
|
||||||
|
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"
|
||||||
|
|
||||||
// ── Constants ──
|
// ── Constants ──
|
||||||
|
|
||||||
@ -34,6 +50,11 @@ const STATUS_OPTIONS = InvoiceStatus.map((v) => ({
|
|||||||
label: v.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
label: v.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const DISCOUNT_OPTIONS = InvoiceDiscount.map((v) => ({
|
||||||
|
value: v,
|
||||||
|
label: v.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
|
||||||
|
}))
|
||||||
|
|
||||||
// ── Props ──
|
// ── Props ──
|
||||||
|
|
||||||
export type InvoiceFormProps = {
|
export type InvoiceFormProps = {
|
||||||
@ -49,11 +70,26 @@ const DEFAULT_VALUES: InvoiceFormValues = {
|
|||||||
customer: null,
|
customer: null,
|
||||||
vehicle: null,
|
vehicle: null,
|
||||||
department: null,
|
department: null,
|
||||||
|
estimate: null,
|
||||||
|
payment_terms: null,
|
||||||
|
invoice_sequence: null,
|
||||||
|
payment_mode: null,
|
||||||
|
insurer: null,
|
||||||
|
invoice_to: null,
|
||||||
invoice_number: "",
|
invoice_number: "",
|
||||||
|
invoice_title: "",
|
||||||
invoice_date: "",
|
invoice_date: "",
|
||||||
due_date: "",
|
due_date: "",
|
||||||
status: "draft",
|
status: "draft",
|
||||||
|
kms_in: undefined,
|
||||||
|
has_insurance: false,
|
||||||
|
discount: "no",
|
||||||
|
deposit_to: "",
|
||||||
notes: "",
|
notes: "",
|
||||||
|
terms_and_conditions: "",
|
||||||
|
parts: [],
|
||||||
|
services: [],
|
||||||
|
expense_items: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Mapping helpers ──
|
// ── Mapping helpers ──
|
||||||
@ -66,11 +102,44 @@ function mapToFormValues(data: unknown): InvoiceFormValues {
|
|||||||
customer: toRelation(d.customer_id, d.customer_name),
|
customer: toRelation(d.customer_id, d.customer_name),
|
||||||
vehicle: toRelation(d.vehicle_id, d.vehicle_name),
|
vehicle: toRelation(d.vehicle_id, d.vehicle_name),
|
||||||
department: toRelation(d.department_id, d.department_name),
|
department: toRelation(d.department_id, d.department_name),
|
||||||
|
estimate: toRelation(d.estimate_id, d.estimate_number ?? d.estimate_title),
|
||||||
|
payment_terms: toRelation(d.payment_terms_id, d.payment_terms_name),
|
||||||
|
invoice_sequence: toRelation(d.invoice_sequence_id, d.invoice_sequence_title ?? d.invoice_sequence?.title),
|
||||||
|
payment_mode: toRelation(d.payment_mode_id, d.payment_mode_name),
|
||||||
|
insurer: toRelation(d.insurer_id, d.insurer_name),
|
||||||
|
invoice_to: toRelation(d.invoice_to_id, d.invoice_to_name),
|
||||||
invoice_number: d.invoice_number || "",
|
invoice_number: d.invoice_number || "",
|
||||||
invoice_date: d.invoice_date || "",
|
invoice_title: d.invoice_title || "",
|
||||||
due_date: d.due_date || "",
|
invoice_date: d.invoice_date ? d.invoice_date.split("T")[0] : "",
|
||||||
|
due_date: d.due_date ? d.due_date.split("T")[0] : "",
|
||||||
status: d.status || "draft",
|
status: d.status || "draft",
|
||||||
|
kms_in: d.kms_in ? Number(d.kms_in) : undefined,
|
||||||
|
has_insurance: d.has_insurance ?? false,
|
||||||
|
discount: d.discount || "no",
|
||||||
|
deposit_to: d.deposit_to || "",
|
||||||
notes: d.notes || "",
|
notes: d.notes || "",
|
||||||
|
terms_and_conditions: d.terms_and_conditions || "",
|
||||||
|
parts: (d.invoice_parts ?? 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 ?? "",
|
||||||
|
})),
|
||||||
|
services: (d.invoice_services ?? 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.invoice_expenses ?? 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 ?? "",
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,11 +149,41 @@ function mapFormToPayload(values: InvoiceFormValues) {
|
|||||||
customer_id: toId(values.customer),
|
customer_id: toId(values.customer),
|
||||||
vehicle_id: toId(values.vehicle),
|
vehicle_id: toId(values.vehicle),
|
||||||
department_id: toId(values.department),
|
department_id: toId(values.department),
|
||||||
|
estimate_id: toId(values.estimate),
|
||||||
|
payment_terms_id: toId(values.payment_terms),
|
||||||
|
invoice_sequence_id: toId(values.invoice_sequence),
|
||||||
|
payment_mode_id: toId(values.payment_mode),
|
||||||
|
insurer_id: toId(values.insurer),
|
||||||
|
invoice_to_id: toId(values.invoice_to),
|
||||||
invoice_number: values.invoice_number || undefined,
|
invoice_number: values.invoice_number || undefined,
|
||||||
|
invoice_title: values.invoice_title || undefined,
|
||||||
invoice_date: values.invoice_date || undefined,
|
invoice_date: values.invoice_date || undefined,
|
||||||
due_date: values.due_date || undefined,
|
due_date: values.due_date || undefined,
|
||||||
status: values.status || undefined,
|
status: values.status || undefined,
|
||||||
|
kms_in: values.kms_in || undefined,
|
||||||
|
has_insurance: values.has_insurance,
|
||||||
|
discount: values.discount || undefined,
|
||||||
|
deposit_to: values.deposit_to || undefined,
|
||||||
notes: values.notes || undefined,
|
notes: values.notes || undefined,
|
||||||
|
terms_and_conditions: values.terms_and_conditions || undefined,
|
||||||
|
parts: (values.parts ?? []).map((item) => ({
|
||||||
|
part_id: item.part_id,
|
||||||
|
quantity: item.quantity,
|
||||||
|
rate: item.rate,
|
||||||
|
description: item.description || undefined,
|
||||||
|
})),
|
||||||
|
services: (values.services ?? []).map((item) => ({
|
||||||
|
service_id: item.service_id,
|
||||||
|
quantity: item.quantity,
|
||||||
|
rate: item.rate,
|
||||||
|
description: item.description || undefined,
|
||||||
|
})),
|
||||||
|
expenses: (values.expense_items ?? []).map((item) => ({
|
||||||
|
expense_id: item.expense_id,
|
||||||
|
quantity: item.quantity,
|
||||||
|
rate: item.rate,
|
||||||
|
description: item.description || undefined,
|
||||||
|
})),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,11 +191,28 @@ function mapFormToPayload(values: InvoiceFormValues) {
|
|||||||
|
|
||||||
const mapLookupOption = (item: any) => ({
|
const mapLookupOption = (item: any) => ({
|
||||||
value: String(item.id),
|
value: String(item.id),
|
||||||
label: item.name,
|
label: item.name ?? item.title ?? `#${item.id}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) => o.label }
|
const STORE_OBJECT = { getOptionValue: (o: any) => o, getOptionLabel: (o: any) => o.label }
|
||||||
|
|
||||||
|
// ── Insurance fields (conditionally rendered) ──
|
||||||
|
|
||||||
|
function InsurerField() {
|
||||||
|
const { watch } = useFormContext<InvoiceFormValues>()
|
||||||
|
const hasInsurance = watch("has_insurance")
|
||||||
|
|
||||||
|
if (!hasInsurance) return null
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RhfCustomerSelectField<InvoiceFormValues, "insurer">
|
||||||
|
name="insurer"
|
||||||
|
label="Insurer"
|
||||||
|
placeholder="Select insurer..."
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// ── Component ──
|
// ── Component ──
|
||||||
|
|
||||||
export function InvoiceForm({ resourceId, initialData, onSuccess }: InvoiceFormProps) {
|
export function InvoiceForm({ resourceId, initialData, onSuccess }: InvoiceFormProps) {
|
||||||
@ -146,25 +262,31 @@ export function InvoiceForm({ resourceId, initialData, onSuccess }: InvoiceFormP
|
|||||||
<RhfTextField name="subject" label="Subject" placeholder="Invoice subject" required />
|
<RhfTextField name="subject" label="Subject" placeholder="Invoice subject" required />
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<RhfAutoGenerateField table="invoices" name="invoice_number" label="Invoice Number" placeholder="INV-0001" />
|
<RhfAutoGenerateField autoFetch table="invoices" name="invoice_number" label="Invoice Number" placeholder="INV-0001" />
|
||||||
<RhfSelectField
|
<RhfTextField name="invoice_title" label="Invoice Title" placeholder="e.g. Tax Invoice" />
|
||||||
name="status"
|
|
||||||
label="Status"
|
|
||||||
placeholder="Select status"
|
|
||||||
options={STATUS_OPTIONS}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<RhfTextField name="invoice_date" label="Invoice Date" type="date" />
|
<RhfSelectField name="status" label="Status" options={STATUS_OPTIONS} />
|
||||||
<RhfTextField name="due_date" label="Due Date" type="date" />
|
<RhfSelectField name="discount" label="Discount" options={DISCOUNT_OPTIONS} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
<RhfCustomerSelectField name="customer" />
|
<RhfDateField name="invoice_date" label="Invoice Date" />
|
||||||
|
<RhfDateField name="due_date" label="Due Date" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
|
<RhfCustomerSelectField<InvoiceFormValues, "customer"> name="customer" />
|
||||||
<RhfVehicleSelectField name="vehicle" />
|
<RhfVehicleSelectField name="vehicle" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
|
<RhfCustomerSelectField<InvoiceFormValues, "invoice_to">
|
||||||
|
name="invoice_to"
|
||||||
|
label="Invoice To"
|
||||||
|
placeholder="Select billing contact..."
|
||||||
|
/>
|
||||||
<RhfAsyncSelectField
|
<RhfAsyncSelectField
|
||||||
name="department"
|
name="department"
|
||||||
label="Department"
|
label="Department"
|
||||||
@ -174,8 +296,70 @@ export function InvoiceForm({ resourceId, initialData, onSuccess }: InvoiceFormP
|
|||||||
mapOption={mapLookupOption}
|
mapOption={mapLookupOption}
|
||||||
{...STORE_OBJECT}
|
{...STORE_OBJECT}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
|
<RhfAsyncSelectField
|
||||||
|
name="estimate"
|
||||||
|
label="Estimate"
|
||||||
|
placeholder="Select estimate"
|
||||||
|
queryKey={[ESTIMATE_ROUTES.INDEX]}
|
||||||
|
listFn={() => api.estimates.list()}
|
||||||
|
mapOption={(item: any) => ({
|
||||||
|
value: String(item.id),
|
||||||
|
label: item.title||item.estimate_number || `#${item.id}`,
|
||||||
|
})}
|
||||||
|
{...STORE_OBJECT}
|
||||||
|
/>
|
||||||
|
<RhfAsyncSelectField
|
||||||
|
name="payment_terms"
|
||||||
|
label="Payment Terms"
|
||||||
|
placeholder="Select payment terms"
|
||||||
|
queryKey={[PAYMENT_TERM_ROUTES.INDEX]}
|
||||||
|
listFn={() => api.paymentTerms.list()}
|
||||||
|
mapOption={mapLookupOption}
|
||||||
|
{...STORE_OBJECT}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
|
<RhfAsyncSelectField
|
||||||
|
name="invoice_sequence"
|
||||||
|
label="Invoice Sequence"
|
||||||
|
placeholder="Select sequence"
|
||||||
|
queryKey={[INVOICE_SEQUENCE_ROUTES.INDEX]}
|
||||||
|
listFn={() => api.invoiceSequences.list()}
|
||||||
|
mapOption={(item: any) => ({
|
||||||
|
value: String(item.id),
|
||||||
|
label: item.title || `#${item.id}`,
|
||||||
|
})}
|
||||||
|
{...STORE_OBJECT}
|
||||||
|
/>
|
||||||
|
<RhfTextField name="kms_in" label="KMs In" type="number" placeholder="e.g. 50000" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2">
|
||||||
|
<RhfAsyncSelectField
|
||||||
|
name="payment_mode"
|
||||||
|
label="Payment Mode"
|
||||||
|
placeholder="Select payment mode"
|
||||||
|
queryKey={[PAYMENT_MODE_ROUTES.INDEX]}
|
||||||
|
listFn={() => api.paymentModes.list()}
|
||||||
|
mapOption={mapLookupOption}
|
||||||
|
{...STORE_OBJECT}
|
||||||
|
/>
|
||||||
|
<RhfTextField name="deposit_to" label="Deposit To" placeholder="e.g. Main Account" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<RhfCheckboxField name="has_insurance" label="Has Insurance" />
|
||||||
|
<InsurerField />
|
||||||
|
|
||||||
<RhfTextareaField name="notes" label="Notes" placeholder="Additional notes" rows={3} />
|
<RhfTextareaField name="notes" label="Notes" placeholder="Additional notes" rows={3} />
|
||||||
|
<RhfTextareaField name="terms_and_conditions" label="Terms & Conditions" rows={3} />
|
||||||
|
|
||||||
|
<PartsSelectorField<InvoiceFormValues, "parts"> name="parts" />
|
||||||
|
<ServicesSelectorField<InvoiceFormValues, "services"> name="services" />
|
||||||
|
<ExpenseItemsSelectorField<InvoiceFormValues, "expense_items"> name="expense_items" />
|
||||||
|
|
||||||
<Button type="submit" variant="default" disabled={isPending}>
|
<Button type="submit" variant="default" disabled={isPending}>
|
||||||
{isEditing ? <Save /> : <Plus />}
|
{isEditing ? <Save /> : <Plus />}
|
||||||
|
|||||||
@ -7,6 +7,9 @@ import {
|
|||||||
Building2,
|
Building2,
|
||||||
CircleDollarSign,
|
CircleDollarSign,
|
||||||
Clock,
|
Clock,
|
||||||
|
Mail,
|
||||||
|
Phone,
|
||||||
|
DollarSign,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import {
|
import {
|
||||||
Card,
|
Card,
|
||||||
@ -16,21 +19,38 @@ import {
|
|||||||
} from "@/shared/components/ui/card"
|
} from "@/shared/components/ui/card"
|
||||||
import { Badge } from "@/shared/components/ui/badge"
|
import { Badge } from "@/shared/components/ui/badge"
|
||||||
import { Separator } from "@/shared/components/ui/separator"
|
import { Separator } from "@/shared/components/ui/separator"
|
||||||
|
import { formatDate, formatCurrency, formatEnum, formatNumber } from "@/shared/utils/formatters"
|
||||||
|
|
||||||
type InvoiceData = {
|
type InvoiceData = {
|
||||||
id?: number
|
id?: number
|
||||||
subject?: string
|
subject?: string
|
||||||
invoice_number?: string
|
invoice_number?: string
|
||||||
|
invoice_title?: string
|
||||||
invoice_date?: string
|
invoice_date?: string
|
||||||
due_date?: string
|
due_date?: string
|
||||||
status?: string
|
status?: string
|
||||||
notes?: string
|
notes?: string
|
||||||
|
terms_and_conditions?: string
|
||||||
customer_name?: string
|
customer_name?: string
|
||||||
customer_id?: number
|
customer_id?: number
|
||||||
|
customer?: any
|
||||||
vehicle_name?: string
|
vehicle_name?: string
|
||||||
vehicle_id?: number
|
vehicle_id?: number
|
||||||
|
vehicle?: any
|
||||||
department_name?: string
|
department_name?: string
|
||||||
department_id?: number
|
department_id?: number
|
||||||
|
payment_terms_id?: number
|
||||||
|
payment_mode_id?: number
|
||||||
|
amount?: number | string | null
|
||||||
|
received_payment?: number | string | null
|
||||||
|
discount?: string
|
||||||
|
has_insurance?: number | boolean
|
||||||
|
insurer_id?: number | null
|
||||||
|
insurer?: any
|
||||||
|
kms_in?: number | null
|
||||||
|
invoice_to_id?: number | null
|
||||||
|
billing_address_id?: number | null
|
||||||
|
delivery_address_id?: number | null
|
||||||
created_at?: string
|
created_at?: string
|
||||||
updated_at?: string
|
updated_at?: string
|
||||||
}
|
}
|
||||||
@ -72,8 +92,12 @@ const statusColorMap: Record<string, string> = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function InvoiceGeneralInfo({ invoice }: InvoiceGeneralInfoProps) {
|
export function InvoiceGeneralInfo({ invoice }: InvoiceGeneralInfoProps) {
|
||||||
|
const customer = invoice.customer || {}
|
||||||
|
const vehicle = invoice.vehicle || {}
|
||||||
|
const insurer = invoice.insurer || {}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid gap-6 md:grid-cols-2">
|
<div className="grid gap-6">
|
||||||
{/* Invoice Details */}
|
{/* Invoice Details */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@ -89,12 +113,12 @@ export function InvoiceGeneralInfo({ invoice }: InvoiceGeneralInfoProps) {
|
|||||||
)}
|
)}
|
||||||
{invoice.status && (
|
{invoice.status && (
|
||||||
<Badge variant={statusColorMap[invoice.status] as any ?? "outline"}>
|
<Badge variant={statusColorMap[invoice.status] as any ?? "outline"}>
|
||||||
{invoice.status.charAt(0).toUpperCase() + invoice.status.slice(1)}
|
{formatEnum(invoice.status)}
|
||||||
</Badge>
|
</Badge>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-3">
|
||||||
<InfoItem
|
<InfoItem
|
||||||
icon={Hash}
|
icon={Hash}
|
||||||
label="Invoice Number"
|
label="Invoice Number"
|
||||||
@ -103,59 +127,209 @@ export function InvoiceGeneralInfo({ invoice }: InvoiceGeneralInfoProps) {
|
|||||||
<InfoItem
|
<InfoItem
|
||||||
icon={Calendar}
|
icon={Calendar}
|
||||||
label="Invoice Date"
|
label="Invoice Date"
|
||||||
value={invoice.invoice_date}
|
value={formatDate(invoice.invoice_date)}
|
||||||
/>
|
/>
|
||||||
<InfoItem
|
<InfoItem
|
||||||
icon={Calendar}
|
icon={Calendar}
|
||||||
label="Due Date"
|
label="Due Date"
|
||||||
value={invoice.due_date}
|
value={formatDate(invoice.due_date)}
|
||||||
/>
|
|
||||||
<InfoItem
|
|
||||||
icon={Clock}
|
|
||||||
label="Created"
|
|
||||||
value={invoice.created_at ? new Date(invoice.created_at).toLocaleDateString() : null}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
{/* Relations */}
|
{/* Customer & Vehicle Information */}
|
||||||
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
|
{/* Customer Details */}
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className="flex items-center gap-2">
|
<CardTitle className="flex items-center gap-2">
|
||||||
<CircleDollarSign className="size-4" />
|
<Users className="size-4" />
|
||||||
Related Information
|
Customer Information
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="grid gap-4">
|
<CardContent className="grid gap-4">
|
||||||
<div className="grid gap-4 sm:grid-cols-2">
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
<InfoItem
|
<InfoItem
|
||||||
icon={Users}
|
icon={Users}
|
||||||
label="Customer"
|
label="Customer Name"
|
||||||
value={invoice.customer_name}
|
value={customer.first_name && customer.last_name ? `${customer.first_name} ${customer.last_name}` : invoice.customer_name}
|
||||||
/>
|
/>
|
||||||
<InfoItem
|
<InfoItem
|
||||||
icon={Car}
|
icon={Mail}
|
||||||
label="Vehicle"
|
label="Email"
|
||||||
value={invoice.vehicle_name}
|
value={customer.email}
|
||||||
/>
|
/>
|
||||||
<InfoItem
|
<InfoItem
|
||||||
icon={Building2}
|
icon={Phone}
|
||||||
label="Department"
|
label="Phone"
|
||||||
value={invoice.department_name}
|
value={customer.phone}
|
||||||
|
/>
|
||||||
|
<InfoItem
|
||||||
|
icon={Phone}
|
||||||
|
label="Alternate Phone"
|
||||||
|
value={customer.alternate_phone}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{invoice.notes && (
|
{customer.address_line_1 && (
|
||||||
<>
|
<>
|
||||||
<Separator />
|
<Separator />
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<span className="text-xs text-muted-foreground">Notes</span>
|
<span className="text-xs text-muted-foreground">Address</span>
|
||||||
<p className="text-sm">{invoice.notes}</p>
|
<p className="text-sm">
|
||||||
|
{customer.address_line_1}
|
||||||
|
{customer.address_line_2 ? `, ${customer.address_line_2}` : ""}
|
||||||
|
<br />
|
||||||
|
{customer.city ? `${customer.city}` : ""}
|
||||||
|
{customer.zip_code ? `, ${customer.zip_code}` : ""}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
{/* Vehicle Details */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Car className="size-4" />
|
||||||
|
Vehicle Information
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="grid gap-4">
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
<InfoItem
|
||||||
|
icon={Car}
|
||||||
|
label="Vehicle"
|
||||||
|
value={vehicle.make && vehicle.model ? `${vehicle.make} ${vehicle.model}` : invoice.vehicle_name}
|
||||||
|
/>
|
||||||
|
<InfoItem
|
||||||
|
icon={Hash}
|
||||||
|
label="License Plate"
|
||||||
|
value={vehicle.license_plate}
|
||||||
|
/>
|
||||||
|
<InfoItem
|
||||||
|
icon={Hash}
|
||||||
|
label="VIN"
|
||||||
|
value={vehicle.vin_number}
|
||||||
|
/>
|
||||||
|
<InfoItem
|
||||||
|
icon={Hash}
|
||||||
|
label="Engine Number"
|
||||||
|
value={vehicle.engine_number}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{vehicle.mileage && (
|
||||||
|
<>
|
||||||
|
<Separator />
|
||||||
|
<InfoItem
|
||||||
|
icon={Clock}
|
||||||
|
label="Mileage"
|
||||||
|
value={formatNumber(vehicle.mileage)}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Payment & Insurance Information */}
|
||||||
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
|
{/* Payment Information */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<DollarSign className="size-4" />
|
||||||
|
Payment Information
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="grid gap-4">
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
<InfoItem
|
||||||
|
icon={CircleDollarSign}
|
||||||
|
label="Amount"
|
||||||
|
value={invoice.amount ? formatCurrency(invoice.amount) : null}
|
||||||
|
/>
|
||||||
|
<InfoItem
|
||||||
|
icon={CircleDollarSign}
|
||||||
|
label="Received Payment"
|
||||||
|
value={invoice.received_payment ? formatCurrency(invoice.received_payment) : null}
|
||||||
|
/>
|
||||||
|
<InfoItem
|
||||||
|
icon={Hash}
|
||||||
|
label="Discount"
|
||||||
|
value={invoice.discount}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Insurance & Additional Info */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<FileText className="size-4" />
|
||||||
|
Additional Information
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="grid gap-4">
|
||||||
|
<div className="grid gap-4 sm:grid-cols-2">
|
||||||
|
<InfoItem
|
||||||
|
icon={Building2}
|
||||||
|
label="Department"
|
||||||
|
value={invoice.department_name}
|
||||||
|
/>
|
||||||
|
<InfoItem
|
||||||
|
icon={Hash}
|
||||||
|
label="Has Insurance"
|
||||||
|
value={invoice.has_insurance ? "Yes" : "No"}
|
||||||
|
/>
|
||||||
|
{invoice.has_insurance && insurer.id && (
|
||||||
|
<InfoItem
|
||||||
|
icon={Users}
|
||||||
|
label="Insurer"
|
||||||
|
value={insurer.name}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{invoice.kms_in && (
|
||||||
|
<InfoItem
|
||||||
|
icon={Clock}
|
||||||
|
label="KMs In"
|
||||||
|
value={formatNumber(invoice.kms_in)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Notes & Terms */}
|
||||||
|
{(invoice.notes || invoice.terms_and_conditions) && (
|
||||||
|
<div className="grid gap-6 md:grid-cols-2">
|
||||||
|
{invoice.notes && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-base">Notes</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-sm whitespace-pre-wrap">{invoice.notes}</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
{invoice.terms_and_conditions && (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-base">Terms & Conditions</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<p className="text-sm whitespace-pre-wrap">{invoice.terms_and_conditions}</p>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
103
apps/dashboard/modules/invoices/invoice-parts-section.tsx
Normal file
103
apps/dashboard/modules/invoices/invoice-parts-section.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Wrench } from "lucide-react"
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/shared/components/ui/card"
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/shared/components/ui/table"
|
||||||
|
import { formatCurrency, formatNumber } from "@/shared/utils/formatters"
|
||||||
|
|
||||||
|
type InvoicePart = {
|
||||||
|
id: number
|
||||||
|
invoice_id: number
|
||||||
|
part_id: number
|
||||||
|
quantity: string | number
|
||||||
|
rate: string | number
|
||||||
|
description?: string
|
||||||
|
chart_of_account?: string
|
||||||
|
department_id?: number
|
||||||
|
created_at?: string
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvoicePartsSectionProps = {
|
||||||
|
parts?: InvoicePart[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InvoicePartsSection({ parts = [] }: InvoicePartsSectionProps) {
|
||||||
|
if (!parts || parts.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const subtotal = parts.reduce((sum, part) => {
|
||||||
|
const qty = parseFloat(String(part.quantity))
|
||||||
|
const rate = parseFloat(String(part.rate))
|
||||||
|
return sum + (qty * rate)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Wrench className="size-4" />
|
||||||
|
Parts
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Description</TableHead>
|
||||||
|
<TableHead className="text-right">Quantity</TableHead>
|
||||||
|
<TableHead className="text-right">Rate</TableHead>
|
||||||
|
<TableHead className="text-right">Amount</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{parts.map((part) => {
|
||||||
|
const qty = parseFloat(String(part.quantity))
|
||||||
|
const rate = parseFloat(String(part.rate))
|
||||||
|
const amount = qty * rate
|
||||||
|
return (
|
||||||
|
<TableRow key={part.id}>
|
||||||
|
<TableCell className="max-w-xs truncate">
|
||||||
|
{part.description || `Part #${part.part_id}`}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatNumber(qty)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatCurrency(rate)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right font-medium">
|
||||||
|
{formatCurrency(amount)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<TableRow className="bg-muted/50 font-medium">
|
||||||
|
<TableCell colSpan={3} className="text-right">
|
||||||
|
Subtotal
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatCurrency(subtotal)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
103
apps/dashboard/modules/invoices/invoice-services-section.tsx
Normal file
103
apps/dashboard/modules/invoices/invoice-services-section.tsx
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Briefcase } from "lucide-react"
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
CardContent,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/shared/components/ui/card"
|
||||||
|
import {
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from "@/shared/components/ui/table"
|
||||||
|
import { formatCurrency, formatNumber } from "@/shared/utils/formatters"
|
||||||
|
|
||||||
|
type InvoiceService = {
|
||||||
|
id: number
|
||||||
|
invoice_id: number
|
||||||
|
service_id: number
|
||||||
|
quantity: string | number
|
||||||
|
rate: string | number
|
||||||
|
description?: string
|
||||||
|
chart_of_account?: string
|
||||||
|
department_id?: number
|
||||||
|
created_at?: string
|
||||||
|
updated_at?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type InvoiceServicesSectionProps = {
|
||||||
|
services?: InvoiceService[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function InvoiceServicesSection({ services = [] }: InvoiceServicesSectionProps) {
|
||||||
|
if (!services || services.length === 0) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
const subtotal = services.reduce((sum, service) => {
|
||||||
|
const qty = parseFloat(String(service.quantity))
|
||||||
|
const rate = parseFloat(String(service.rate))
|
||||||
|
return sum + (qty * rate)
|
||||||
|
}, 0)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Briefcase className="size-4" />
|
||||||
|
Services
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Description</TableHead>
|
||||||
|
<TableHead className="text-right">Quantity</TableHead>
|
||||||
|
<TableHead className="text-right">Rate</TableHead>
|
||||||
|
<TableHead className="text-right">Amount</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{services.map((service) => {
|
||||||
|
const qty = parseFloat(String(service.quantity))
|
||||||
|
const rate = parseFloat(String(service.rate))
|
||||||
|
const amount = qty * rate
|
||||||
|
return (
|
||||||
|
<TableRow key={service.id}>
|
||||||
|
<TableCell className="max-w-xs truncate">
|
||||||
|
{service.description || `Service #${service.service_id}`}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatNumber(qty)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatCurrency(rate)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right font-medium">
|
||||||
|
{formatCurrency(amount)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
<TableRow className="bg-muted/50 font-medium">
|
||||||
|
<TableCell colSpan={3} className="text-right">
|
||||||
|
Subtotal
|
||||||
|
</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatCurrency(subtotal)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -4,6 +4,30 @@ const relationFieldSchema = z
|
|||||||
.object({ value: z.string(), label: z.string() })
|
.object({ value: z.string(), label: z.string() })
|
||||||
.nullable()
|
.nullable()
|
||||||
|
|
||||||
|
const invoicePartItemSchema = z.object({
|
||||||
|
part_id: z.number(),
|
||||||
|
title: z.string(),
|
||||||
|
quantity: z.number().min(1),
|
||||||
|
rate: z.number().min(0),
|
||||||
|
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),
|
||||||
|
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),
|
||||||
|
description: z.string().optional(),
|
||||||
|
})
|
||||||
|
|
||||||
const invoiceFormSchema = z.object({
|
const invoiceFormSchema = z.object({
|
||||||
// ── Required fields ──
|
// ── Required fields ──
|
||||||
subject: z.string().min(1, "Subject is required"),
|
subject: z.string().min(1, "Subject is required"),
|
||||||
@ -12,16 +36,36 @@ const invoiceFormSchema = z.object({
|
|||||||
customer: relationFieldSchema,
|
customer: relationFieldSchema,
|
||||||
vehicle: relationFieldSchema,
|
vehicle: relationFieldSchema,
|
||||||
department: relationFieldSchema,
|
department: relationFieldSchema,
|
||||||
|
estimate: relationFieldSchema,
|
||||||
|
payment_terms: relationFieldSchema,
|
||||||
|
invoice_sequence: relationFieldSchema,
|
||||||
|
payment_mode: relationFieldSchema,
|
||||||
|
insurer: relationFieldSchema,
|
||||||
|
invoice_to: relationFieldSchema,
|
||||||
|
|
||||||
// ── Optional fields ──
|
// ── Optional fields ──
|
||||||
invoice_number: z.string().optional(),
|
invoice_number: z.string().optional(),
|
||||||
|
invoice_title: z.string().optional(),
|
||||||
invoice_date: z.string().optional(),
|
invoice_date: z.string().optional(),
|
||||||
due_date: z.string().optional(),
|
due_date: z.string().optional(),
|
||||||
status: z.string().optional(),
|
status: z.string().optional(),
|
||||||
|
kms_in: z.coerce.number().optional(),
|
||||||
|
has_insurance: z.boolean().default(false),
|
||||||
|
discount: z.string().optional(),
|
||||||
|
deposit_to: z.string().optional(),
|
||||||
notes: 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(),
|
||||||
})
|
})
|
||||||
|
|
||||||
type InvoiceFormValues = z.infer<typeof invoiceFormSchema>
|
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 }
|
export { invoiceFormSchema, relationFieldSchema, invoicePartItemSchema, invoiceServiceItemSchema, invoiceExpenseItemSchema }
|
||||||
export type { InvoiceFormValues }
|
export type { InvoiceFormValues, InvoicePartItem, InvoiceServiceItem, InvoiceExpenseItem }
|
||||||
|
|||||||
@ -462,8 +462,8 @@
|
|||||||
],
|
],
|
||||||
"summary": "GET /api/profile",
|
"summary": "GET /api/profile",
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"201": {
|
||||||
"description": "OK",
|
"description": "Created",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
@ -21668,6 +21668,290 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"/api/estimates/{id}": {
|
"/api/estimates/{id}": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"Estimates"
|
||||||
|
],
|
||||||
|
"summary": "Display the specified estimate.",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "id",
|
||||||
|
"in": "path",
|
||||||
|
"required": true,
|
||||||
|
"schema": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "OK",
|
||||||
|
"content": {
|
||||||
|
"application/json": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"data": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"customer_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"vehicle_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"department_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"estimate_number": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"has_insurance": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"enable_digital_authorisation": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"insurance_type_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"insurer_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"service_writer_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"labels": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"color_code": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"pivot": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"estimate_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"label_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"customer_remarks": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"estimate_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"remark": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"created_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"updated_at": {
|
||||||
|
"type": "string",
|
||||||
|
"format": "date-time"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"customer": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vehicle": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"registration_number": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"make": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"model": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"department": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"insurance_type": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"insurer": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"service_writer": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"first_name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"last_name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"example": {
|
||||||
|
"data": {
|
||||||
|
"id": 1,
|
||||||
|
"title": "Estimate for Toyota Camry",
|
||||||
|
"customer_id": 1,
|
||||||
|
"vehicle_id": 1,
|
||||||
|
"department_id": 1,
|
||||||
|
"estimate_number": "EST-001",
|
||||||
|
"date": "2026-03-31",
|
||||||
|
"has_insurance": false,
|
||||||
|
"enable_digital_authorisation": false,
|
||||||
|
"insurance_type_id": 1,
|
||||||
|
"insurer_id": 2,
|
||||||
|
"service_writer_id": 1,
|
||||||
|
"footer": "Thank you for your business.",
|
||||||
|
"created_at": "2026-03-31T10:00:00.000000Z",
|
||||||
|
"updated_at": "2026-03-31T10:00:00.000000Z",
|
||||||
|
"labels": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"title": "Urgent",
|
||||||
|
"color_code": "#FF0000",
|
||||||
|
"pivot": {
|
||||||
|
"estimate_id": 1,
|
||||||
|
"label_id": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"customer_remarks": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"estimate_id": 1,
|
||||||
|
"remark": "Oil change recommended.",
|
||||||
|
"created_at": "2026-03-31T10:00:00.000000Z",
|
||||||
|
"updated_at": "2026-03-31T10:00:00.000000Z"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"customer": {
|
||||||
|
"id": 1,
|
||||||
|
"first_name": "John",
|
||||||
|
"last_name": "Doe"
|
||||||
|
},
|
||||||
|
"vehicle": {
|
||||||
|
"id": 1,
|
||||||
|
"registration_number": "ABC-1234",
|
||||||
|
"make": "Toyota",
|
||||||
|
"model": "Camry"
|
||||||
|
},
|
||||||
|
"department": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Service"
|
||||||
|
},
|
||||||
|
"insurance_type": {
|
||||||
|
"id": 1,
|
||||||
|
"title": "Comprehensive"
|
||||||
|
},
|
||||||
|
"insurer": {
|
||||||
|
"id": 2,
|
||||||
|
"first_name": "Alex",
|
||||||
|
"last_name": "Insurer"
|
||||||
|
},
|
||||||
|
"service_writer": {
|
||||||
|
"id": 1,
|
||||||
|
"first_name": "Sam",
|
||||||
|
"last_name": "Writer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"put": {
|
"put": {
|
||||||
"tags": [
|
"tags": [
|
||||||
"Estimates"
|
"Estimates"
|
||||||
@ -42973,6 +43257,24 @@
|
|||||||
"estimate_id": {
|
"estimate_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
|
"kms_in": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"has_insurance": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"insurer_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"invoice_to_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"billing_address_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"delivery_address_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"invoice_date": {
|
"invoice_date": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
@ -42988,17 +43290,195 @@
|
|||||||
"invoice_number": {
|
"invoice_number": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"invoice_title": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"department_id": {
|
"department_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"notes": {
|
"notes": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"terms_and_conditions": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
"status": {
|
"status": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
|
"received_payment": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"payment_mode_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"deposit_to": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"amount": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
"discount": {
|
"discount": {
|
||||||
"type": "string"
|
"type": "string"
|
||||||
|
},
|
||||||
|
"inspection_categories": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"inspection_category_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"labor_rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"working_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"labor_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chart_of_account": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"department_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"parts": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"part_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chart_of_account": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"department_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"expenses": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"expense_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chart_of_account": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"department_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"services": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"service_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"labor_rate_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"working_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"labor_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chart_of_account": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"department_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"service_groups": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"service_group_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"labor_rate_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"working_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"labor_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chart_of_account": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"department_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -43007,22 +43487,94 @@
|
|||||||
"customer_id": 1,
|
"customer_id": 1,
|
||||||
"vehicle_id": 1,
|
"vehicle_id": 1,
|
||||||
"estimate_id": 1,
|
"estimate_id": 1,
|
||||||
|
"kms_in": 50000,
|
||||||
|
"has_insurance": false,
|
||||||
|
"insurer_id": 2,
|
||||||
|
"invoice_to_id": 1,
|
||||||
|
"billing_address_id": 10,
|
||||||
|
"delivery_address_id": 11,
|
||||||
"invoice_date": "2026-03-31",
|
"invoice_date": "2026-03-31",
|
||||||
"due_date": "2026-04-14",
|
"due_date": "2026-04-14",
|
||||||
"payment_terms_id": 1,
|
"payment_terms_id": 1,
|
||||||
"invoice_sequence_id": 1,
|
"invoice_sequence_id": 1,
|
||||||
"invoice_number": "INV-001",
|
"invoice_number": "INV-001",
|
||||||
|
"invoice_title": "Tax Invoice",
|
||||||
"department_id": 1,
|
"department_id": 1,
|
||||||
"notes": "string",
|
"notes": "Vehicle service and parts",
|
||||||
|
"terms_and_conditions": "Payment due in 14 days.",
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"discount": "no"
|
"received_payment": false,
|
||||||
|
"payment_mode_id": 1,
|
||||||
|
"deposit_to": "Main Account",
|
||||||
|
"amount": 2500,
|
||||||
|
"discount": "no",
|
||||||
|
"inspection_categories": [
|
||||||
|
{
|
||||||
|
"inspection_category_id": 1,
|
||||||
|
"rate_type": "flat_rate",
|
||||||
|
"labor_rate": 500,
|
||||||
|
"working_hours": 1,
|
||||||
|
"labor_hours": 1,
|
||||||
|
"rate": 500,
|
||||||
|
"chart_of_account": "4000",
|
||||||
|
"description": "General inspection",
|
||||||
|
"department_id": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parts": [
|
||||||
|
{
|
||||||
|
"part_id": 1,
|
||||||
|
"quantity": 2,
|
||||||
|
"rate": 150,
|
||||||
|
"chart_of_account": "4100",
|
||||||
|
"description": "Oil filter",
|
||||||
|
"department_id": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"expenses": [
|
||||||
|
{
|
||||||
|
"expense_id": 1,
|
||||||
|
"quantity": 1,
|
||||||
|
"rate": 100,
|
||||||
|
"chart_of_account": "4200",
|
||||||
|
"description": "Shop supplies",
|
||||||
|
"department_id": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"services": [
|
||||||
|
{
|
||||||
|
"service_id": 1,
|
||||||
|
"rate_type": "hourly",
|
||||||
|
"labor_rate_id": 1,
|
||||||
|
"working_hours": 2,
|
||||||
|
"labor_hours": 2,
|
||||||
|
"quantity": 1,
|
||||||
|
"rate": 800,
|
||||||
|
"chart_of_account": "4300",
|
||||||
|
"description": "Engine service",
|
||||||
|
"department_id": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"service_groups": [
|
||||||
|
{
|
||||||
|
"service_group_id": 1,
|
||||||
|
"rate_type": "flat_rate",
|
||||||
|
"labor_rate_id": 1,
|
||||||
|
"working_hours": 1,
|
||||||
|
"labor_hours": 1,
|
||||||
|
"rate": 600,
|
||||||
|
"chart_of_account": "4400",
|
||||||
|
"description": "Major service package",
|
||||||
|
"department_id": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"responses": {
|
"responses": {
|
||||||
"200": {
|
"201": {
|
||||||
"description": "OK",
|
"description": "Created",
|
||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
@ -43098,7 +43650,7 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"received_payment": {
|
"received_payment": {
|
||||||
"type": "integer"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"payment_mode_id": {
|
"payment_mode_id": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@ -43119,25 +43671,253 @@
|
|||||||
"updated_at": {
|
"updated_at": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"format": "date-time"
|
"format": "date-time"
|
||||||
|
},
|
||||||
|
"customer": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"vehicle": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"invoice_sequence": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"department": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"invoice_inspection_categories": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"invoice_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"inspection_category_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"labor_rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"working_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"labor_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chart_of_account": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"department_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"invoice_parts": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"invoice_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"part_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chart_of_account": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"department_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"invoice_expenses": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"invoice_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"expense_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chart_of_account": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"department_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"invoice_services": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"invoice_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"service_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"labor_rate_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"working_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"labor_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"quantity": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chart_of_account": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"department_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"invoice_service_groups": {
|
||||||
|
"type": "array",
|
||||||
|
"items": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"invoice_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"service_group_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate_type": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"labor_rate_id": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"working_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"labor_hours": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"rate": {
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"chart_of_account": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"department_id": {
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"example": {
|
"example": {
|
||||||
"message": "Operation completed successfully.",
|
"message": "Invoice created successfully.",
|
||||||
"data": {
|
"data": {
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"subject": "Invoice for Job Card 001",
|
"subject": "Invoice for Service",
|
||||||
"customer_id": 1,
|
"customer_id": 1,
|
||||||
"vehicle_id": 1,
|
"vehicle_id": 1,
|
||||||
"estimate_id": 1,
|
"estimate_id": 1,
|
||||||
"kms_in": 50000,
|
"kms_in": 50000,
|
||||||
"has_insurance": false,
|
"has_insurance": false,
|
||||||
"insurer_id": 1,
|
"insurer_id": 2,
|
||||||
"invoice_to_id": 1,
|
"invoice_to_id": 1,
|
||||||
"billing_address_id": 1,
|
"billing_address_id": 10,
|
||||||
"delivery_address_id": 1,
|
"delivery_address_id": 11,
|
||||||
"invoice_date": "2026-03-31",
|
"invoice_date": "2026-03-31",
|
||||||
"due_date": "2026-04-14",
|
"due_date": "2026-04-14",
|
||||||
"payment_terms_id": 1,
|
"payment_terms_id": 1,
|
||||||
@ -43145,16 +43925,100 @@
|
|||||||
"invoice_number": "INV-001",
|
"invoice_number": "INV-001",
|
||||||
"invoice_title": "Tax Invoice",
|
"invoice_title": "Tax Invoice",
|
||||||
"department_id": 1,
|
"department_id": 1,
|
||||||
"notes": "string",
|
"notes": "Vehicle service and parts",
|
||||||
"terms_and_conditions": "string",
|
"terms_and_conditions": "Payment due in 14 days.",
|
||||||
"status": "draft",
|
"status": "draft",
|
||||||
"received_payment": 0,
|
"received_payment": false,
|
||||||
"payment_mode_id": 1,
|
"payment_mode_id": 1,
|
||||||
"deposit_to": "string",
|
"deposit_to": "Main Account",
|
||||||
"amount": 0,
|
"amount": 2500,
|
||||||
"discount": "no",
|
"discount": "no",
|
||||||
"created_at": "2026-03-31T10:00:00.000000Z",
|
"created_at": "2026-03-31T10:00:00.000000Z",
|
||||||
"updated_at": "2026-03-31T10:00:00.000000Z"
|
"updated_at": "2026-03-31T10:00:00.000000Z",
|
||||||
|
"customer": {
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"vehicle": {
|
||||||
|
"id": 1
|
||||||
|
},
|
||||||
|
"invoice_sequence": {
|
||||||
|
"id": 1,
|
||||||
|
"title": "INV"
|
||||||
|
},
|
||||||
|
"department": {
|
||||||
|
"id": 1,
|
||||||
|
"name": "Service"
|
||||||
|
},
|
||||||
|
"invoice_inspection_categories": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"invoice_id": 1,
|
||||||
|
"inspection_category_id": 1,
|
||||||
|
"rate_type": "flat_rate",
|
||||||
|
"labor_rate": 500,
|
||||||
|
"working_hours": 1,
|
||||||
|
"labor_hours": 1,
|
||||||
|
"rate": 500,
|
||||||
|
"chart_of_account": "4000",
|
||||||
|
"description": "General inspection",
|
||||||
|
"department_id": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invoice_parts": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"invoice_id": 1,
|
||||||
|
"part_id": 1,
|
||||||
|
"quantity": 2,
|
||||||
|
"rate": 150,
|
||||||
|
"chart_of_account": "4100",
|
||||||
|
"description": "Oil filter",
|
||||||
|
"department_id": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invoice_expenses": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"invoice_id": 1,
|
||||||
|
"expense_id": 1,
|
||||||
|
"quantity": 1,
|
||||||
|
"rate": 100,
|
||||||
|
"chart_of_account": "4200",
|
||||||
|
"description": "Shop supplies",
|
||||||
|
"department_id": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invoice_services": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"invoice_id": 1,
|
||||||
|
"service_id": 1,
|
||||||
|
"rate_type": "hourly",
|
||||||
|
"labor_rate_id": 1,
|
||||||
|
"working_hours": 2,
|
||||||
|
"labor_hours": 2,
|
||||||
|
"quantity": 1,
|
||||||
|
"rate": 800,
|
||||||
|
"chart_of_account": "4300",
|
||||||
|
"description": "Engine service",
|
||||||
|
"department_id": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"invoice_service_groups": [
|
||||||
|
{
|
||||||
|
"id": 1,
|
||||||
|
"invoice_id": 1,
|
||||||
|
"service_group_id": 1,
|
||||||
|
"rate_type": "flat_rate",
|
||||||
|
"labor_rate_id": 1,
|
||||||
|
"working_hours": 1,
|
||||||
|
"labor_hours": 1,
|
||||||
|
"rate": 600,
|
||||||
|
"chart_of_account": "4400",
|
||||||
|
"description": "Major service package",
|
||||||
|
"department_id": 1
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -23,7 +23,8 @@
|
|||||||
"check-types": "echo \"No typecheck configured for @garage/api\""
|
"check-types": "echo \"No typecheck configured for @garage/api\""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"openapi-fetch": "^0.14.0"
|
"openapi-fetch": "^0.14.0",
|
||||||
|
"pino": "^10.3.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"openapi-typescript": "^7.10.1"
|
"openapi-typescript": "^7.10.1"
|
||||||
@ -33,7 +34,11 @@
|
|||||||
"server-only": "*"
|
"server-only": "*"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"next": { "optional": true },
|
"next": {
|
||||||
"server-only": { "optional": true }
|
"optional": true
|
||||||
|
},
|
||||||
|
"server-only": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"info": {
|
"info": {
|
||||||
"_postman_id": "351b2d30-cb82-4904-9a47-0d356181a8ea",
|
"_postman_id": "38ac2be3-7ab6-4dcc-ad76-6112212395af",
|
||||||
"name": "Reparee Collection",
|
"name": "Reparee Collection",
|
||||||
"description": "Auto-generated from OpenAPI spec. Import storage/app/openapi-default.json for the full schema.",
|
"description": "Auto-generated from OpenAPI spec. Import storage/app/openapi-default.json for the full schema.",
|
||||||
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
|
||||||
@ -135,7 +135,7 @@
|
|||||||
},
|
},
|
||||||
"response": [
|
"response": [
|
||||||
{
|
{
|
||||||
"name": "200 OK",
|
"name": "201 Created",
|
||||||
"originalRequest": {
|
"originalRequest": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"type": "bearer",
|
"type": "bearer",
|
||||||
@ -169,8 +169,8 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": "OK",
|
"status": "Created",
|
||||||
"code": 200,
|
"code": 201,
|
||||||
"_postman_previewlanguage": "json",
|
"_postman_previewlanguage": "json",
|
||||||
"header": [
|
"header": [
|
||||||
{
|
{
|
||||||
@ -14953,6 +14953,93 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Display the specified estimate.",
|
||||||
|
"request": {
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "{{auth_token}}",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Accept",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/estimates/{{id}}",
|
||||||
|
"host": [
|
||||||
|
"{{base_url}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"api",
|
||||||
|
"estimates",
|
||||||
|
"{{id}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"response": [
|
||||||
|
{
|
||||||
|
"name": "200 OK",
|
||||||
|
"originalRequest": {
|
||||||
|
"auth": {
|
||||||
|
"type": "bearer",
|
||||||
|
"bearer": [
|
||||||
|
{
|
||||||
|
"key": "token",
|
||||||
|
"value": "{{auth_token}}",
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"method": "GET",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Accept",
|
||||||
|
"value": "application/json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/estimates/{{id}}",
|
||||||
|
"host": [
|
||||||
|
"{{base_url}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"api",
|
||||||
|
"estimates",
|
||||||
|
"{{id}}"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"status": "OK",
|
||||||
|
"code": 200,
|
||||||
|
"_postman_previewlanguage": "json",
|
||||||
|
"header": [
|
||||||
|
{
|
||||||
|
"key": "Content-Type",
|
||||||
|
"value": "application/json"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"cookie": [],
|
||||||
|
"body": "{\n \"data\": {\n \"id\": 1,\n \"title\": \"Estimate for Toyota Camry\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"department_id\": 1,\n \"estimate_number\": \"EST-001\",\n \"date\": \"2026-03-31\",\n \"has_insurance\": false,\n \"enable_digital_authorisation\": false,\n \"insurance_type_id\": 1,\n \"insurer_id\": 2,\n \"service_writer_id\": 1,\n \"footer\": \"Thank you for your business.\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\",\n \"labels\": [\n {\n \"id\": 1,\n \"title\": \"Urgent\",\n \"color_code\": \"#FF0000\",\n \"pivot\": {\n \"estimate_id\": 1,\n \"label_id\": 1\n }\n }\n ],\n \"customer_remarks\": [\n {\n \"id\": 1,\n \"estimate_id\": 1,\n \"remark\": \"Oil change recommended.\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n ],\n \"customer\": {\n \"id\": 1,\n \"first_name\": \"John\",\n \"last_name\": \"Doe\"\n },\n \"vehicle\": {\n \"id\": 1,\n \"registration_number\": \"ABC-1234\",\n \"make\": \"Toyota\",\n \"model\": \"Camry\"\n },\n \"department\": {\n \"id\": 1,\n \"name\": \"Service\"\n },\n \"insurance_type\": {\n \"id\": 1,\n \"title\": \"Comprehensive\"\n },\n \"insurer\": {\n \"id\": 2,\n \"first_name\": \"Alex\",\n \"last_name\": \"Insurer\"\n },\n \"service_writer\": {\n \"id\": 1,\n \"first_name\": \"Sam\",\n \"last_name\": \"Writer\"\n }\n }\n}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Store a newly created estimate.",
|
"name": "Store a newly created estimate.",
|
||||||
"request": {
|
"request": {
|
||||||
@ -33569,7 +33656,7 @@
|
|||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"subject\": \"Invoice for Service\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"estimate_id\": 1,\n \"invoice_date\": \"2026-03-31\",\n \"due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"invoice_sequence_id\": 1,\n \"invoice_number\": \"INV-001\",\n \"department_id\": 1,\n \"notes\": \"string\",\n \"status\": \"draft\",\n \"discount\": \"no\"\n}",
|
"raw": "{\n \"subject\": \"Invoice for Service\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"estimate_id\": 1,\n \"kms_in\": 50000,\n \"has_insurance\": false,\n \"insurer_id\": 2,\n \"invoice_to_id\": 1,\n \"billing_address_id\": 10,\n \"delivery_address_id\": 11,\n \"invoice_date\": \"2026-03-31\",\n \"due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"invoice_sequence_id\": 1,\n \"invoice_number\": \"INV-001\",\n \"invoice_title\": \"Tax Invoice\",\n \"department_id\": 1,\n \"notes\": \"Vehicle service and parts\",\n \"terms_and_conditions\": \"Payment due in 14 days.\",\n \"status\": \"draft\",\n \"received_payment\": false,\n \"payment_mode_id\": 1,\n \"deposit_to\": \"Main Account\",\n \"amount\": 2500,\n \"discount\": \"no\",\n \"inspection_categories\": [\n {\n \"inspection_category_id\": 1,\n \"rate_type\": \"flat_rate\",\n \"labor_rate\": 500,\n \"working_hours\": 1,\n \"labor_hours\": 1,\n \"rate\": 500,\n \"chart_of_account\": \"4000\",\n \"description\": \"General inspection\",\n \"department_id\": 1\n }\n ],\n \"parts\": [\n {\n \"part_id\": 1,\n \"quantity\": 2,\n \"rate\": 150,\n \"chart_of_account\": \"4100\",\n \"description\": \"Oil filter\",\n \"department_id\": 1\n }\n ],\n \"expenses\": [\n {\n \"expense_id\": 1,\n \"quantity\": 1,\n \"rate\": 100,\n \"chart_of_account\": \"4200\",\n \"description\": \"Shop supplies\",\n \"department_id\": 1\n }\n ],\n \"services\": [\n {\n \"service_id\": 1,\n \"rate_type\": \"hourly\",\n \"labor_rate_id\": 1,\n \"working_hours\": 2,\n \"labor_hours\": 2,\n \"quantity\": 1,\n \"rate\": 800,\n \"chart_of_account\": \"4300\",\n \"description\": \"Engine service\",\n \"department_id\": 1\n }\n ],\n \"service_groups\": [\n {\n \"service_group_id\": 1,\n \"rate_type\": \"flat_rate\",\n \"labor_rate_id\": 1,\n \"working_hours\": 1,\n \"labor_hours\": 1,\n \"rate\": 600,\n \"chart_of_account\": \"4400\",\n \"description\": \"Major service package\",\n \"department_id\": 1\n }\n ]\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
@ -33589,7 +33676,7 @@
|
|||||||
},
|
},
|
||||||
"response": [
|
"response": [
|
||||||
{
|
{
|
||||||
"name": "200 OK",
|
"name": "201 Created",
|
||||||
"originalRequest": {
|
"originalRequest": {
|
||||||
"auth": {
|
"auth": {
|
||||||
"type": "bearer",
|
"type": "bearer",
|
||||||
@ -33614,7 +33701,7 @@
|
|||||||
],
|
],
|
||||||
"body": {
|
"body": {
|
||||||
"mode": "raw",
|
"mode": "raw",
|
||||||
"raw": "{\n \"subject\": \"Invoice for Service\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"estimate_id\": 1,\n \"invoice_date\": \"2026-03-31\",\n \"due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"invoice_sequence_id\": 1,\n \"invoice_number\": \"INV-001\",\n \"department_id\": 1,\n \"notes\": \"string\",\n \"status\": \"draft\",\n \"discount\": \"no\"\n}",
|
"raw": "{\n \"subject\": \"Invoice for Service\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"estimate_id\": 1,\n \"kms_in\": 50000,\n \"has_insurance\": false,\n \"insurer_id\": 2,\n \"invoice_to_id\": 1,\n \"billing_address_id\": 10,\n \"delivery_address_id\": 11,\n \"invoice_date\": \"2026-03-31\",\n \"due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"invoice_sequence_id\": 1,\n \"invoice_number\": \"INV-001\",\n \"invoice_title\": \"Tax Invoice\",\n \"department_id\": 1,\n \"notes\": \"Vehicle service and parts\",\n \"terms_and_conditions\": \"Payment due in 14 days.\",\n \"status\": \"draft\",\n \"received_payment\": false,\n \"payment_mode_id\": 1,\n \"deposit_to\": \"Main Account\",\n \"amount\": 2500,\n \"discount\": \"no\",\n \"inspection_categories\": [\n {\n \"inspection_category_id\": 1,\n \"rate_type\": \"flat_rate\",\n \"labor_rate\": 500,\n \"working_hours\": 1,\n \"labor_hours\": 1,\n \"rate\": 500,\n \"chart_of_account\": \"4000\",\n \"description\": \"General inspection\",\n \"department_id\": 1\n }\n ],\n \"parts\": [\n {\n \"part_id\": 1,\n \"quantity\": 2,\n \"rate\": 150,\n \"chart_of_account\": \"4100\",\n \"description\": \"Oil filter\",\n \"department_id\": 1\n }\n ],\n \"expenses\": [\n {\n \"expense_id\": 1,\n \"quantity\": 1,\n \"rate\": 100,\n \"chart_of_account\": \"4200\",\n \"description\": \"Shop supplies\",\n \"department_id\": 1\n }\n ],\n \"services\": [\n {\n \"service_id\": 1,\n \"rate_type\": \"hourly\",\n \"labor_rate_id\": 1,\n \"working_hours\": 2,\n \"labor_hours\": 2,\n \"quantity\": 1,\n \"rate\": 800,\n \"chart_of_account\": \"4300\",\n \"description\": \"Engine service\",\n \"department_id\": 1\n }\n ],\n \"service_groups\": [\n {\n \"service_group_id\": 1,\n \"rate_type\": \"flat_rate\",\n \"labor_rate_id\": 1,\n \"working_hours\": 1,\n \"labor_hours\": 1,\n \"rate\": 600,\n \"chart_of_account\": \"4400\",\n \"description\": \"Major service package\",\n \"department_id\": 1\n }\n ]\n}",
|
||||||
"options": {
|
"options": {
|
||||||
"raw": {
|
"raw": {
|
||||||
"language": "json"
|
"language": "json"
|
||||||
@ -33632,8 +33719,8 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"status": "OK",
|
"status": "Created",
|
||||||
"code": 200,
|
"code": 201,
|
||||||
"_postman_previewlanguage": "json",
|
"_postman_previewlanguage": "json",
|
||||||
"header": [
|
"header": [
|
||||||
{
|
{
|
||||||
@ -33642,7 +33729,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"cookie": [],
|
"cookie": [],
|
||||||
"body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"subject\": \"Invoice for Job Card 001\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"estimate_id\": 1,\n \"kms_in\": 50000,\n \"has_insurance\": false,\n \"insurer_id\": 1,\n \"invoice_to_id\": 1,\n \"billing_address_id\": 1,\n \"delivery_address_id\": 1,\n \"invoice_date\": \"2026-03-31\",\n \"due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"invoice_sequence_id\": 1,\n \"invoice_number\": \"INV-001\",\n \"invoice_title\": \"Tax Invoice\",\n \"department_id\": 1,\n \"notes\": \"string\",\n \"terms_and_conditions\": \"string\",\n \"status\": \"draft\",\n \"received_payment\": 0,\n \"payment_mode_id\": 1,\n \"deposit_to\": \"string\",\n \"amount\": 0,\n \"discount\": \"no\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}"
|
"body": "{\n \"message\": \"Invoice created successfully.\",\n \"data\": {\n \"id\": 1,\n \"subject\": \"Invoice for Service\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"estimate_id\": 1,\n \"kms_in\": 50000,\n \"has_insurance\": false,\n \"insurer_id\": 2,\n \"invoice_to_id\": 1,\n \"billing_address_id\": 10,\n \"delivery_address_id\": 11,\n \"invoice_date\": \"2026-03-31\",\n \"due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"invoice_sequence_id\": 1,\n \"invoice_number\": \"INV-001\",\n \"invoice_title\": \"Tax Invoice\",\n \"department_id\": 1,\n \"notes\": \"Vehicle service and parts\",\n \"terms_and_conditions\": \"Payment due in 14 days.\",\n \"status\": \"draft\",\n \"received_payment\": false,\n \"payment_mode_id\": 1,\n \"deposit_to\": \"Main Account\",\n \"amount\": 2500,\n \"discount\": \"no\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\",\n \"customer\": {\n \"id\": 1\n },\n \"vehicle\": {\n \"id\": 1\n },\n \"invoice_sequence\": {\n \"id\": 1,\n \"title\": \"INV\"\n },\n \"department\": {\n \"id\": 1,\n \"name\": \"Service\"\n },\n \"invoice_inspection_categories\": [\n {\n \"id\": 1,\n \"invoice_id\": 1,\n \"inspection_category_id\": 1,\n \"rate_type\": \"flat_rate\",\n \"labor_rate\": 500,\n \"working_hours\": 1,\n \"labor_hours\": 1,\n \"rate\": 500,\n \"chart_of_account\": \"4000\",\n \"description\": \"General inspection\",\n \"department_id\": 1\n }\n ],\n \"invoice_parts\": [\n {\n \"id\": 1,\n \"invoice_id\": 1,\n \"part_id\": 1,\n \"quantity\": 2,\n \"rate\": 150,\n \"chart_of_account\": \"4100\",\n \"description\": \"Oil filter\",\n \"department_id\": 1\n }\n ],\n \"invoice_expenses\": [\n {\n \"id\": 1,\n \"invoice_id\": 1,\n \"expense_id\": 1,\n \"quantity\": 1,\n \"rate\": 100,\n \"chart_of_account\": \"4200\",\n \"description\": \"Shop supplies\",\n \"department_id\": 1\n }\n ],\n \"invoice_services\": [\n {\n \"id\": 1,\n \"invoice_id\": 1,\n \"service_id\": 1,\n \"rate_type\": \"hourly\",\n \"labor_rate_id\": 1,\n \"working_hours\": 2,\n \"labor_hours\": 2,\n \"quantity\": 1,\n \"rate\": 800,\n \"chart_of_account\": \"4300\",\n \"description\": \"Engine service\",\n \"department_id\": 1\n }\n ],\n \"invoice_service_groups\": [\n {\n \"id\": 1,\n \"invoice_id\": 1,\n \"service_group_id\": 1,\n \"rate_type\": \"flat_rate\",\n \"labor_rate_id\": 1,\n \"working_hours\": 1,\n \"labor_hours\": 1,\n \"rate\": 600,\n \"chart_of_account\": \"4400\",\n \"description\": \"Major service package\",\n \"department_id\": 1\n }\n ]\n }\n}"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { CrudClient } from "../infra/crud-client"
|
import { CrudClient } from "../infra/crud-client"
|
||||||
import type { ApiClientOptions } from "../infra/client"
|
import type { ApiClientOptions } from "../infra/client"
|
||||||
import type { ApiPath } from "../infra/types"
|
import type { ApiPath, ApiResponse } from "../infra/types"
|
||||||
|
|
||||||
export const ESTIMATE_ROUTES = {
|
export const ESTIMATE_ROUTES = {
|
||||||
INDEX: "/api/estimates",
|
INDEX: "/api/estimates",
|
||||||
@ -25,8 +25,8 @@ export class EstimatesClient extends CrudClient<
|
|||||||
// Note: GET /api/estimates/{id} is not in the OpenAPI schema.
|
// Note: GET /api/estimates/{id} is not in the OpenAPI schema.
|
||||||
// This method uses a type cast and relies on the backend supporting the route.
|
// This method uses a type cast and relies on the backend supporting the route.
|
||||||
async getById(id: string) {
|
async getById(id: string) {
|
||||||
const data = await this.get(ESTIMATE_ROUTES.INDEX, { query: { id } } as never)
|
const data = await this.get(ESTIMATE_ROUTES.BY_ID, { params: { id } })
|
||||||
return {...data, data: (data as any)?.data?.[0] ?? null }
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Estimate Services ──
|
// ── Estimate Services ──
|
||||||
@ -48,7 +48,7 @@ export class EstimatesClient extends CrudClient<
|
|||||||
|
|
||||||
// ── Estimate Parts ──
|
// ── Estimate Parts ──
|
||||||
async listParts(estimateId: string) {
|
async listParts(estimateId: string) {
|
||||||
return this.get(ESTIMATE_ROUTES.PARTS, { params: { id: estimateId } } as never)
|
return this.get(ESTIMATE_ROUTES.PARTS, { params: { id: estimateId } }) as Promise<ApiResponse<typeof ESTIMATE_ROUTES.PARTS, "get">>
|
||||||
}
|
}
|
||||||
|
|
||||||
async addPart(estimateId: string, payload: { part_id?: number; quantity?: number; rate?: string; description?: string }) {
|
async addPart(estimateId: string, payload: { part_id?: number; quantity?: number; rate?: string; description?: string }) {
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import type {
|
|||||||
} from "./types"
|
} from "./types"
|
||||||
import createClient from "openapi-fetch"
|
import createClient from "openapi-fetch"
|
||||||
import type { paths } from "../../types/index"
|
import type { paths } from "../../types/index"
|
||||||
|
import { logger } from "./logger"
|
||||||
|
|
||||||
type HttpMethod = "get" | "post" | "put" | "delete" | "patch"
|
type HttpMethod = "get" | "post" | "put" | "delete" | "patch"
|
||||||
|
|
||||||
@ -73,6 +74,8 @@ export class ApiClient {
|
|||||||
const opts = options as never
|
const opts = options as never
|
||||||
const body = (options as Record<string, unknown>).body as never
|
const body = (options as Record<string, unknown>).body as never
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
switch (method) {
|
switch (method) {
|
||||||
case "get":
|
case "get":
|
||||||
return this.get(ep, opts) as Promise<ApiResponse<Path, Method>>
|
return this.get(ep, opts) as Promise<ApiResponse<Path, Method>>
|
||||||
@ -96,12 +99,15 @@ export class ApiClient {
|
|||||||
options: ApiRequestOptions<Path, "get"> = {} as ApiRequestOptions<Path, "get">,
|
options: ApiRequestOptions<Path, "get"> = {} as ApiRequestOptions<Path, "get">,
|
||||||
): Promise<ApiResponse<Path, "get">> {
|
): Promise<ApiResponse<Path, "get">> {
|
||||||
const requestOptions = this.toFetchOptions(options)
|
const requestOptions = this.toFetchOptions(options)
|
||||||
|
const startTime = Date.now()
|
||||||
|
logger.debug({ method: "GET", endpoint }, "API request")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, error, response } = await this.client.GET(endpoint, requestOptions as never)
|
const { data, error, response } = await this.client.GET(endpoint, requestOptions as never)
|
||||||
return this.resolveResult(endpoint, "get", data, error, response)
|
return this.resolveResult(endpoint, "get", data, error, response, startTime)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ApiError) throw err
|
if (err instanceof ApiError) throw err
|
||||||
|
logger.error({ method: "GET", endpoint, duration: Date.now() - startTime }, "Network error")
|
||||||
throw this.createNetworkError(endpoint, "get")
|
throw this.createNetworkError(endpoint, "get")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,12 +118,15 @@ export class ApiClient {
|
|||||||
options: Omit<ApiRequestOptions<Path, "post">, "body"> = {} as Omit<ApiRequestOptions<Path, "post">, "body">,
|
options: Omit<ApiRequestOptions<Path, "post">, "body"> = {} as Omit<ApiRequestOptions<Path, "post">, "body">,
|
||||||
): Promise<ApiResponse<Path, "post">> {
|
): Promise<ApiResponse<Path, "post">> {
|
||||||
const requestOptions = this.toFetchOptions({ ...options, body })
|
const requestOptions = this.toFetchOptions({ ...options, body })
|
||||||
|
const startTime = Date.now()
|
||||||
|
logger.debug({ method: "POST", endpoint }, "API request")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, error, response } = await this.client.POST(endpoint, requestOptions as never)
|
const { data, error, response } = await this.client.POST(endpoint, requestOptions as never)
|
||||||
return this.resolveResult(endpoint, "post", data, error, response)
|
return this.resolveResult(endpoint, "post", data, error, response, startTime)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ApiError) throw err
|
if (err instanceof ApiError) throw err
|
||||||
|
logger.error({ method: "POST", endpoint, duration: Date.now() - startTime }, "Network error")
|
||||||
throw this.createNetworkError(endpoint, "post")
|
throw this.createNetworkError(endpoint, "post")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,12 +137,15 @@ export class ApiClient {
|
|||||||
options: Omit<ApiRequestOptions<Path, "put">, "body"> = {} as Omit<ApiRequestOptions<Path, "put">, "body">,
|
options: Omit<ApiRequestOptions<Path, "put">, "body"> = {} as Omit<ApiRequestOptions<Path, "put">, "body">,
|
||||||
): Promise<ApiResponse<Path, "put">> {
|
): Promise<ApiResponse<Path, "put">> {
|
||||||
const requestOptions = this.toFetchOptions({ ...options, body })
|
const requestOptions = this.toFetchOptions({ ...options, body })
|
||||||
|
const startTime = Date.now()
|
||||||
|
logger.debug({ method: "PUT", endpoint }, "API request")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, error, response } = await this.client.PUT(endpoint, requestOptions as never)
|
const { data, error, response } = await this.client.PUT(endpoint, requestOptions as never)
|
||||||
return this.resolveResult(endpoint, "put", data, error, response)
|
return this.resolveResult(endpoint, "put", data, error, response, startTime)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ApiError) throw err
|
if (err instanceof ApiError) throw err
|
||||||
|
logger.error({ method: "PUT", endpoint, duration: Date.now() - startTime }, "Network error")
|
||||||
throw this.createNetworkError(endpoint, "put")
|
throw this.createNetworkError(endpoint, "put")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -143,12 +155,15 @@ export class ApiClient {
|
|||||||
options: ApiRequestOptions<Path, "delete"> = {} as ApiRequestOptions<Path, "delete">,
|
options: ApiRequestOptions<Path, "delete"> = {} as ApiRequestOptions<Path, "delete">,
|
||||||
): Promise<ApiResponse<Path, "delete">> {
|
): Promise<ApiResponse<Path, "delete">> {
|
||||||
const requestOptions = this.toFetchOptions(options)
|
const requestOptions = this.toFetchOptions(options)
|
||||||
|
const startTime = Date.now()
|
||||||
|
logger.debug({ method: "DELETE", endpoint }, "API request")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, error, response } = await this.client.DELETE(endpoint, requestOptions as never)
|
const { data, error, response } = await this.client.DELETE(endpoint, requestOptions as never)
|
||||||
return this.resolveResult(endpoint, "delete", data, error, response)
|
return this.resolveResult(endpoint, "delete", data, error, response, startTime)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ApiError) throw err
|
if (err instanceof ApiError) throw err
|
||||||
|
logger.error({ method: "DELETE", endpoint, duration: Date.now() - startTime }, "Network error")
|
||||||
throw this.createNetworkError(endpoint, "delete")
|
throw this.createNetworkError(endpoint, "delete")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,12 +174,15 @@ export class ApiClient {
|
|||||||
options: Omit<ApiRequestOptions<Path, "patch">, "body"> = {} as Omit<ApiRequestOptions<Path, "patch">, "body">,
|
options: Omit<ApiRequestOptions<Path, "patch">, "body"> = {} as Omit<ApiRequestOptions<Path, "patch">, "body">,
|
||||||
): Promise<ApiResponse<Path, "patch">> {
|
): Promise<ApiResponse<Path, "patch">> {
|
||||||
const requestOptions = this.toFetchOptions({ ...options, body })
|
const requestOptions = this.toFetchOptions({ ...options, body })
|
||||||
|
const startTime = Date.now()
|
||||||
|
logger.debug({ method: "PATCH", endpoint }, "API request")
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, error, response } = await this.client.PATCH(endpoint, requestOptions as never)
|
const { data, error, response } = await this.client.PATCH(endpoint, requestOptions as never)
|
||||||
return this.resolveResult(endpoint, "patch", data, error, response)
|
return this.resolveResult(endpoint, "patch", data, error, response, startTime)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof ApiError) throw err
|
if (err instanceof ApiError) throw err
|
||||||
|
logger.error({ method: "PATCH", endpoint, duration: Date.now() - startTime }, "Network error")
|
||||||
throw this.createNetworkError(endpoint, "patch")
|
throw this.createNetworkError(endpoint, "patch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -179,14 +197,21 @@ export class ApiClient {
|
|||||||
headers.set("Accept", "application/json")
|
headers.set("Accept", "application/json")
|
||||||
// Content-Type is intentionally omitted — fetch sets multipart/form-data + boundary automatically
|
// Content-Type is intentionally omitted — fetch sets multipart/form-data + boundary automatically
|
||||||
|
|
||||||
|
const startTime = Date.now()
|
||||||
|
logger.debug({ method: "POST", endpoint }, "API request (form-data)")
|
||||||
|
|
||||||
const response = await fetch(url, { method: "POST", headers, body: formData })
|
const response = await fetch(url, { method: "POST", headers, body: formData })
|
||||||
const text = await response.text()
|
const text = await response.text()
|
||||||
const data = text ? JSON.parse(text) : null
|
const data = text ? JSON.parse(text) : null
|
||||||
|
const duration = Date.now() - startTime
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
|
const level = response.status >= 500 ? "error" : "warn"
|
||||||
|
logger[level]({ method: "POST", endpoint, status: response.status, duration }, data?.message ?? "API request failed")
|
||||||
throw new ApiError(response.status, response.statusText, endpoint, "post", data)
|
throw new ApiError(response.status, response.statusText, endpoint, "post", data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug({ method: "POST", endpoint, status: response.status, duration }, "API request completed (form-data)")
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -201,14 +226,21 @@ export class ApiClient {
|
|||||||
init.body = JSON.stringify(options.body)
|
init.body = JSON.stringify(options.body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const startTime = Date.now()
|
||||||
|
logger.debug({ method, endpoint }, "API request (blob)")
|
||||||
|
|
||||||
const response = await fetch(url, init)
|
const response = await fetch(url, init)
|
||||||
|
const duration = Date.now() - startTime
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const text = await response.text()
|
const text = await response.text()
|
||||||
const data = text ? JSON.parse(text) : null
|
const data = text ? JSON.parse(text) : null
|
||||||
|
const level = response.status >= 500 ? "error" : "warn"
|
||||||
|
logger[level]({ method, endpoint, status: response.status, duration }, data?.message ?? "API request failed")
|
||||||
throw new ApiError(response.status, response.statusText, endpoint, method.toLowerCase(), data)
|
throw new ApiError(response.status, response.statusText, endpoint, method.toLowerCase(), data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug({ method, endpoint, status: response.status, duration }, "API request completed (blob)")
|
||||||
return response.blob()
|
return response.blob()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -250,17 +282,28 @@ export class ApiClient {
|
|||||||
data: unknown,
|
data: unknown,
|
||||||
error: unknown,
|
error: unknown,
|
||||||
response: Response,
|
response: Response,
|
||||||
|
startTime: number,
|
||||||
): ApiResponse<Path, Method> {
|
): ApiResponse<Path, Method> {
|
||||||
|
const duration = Date.now() - startTime
|
||||||
|
|
||||||
if (error !== undefined) {
|
if (error !== undefined) {
|
||||||
|
const payload = this.normalizeErrorPayload(error)
|
||||||
|
if (response.status >= 500) {
|
||||||
|
logger.error({ method: method.toUpperCase(), endpoint, status: response.status, duration }, payload?.message ?? "API request failed")
|
||||||
|
} else {
|
||||||
|
logger.warn({ method: method.toUpperCase(), endpoint, status: response.status, duration }, payload?.message ?? "API request failed")
|
||||||
|
}
|
||||||
throw new ApiError(
|
throw new ApiError(
|
||||||
response.status,
|
response.status,
|
||||||
response.statusText,
|
response.statusText,
|
||||||
endpoint,
|
endpoint,
|
||||||
method,
|
method,
|
||||||
this.normalizeErrorPayload(error),
|
payload,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug({ method: method.toUpperCase(), endpoint, status: response.status, duration, data }, "API request completed")
|
||||||
|
|
||||||
return data as ApiResponse<Path, Method>
|
return data as ApiResponse<Path, Method>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
32
packages/api/src/infra/logger.ts
Normal file
32
packages/api/src/infra/logger.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import pino from "pino"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Isomorphic logger for the API client.
|
||||||
|
*
|
||||||
|
* - In Node.js (server / Next.js RSC / server actions): pino writes newline-delimited
|
||||||
|
* JSON to stdout — structured and machine-parseable.
|
||||||
|
* - In the browser (Next.js client components): pino switches to the browser
|
||||||
|
* transport automatically and delegates to the appropriate `console.*` method
|
||||||
|
* (console.debug, console.warn, console.error, …).
|
||||||
|
*
|
||||||
|
* Log level defaults:
|
||||||
|
* development → debug (all messages)
|
||||||
|
* production → warn (warnings + errors only)
|
||||||
|
*
|
||||||
|
* Override with the NEXT_PUBLIC_LOG_LEVEL env var (e.g. "info").
|
||||||
|
*/
|
||||||
|
const level =
|
||||||
|
(process.env.NEXT_PUBLIC_LOG_LEVEL as pino.Level | undefined) ??
|
||||||
|
(process.env.NODE_ENV === "production" ? "warn" : "debug")
|
||||||
|
|
||||||
|
export const logger = pino({
|
||||||
|
name: "garage-api",
|
||||||
|
level,
|
||||||
|
browser: {
|
||||||
|
/**
|
||||||
|
* Pass a plain object to `console.*` so DevTools display
|
||||||
|
* log fields (method, endpoint, status, duration) as expandable props.
|
||||||
|
*/
|
||||||
|
asObject: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
@ -95,8 +95,8 @@ export interface paths {
|
|||||||
};
|
};
|
||||||
requestBody?: never;
|
requestBody?: never;
|
||||||
responses: {
|
responses: {
|
||||||
/** @description OK */
|
/** @description Created */
|
||||||
200: {
|
201: {
|
||||||
headers: {
|
headers: {
|
||||||
[name: string]: unknown;
|
[name: string]: unknown;
|
||||||
};
|
};
|
||||||
@ -14538,7 +14538,166 @@ export interface paths {
|
|||||||
path?: never;
|
path?: never;
|
||||||
cookie?: never;
|
cookie?: never;
|
||||||
};
|
};
|
||||||
get?: never;
|
/** Display the specified estimate. */
|
||||||
|
get: {
|
||||||
|
parameters: {
|
||||||
|
query?: never;
|
||||||
|
header?: never;
|
||||||
|
path: {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
cookie?: never;
|
||||||
|
};
|
||||||
|
requestBody?: never;
|
||||||
|
responses: {
|
||||||
|
/** @description OK */
|
||||||
|
200: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
/**
|
||||||
|
* @example {
|
||||||
|
* "data": {
|
||||||
|
* "id": 1,
|
||||||
|
* "title": "Estimate for Toyota Camry",
|
||||||
|
* "customer_id": 1,
|
||||||
|
* "vehicle_id": 1,
|
||||||
|
* "department_id": 1,
|
||||||
|
* "estimate_number": "EST-001",
|
||||||
|
* "date": "2026-03-31",
|
||||||
|
* "has_insurance": false,
|
||||||
|
* "enable_digital_authorisation": false,
|
||||||
|
* "insurance_type_id": 1,
|
||||||
|
* "insurer_id": 2,
|
||||||
|
* "service_writer_id": 1,
|
||||||
|
* "footer": "Thank you for your business.",
|
||||||
|
* "created_at": "2026-03-31T10:00:00.000000Z",
|
||||||
|
* "updated_at": "2026-03-31T10:00:00.000000Z",
|
||||||
|
* "labels": [
|
||||||
|
* {
|
||||||
|
* "id": 1,
|
||||||
|
* "title": "Urgent",
|
||||||
|
* "color_code": "#FF0000",
|
||||||
|
* "pivot": {
|
||||||
|
* "estimate_id": 1,
|
||||||
|
* "label_id": 1
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "customer_remarks": [
|
||||||
|
* {
|
||||||
|
* "id": 1,
|
||||||
|
* "estimate_id": 1,
|
||||||
|
* "remark": "Oil change recommended.",
|
||||||
|
* "created_at": "2026-03-31T10:00:00.000000Z",
|
||||||
|
* "updated_at": "2026-03-31T10:00:00.000000Z"
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "customer": {
|
||||||
|
* "id": 1,
|
||||||
|
* "first_name": "John",
|
||||||
|
* "last_name": "Doe"
|
||||||
|
* },
|
||||||
|
* "vehicle": {
|
||||||
|
* "id": 1,
|
||||||
|
* "registration_number": "ABC-1234",
|
||||||
|
* "make": "Toyota",
|
||||||
|
* "model": "Camry"
|
||||||
|
* },
|
||||||
|
* "department": {
|
||||||
|
* "id": 1,
|
||||||
|
* "name": "Service"
|
||||||
|
* },
|
||||||
|
* "insurance_type": {
|
||||||
|
* "id": 1,
|
||||||
|
* "title": "Comprehensive"
|
||||||
|
* },
|
||||||
|
* "insurer": {
|
||||||
|
* "id": 2,
|
||||||
|
* "first_name": "Alex",
|
||||||
|
* "last_name": "Insurer"
|
||||||
|
* },
|
||||||
|
* "service_writer": {
|
||||||
|
* "id": 1,
|
||||||
|
* "first_name": "Sam",
|
||||||
|
* "last_name": "Writer"
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
"application/json": {
|
||||||
|
data?: {
|
||||||
|
id?: number;
|
||||||
|
title?: string;
|
||||||
|
customer_id?: number;
|
||||||
|
vehicle_id?: number;
|
||||||
|
department_id?: number;
|
||||||
|
estimate_number?: string;
|
||||||
|
date?: string;
|
||||||
|
has_insurance?: boolean;
|
||||||
|
enable_digital_authorisation?: boolean;
|
||||||
|
insurance_type_id?: number;
|
||||||
|
insurer_id?: number;
|
||||||
|
service_writer_id?: number;
|
||||||
|
footer?: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
created_at?: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
updated_at?: string;
|
||||||
|
labels?: {
|
||||||
|
id?: number;
|
||||||
|
title?: string;
|
||||||
|
color_code?: string;
|
||||||
|
pivot?: {
|
||||||
|
estimate_id?: number;
|
||||||
|
label_id?: number;
|
||||||
|
};
|
||||||
|
}[];
|
||||||
|
customer_remarks?: {
|
||||||
|
id?: number;
|
||||||
|
estimate_id?: number;
|
||||||
|
remark?: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
created_at?: string;
|
||||||
|
/** Format: date-time */
|
||||||
|
updated_at?: string;
|
||||||
|
}[];
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
/** Update the specified estimate. */
|
/** Update the specified estimate. */
|
||||||
put: {
|
put: {
|
||||||
parameters: {
|
parameters: {
|
||||||
@ -29708,56 +29867,12 @@ export interface paths {
|
|||||||
* "customer_id": 1,
|
* "customer_id": 1,
|
||||||
* "vehicle_id": 1,
|
* "vehicle_id": 1,
|
||||||
* "estimate_id": 1,
|
* "estimate_id": 1,
|
||||||
* "invoice_date": "2026-03-31",
|
|
||||||
* "due_date": "2026-04-14",
|
|
||||||
* "payment_terms_id": 1,
|
|
||||||
* "invoice_sequence_id": 1,
|
|
||||||
* "invoice_number": "INV-001",
|
|
||||||
* "department_id": 1,
|
|
||||||
* "notes": "string",
|
|
||||||
* "status": "draft",
|
|
||||||
* "discount": "no"
|
|
||||||
* }
|
|
||||||
*/
|
|
||||||
"application/json": {
|
|
||||||
subject?: string;
|
|
||||||
customer_id?: number;
|
|
||||||
vehicle_id?: number;
|
|
||||||
estimate_id?: number;
|
|
||||||
invoice_date?: string;
|
|
||||||
due_date?: string;
|
|
||||||
payment_terms_id?: number;
|
|
||||||
invoice_sequence_id?: number;
|
|
||||||
invoice_number?: string;
|
|
||||||
department_id?: number;
|
|
||||||
notes?: string;
|
|
||||||
status?: string;
|
|
||||||
discount?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
};
|
|
||||||
responses: {
|
|
||||||
/** @description OK */
|
|
||||||
200: {
|
|
||||||
headers: {
|
|
||||||
[name: string]: unknown;
|
|
||||||
};
|
|
||||||
content: {
|
|
||||||
/**
|
|
||||||
* @example {
|
|
||||||
* "message": "Operation completed successfully.",
|
|
||||||
* "data": {
|
|
||||||
* "id": 1,
|
|
||||||
* "subject": "Invoice for Job Card 001",
|
|
||||||
* "customer_id": 1,
|
|
||||||
* "vehicle_id": 1,
|
|
||||||
* "estimate_id": 1,
|
|
||||||
* "kms_in": 50000,
|
* "kms_in": 50000,
|
||||||
* "has_insurance": false,
|
* "has_insurance": false,
|
||||||
* "insurer_id": 1,
|
* "insurer_id": 2,
|
||||||
* "invoice_to_id": 1,
|
* "invoice_to_id": 1,
|
||||||
* "billing_address_id": 1,
|
* "billing_address_id": 10,
|
||||||
* "delivery_address_id": 1,
|
* "delivery_address_id": 11,
|
||||||
* "invoice_date": "2026-03-31",
|
* "invoice_date": "2026-03-31",
|
||||||
* "due_date": "2026-04-14",
|
* "due_date": "2026-04-14",
|
||||||
* "payment_terms_id": 1,
|
* "payment_terms_id": 1,
|
||||||
@ -29765,16 +29880,278 @@ export interface paths {
|
|||||||
* "invoice_number": "INV-001",
|
* "invoice_number": "INV-001",
|
||||||
* "invoice_title": "Tax Invoice",
|
* "invoice_title": "Tax Invoice",
|
||||||
* "department_id": 1,
|
* "department_id": 1,
|
||||||
* "notes": "string",
|
* "notes": "Vehicle service and parts",
|
||||||
* "terms_and_conditions": "string",
|
* "terms_and_conditions": "Payment due in 14 days.",
|
||||||
* "status": "draft",
|
* "status": "draft",
|
||||||
* "received_payment": 0,
|
* "received_payment": false,
|
||||||
* "payment_mode_id": 1,
|
* "payment_mode_id": 1,
|
||||||
* "deposit_to": "string",
|
* "deposit_to": "Main Account",
|
||||||
* "amount": 0,
|
* "amount": 2500,
|
||||||
|
* "discount": "no",
|
||||||
|
* "inspection_categories": [
|
||||||
|
* {
|
||||||
|
* "inspection_category_id": 1,
|
||||||
|
* "rate_type": "flat_rate",
|
||||||
|
* "labor_rate": 500,
|
||||||
|
* "working_hours": 1,
|
||||||
|
* "labor_hours": 1,
|
||||||
|
* "rate": 500,
|
||||||
|
* "chart_of_account": "4000",
|
||||||
|
* "description": "General inspection",
|
||||||
|
* "department_id": 1
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "parts": [
|
||||||
|
* {
|
||||||
|
* "part_id": 1,
|
||||||
|
* "quantity": 2,
|
||||||
|
* "rate": 150,
|
||||||
|
* "chart_of_account": "4100",
|
||||||
|
* "description": "Oil filter",
|
||||||
|
* "department_id": 1
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "expenses": [
|
||||||
|
* {
|
||||||
|
* "expense_id": 1,
|
||||||
|
* "quantity": 1,
|
||||||
|
* "rate": 100,
|
||||||
|
* "chart_of_account": "4200",
|
||||||
|
* "description": "Shop supplies",
|
||||||
|
* "department_id": 1
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "services": [
|
||||||
|
* {
|
||||||
|
* "service_id": 1,
|
||||||
|
* "rate_type": "hourly",
|
||||||
|
* "labor_rate_id": 1,
|
||||||
|
* "working_hours": 2,
|
||||||
|
* "labor_hours": 2,
|
||||||
|
* "quantity": 1,
|
||||||
|
* "rate": 800,
|
||||||
|
* "chart_of_account": "4300",
|
||||||
|
* "description": "Engine service",
|
||||||
|
* "department_id": 1
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "service_groups": [
|
||||||
|
* {
|
||||||
|
* "service_group_id": 1,
|
||||||
|
* "rate_type": "flat_rate",
|
||||||
|
* "labor_rate_id": 1,
|
||||||
|
* "working_hours": 1,
|
||||||
|
* "labor_hours": 1,
|
||||||
|
* "rate": 600,
|
||||||
|
* "chart_of_account": "4400",
|
||||||
|
* "description": "Major service package",
|
||||||
|
* "department_id": 1
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
"application/json": {
|
||||||
|
subject?: string;
|
||||||
|
customer_id?: number;
|
||||||
|
vehicle_id?: number;
|
||||||
|
estimate_id?: number;
|
||||||
|
kms_in?: number;
|
||||||
|
has_insurance?: boolean;
|
||||||
|
insurer_id?: number;
|
||||||
|
invoice_to_id?: number;
|
||||||
|
billing_address_id?: number;
|
||||||
|
delivery_address_id?: number;
|
||||||
|
invoice_date?: string;
|
||||||
|
due_date?: string;
|
||||||
|
payment_terms_id?: number;
|
||||||
|
invoice_sequence_id?: number;
|
||||||
|
invoice_number?: string;
|
||||||
|
invoice_title?: string;
|
||||||
|
department_id?: number;
|
||||||
|
notes?: string;
|
||||||
|
terms_and_conditions?: string;
|
||||||
|
status?: string;
|
||||||
|
received_payment?: boolean;
|
||||||
|
payment_mode_id?: number;
|
||||||
|
deposit_to?: string;
|
||||||
|
amount?: number;
|
||||||
|
discount?: string;
|
||||||
|
inspection_categories?: {
|
||||||
|
inspection_category_id?: number;
|
||||||
|
rate_type?: string;
|
||||||
|
labor_rate?: number;
|
||||||
|
working_hours?: number;
|
||||||
|
labor_hours?: number;
|
||||||
|
rate?: number;
|
||||||
|
chart_of_account?: string;
|
||||||
|
description?: string;
|
||||||
|
department_id?: number;
|
||||||
|
}[];
|
||||||
|
parts?: {
|
||||||
|
part_id?: number;
|
||||||
|
quantity?: number;
|
||||||
|
rate?: number;
|
||||||
|
chart_of_account?: string;
|
||||||
|
description?: string;
|
||||||
|
department_id?: number;
|
||||||
|
}[];
|
||||||
|
expenses?: {
|
||||||
|
expense_id?: number;
|
||||||
|
quantity?: number;
|
||||||
|
rate?: number;
|
||||||
|
chart_of_account?: string;
|
||||||
|
description?: string;
|
||||||
|
department_id?: number;
|
||||||
|
}[];
|
||||||
|
services?: {
|
||||||
|
service_id?: number;
|
||||||
|
rate_type?: string;
|
||||||
|
labor_rate_id?: number;
|
||||||
|
working_hours?: number;
|
||||||
|
labor_hours?: number;
|
||||||
|
quantity?: number;
|
||||||
|
rate?: number;
|
||||||
|
chart_of_account?: string;
|
||||||
|
description?: string;
|
||||||
|
department_id?: number;
|
||||||
|
}[];
|
||||||
|
service_groups?: {
|
||||||
|
service_group_id?: number;
|
||||||
|
rate_type?: string;
|
||||||
|
labor_rate_id?: number;
|
||||||
|
working_hours?: number;
|
||||||
|
labor_hours?: number;
|
||||||
|
rate?: number;
|
||||||
|
chart_of_account?: string;
|
||||||
|
description?: string;
|
||||||
|
department_id?: number;
|
||||||
|
}[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
responses: {
|
||||||
|
/** @description Created */
|
||||||
|
201: {
|
||||||
|
headers: {
|
||||||
|
[name: string]: unknown;
|
||||||
|
};
|
||||||
|
content: {
|
||||||
|
/**
|
||||||
|
* @example {
|
||||||
|
* "message": "Invoice created successfully.",
|
||||||
|
* "data": {
|
||||||
|
* "id": 1,
|
||||||
|
* "subject": "Invoice for Service",
|
||||||
|
* "customer_id": 1,
|
||||||
|
* "vehicle_id": 1,
|
||||||
|
* "estimate_id": 1,
|
||||||
|
* "kms_in": 50000,
|
||||||
|
* "has_insurance": false,
|
||||||
|
* "insurer_id": 2,
|
||||||
|
* "invoice_to_id": 1,
|
||||||
|
* "billing_address_id": 10,
|
||||||
|
* "delivery_address_id": 11,
|
||||||
|
* "invoice_date": "2026-03-31",
|
||||||
|
* "due_date": "2026-04-14",
|
||||||
|
* "payment_terms_id": 1,
|
||||||
|
* "invoice_sequence_id": 1,
|
||||||
|
* "invoice_number": "INV-001",
|
||||||
|
* "invoice_title": "Tax Invoice",
|
||||||
|
* "department_id": 1,
|
||||||
|
* "notes": "Vehicle service and parts",
|
||||||
|
* "terms_and_conditions": "Payment due in 14 days.",
|
||||||
|
* "status": "draft",
|
||||||
|
* "received_payment": false,
|
||||||
|
* "payment_mode_id": 1,
|
||||||
|
* "deposit_to": "Main Account",
|
||||||
|
* "amount": 2500,
|
||||||
* "discount": "no",
|
* "discount": "no",
|
||||||
* "created_at": "2026-03-31T10:00:00.000000Z",
|
* "created_at": "2026-03-31T10:00:00.000000Z",
|
||||||
* "updated_at": "2026-03-31T10:00:00.000000Z"
|
* "updated_at": "2026-03-31T10:00:00.000000Z",
|
||||||
|
* "customer": {
|
||||||
|
* "id": 1
|
||||||
|
* },
|
||||||
|
* "vehicle": {
|
||||||
|
* "id": 1
|
||||||
|
* },
|
||||||
|
* "invoice_sequence": {
|
||||||
|
* "id": 1,
|
||||||
|
* "title": "INV"
|
||||||
|
* },
|
||||||
|
* "department": {
|
||||||
|
* "id": 1,
|
||||||
|
* "name": "Service"
|
||||||
|
* },
|
||||||
|
* "invoice_inspection_categories": [
|
||||||
|
* {
|
||||||
|
* "id": 1,
|
||||||
|
* "invoice_id": 1,
|
||||||
|
* "inspection_category_id": 1,
|
||||||
|
* "rate_type": "flat_rate",
|
||||||
|
* "labor_rate": 500,
|
||||||
|
* "working_hours": 1,
|
||||||
|
* "labor_hours": 1,
|
||||||
|
* "rate": 500,
|
||||||
|
* "chart_of_account": "4000",
|
||||||
|
* "description": "General inspection",
|
||||||
|
* "department_id": 1
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "invoice_parts": [
|
||||||
|
* {
|
||||||
|
* "id": 1,
|
||||||
|
* "invoice_id": 1,
|
||||||
|
* "part_id": 1,
|
||||||
|
* "quantity": 2,
|
||||||
|
* "rate": 150,
|
||||||
|
* "chart_of_account": "4100",
|
||||||
|
* "description": "Oil filter",
|
||||||
|
* "department_id": 1
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "invoice_expenses": [
|
||||||
|
* {
|
||||||
|
* "id": 1,
|
||||||
|
* "invoice_id": 1,
|
||||||
|
* "expense_id": 1,
|
||||||
|
* "quantity": 1,
|
||||||
|
* "rate": 100,
|
||||||
|
* "chart_of_account": "4200",
|
||||||
|
* "description": "Shop supplies",
|
||||||
|
* "department_id": 1
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "invoice_services": [
|
||||||
|
* {
|
||||||
|
* "id": 1,
|
||||||
|
* "invoice_id": 1,
|
||||||
|
* "service_id": 1,
|
||||||
|
* "rate_type": "hourly",
|
||||||
|
* "labor_rate_id": 1,
|
||||||
|
* "working_hours": 2,
|
||||||
|
* "labor_hours": 2,
|
||||||
|
* "quantity": 1,
|
||||||
|
* "rate": 800,
|
||||||
|
* "chart_of_account": "4300",
|
||||||
|
* "description": "Engine service",
|
||||||
|
* "department_id": 1
|
||||||
|
* }
|
||||||
|
* ],
|
||||||
|
* "invoice_service_groups": [
|
||||||
|
* {
|
||||||
|
* "id": 1,
|
||||||
|
* "invoice_id": 1,
|
||||||
|
* "service_group_id": 1,
|
||||||
|
* "rate_type": "flat_rate",
|
||||||
|
* "labor_rate_id": 1,
|
||||||
|
* "working_hours": 1,
|
||||||
|
* "labor_hours": 1,
|
||||||
|
* "rate": 600,
|
||||||
|
* "chart_of_account": "4400",
|
||||||
|
* "description": "Major service package",
|
||||||
|
* "department_id": 1
|
||||||
|
* }
|
||||||
|
* ]
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
@ -29802,7 +30179,7 @@ export interface paths {
|
|||||||
notes?: string;
|
notes?: string;
|
||||||
terms_and_conditions?: string;
|
terms_and_conditions?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
received_payment?: number;
|
received_payment?: boolean;
|
||||||
payment_mode_id?: number;
|
payment_mode_id?: number;
|
||||||
deposit_to?: string;
|
deposit_to?: string;
|
||||||
amount?: number;
|
amount?: number;
|
||||||
@ -29811,6 +30188,80 @@ export interface paths {
|
|||||||
created_at?: string;
|
created_at?: string;
|
||||||
/** Format: date-time */
|
/** Format: date-time */
|
||||||
updated_at?: string;
|
updated_at?: string;
|
||||||
|
customer?: {
|
||||||
|
id?: number;
|
||||||
|
};
|
||||||
|
vehicle?: {
|
||||||
|
id?: number;
|
||||||
|
};
|
||||||
|
invoice_sequence?: {
|
||||||
|
id?: number;
|
||||||
|
title?: string;
|
||||||
|
};
|
||||||
|
department?: {
|
||||||
|
id?: number;
|
||||||
|
name?: string;
|
||||||
|
};
|
||||||
|
invoice_inspection_categories?: {
|
||||||
|
id?: number;
|
||||||
|
invoice_id?: number;
|
||||||
|
inspection_category_id?: number;
|
||||||
|
rate_type?: string;
|
||||||
|
labor_rate?: number;
|
||||||
|
working_hours?: number;
|
||||||
|
labor_hours?: number;
|
||||||
|
rate?: number;
|
||||||
|
chart_of_account?: string;
|
||||||
|
description?: string;
|
||||||
|
department_id?: number;
|
||||||
|
}[];
|
||||||
|
invoice_parts?: {
|
||||||
|
id?: number;
|
||||||
|
invoice_id?: number;
|
||||||
|
part_id?: number;
|
||||||
|
quantity?: number;
|
||||||
|
rate?: number;
|
||||||
|
chart_of_account?: string;
|
||||||
|
description?: string;
|
||||||
|
department_id?: number;
|
||||||
|
}[];
|
||||||
|
invoice_expenses?: {
|
||||||
|
id?: number;
|
||||||
|
invoice_id?: number;
|
||||||
|
expense_id?: number;
|
||||||
|
quantity?: number;
|
||||||
|
rate?: number;
|
||||||
|
chart_of_account?: string;
|
||||||
|
description?: string;
|
||||||
|
department_id?: number;
|
||||||
|
}[];
|
||||||
|
invoice_services?: {
|
||||||
|
id?: number;
|
||||||
|
invoice_id?: number;
|
||||||
|
service_id?: number;
|
||||||
|
rate_type?: string;
|
||||||
|
labor_rate_id?: number;
|
||||||
|
working_hours?: number;
|
||||||
|
labor_hours?: number;
|
||||||
|
quantity?: number;
|
||||||
|
rate?: number;
|
||||||
|
chart_of_account?: string;
|
||||||
|
description?: string;
|
||||||
|
department_id?: number;
|
||||||
|
}[];
|
||||||
|
invoice_service_groups?: {
|
||||||
|
id?: number;
|
||||||
|
invoice_id?: number;
|
||||||
|
service_group_id?: number;
|
||||||
|
rate_type?: string;
|
||||||
|
labor_rate_id?: number;
|
||||||
|
working_hours?: number;
|
||||||
|
labor_hours?: number;
|
||||||
|
rate?: number;
|
||||||
|
chart_of_account?: string;
|
||||||
|
description?: string;
|
||||||
|
department_id?: number;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
146
pnpm-lock.yaml
generated
146
pnpm-lock.yaml
generated
@ -159,6 +159,9 @@ importers:
|
|||||||
openapi-fetch:
|
openapi-fetch:
|
||||||
specifier: ^0.14.0
|
specifier: ^0.14.0
|
||||||
version: 0.14.1
|
version: 0.14.1
|
||||||
|
pino:
|
||||||
|
specifier: ^10.3.1
|
||||||
|
version: 10.3.1
|
||||||
server-only:
|
server-only:
|
||||||
specifier: '*'
|
specifier: '*'
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
@ -816,6 +819,9 @@ packages:
|
|||||||
'@open-draft/until@2.1.0':
|
'@open-draft/until@2.1.0':
|
||||||
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
|
resolution: {integrity: sha512-U69T3ItWHvLwGg5eJ0n3I62nWuE6ilHlmz7zM0npLBRvPRd7e6NYmg54vvRtP5mZG7kZqZCFVdsTWo7BPtBujg==}
|
||||||
|
|
||||||
|
'@pinojs/redact@0.4.0':
|
||||||
|
resolution: {integrity: sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==}
|
||||||
|
|
||||||
'@radix-ui/number@1.1.1':
|
'@radix-ui/number@1.1.1':
|
||||||
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
|
resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==}
|
||||||
|
|
||||||
@ -2063,6 +2069,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
|
resolution: {integrity: sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
|
|
||||||
|
atomic-sleep@1.0.0:
|
||||||
|
resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==}
|
||||||
|
engines: {node: '>=8.0.0'}
|
||||||
|
|
||||||
available-typed-arrays@1.0.7:
|
available-typed-arrays@1.0.7:
|
||||||
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
@ -3777,6 +3787,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
on-exit-leak-free@2.1.2:
|
||||||
|
resolution: {integrity: sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==}
|
||||||
|
engines: {node: '>=14.0.0'}
|
||||||
|
|
||||||
on-finished@2.4.1:
|
on-finished@2.4.1:
|
||||||
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==}
|
||||||
engines: {node: '>= 0.8'}
|
engines: {node: '>= 0.8'}
|
||||||
@ -3903,6 +3917,16 @@ packages:
|
|||||||
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
pino-abstract-transport@3.0.0:
|
||||||
|
resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==}
|
||||||
|
|
||||||
|
pino-std-serializers@7.1.0:
|
||||||
|
resolution: {integrity: sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==}
|
||||||
|
|
||||||
|
pino@10.3.1:
|
||||||
|
resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
pkce-challenge@5.0.1:
|
pkce-challenge@5.0.1:
|
||||||
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
|
resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==}
|
||||||
engines: {node: '>=16.20.0'}
|
engines: {node: '>=16.20.0'}
|
||||||
@ -4008,6 +4032,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
|
resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
process-warning@5.0.0:
|
||||||
|
resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==}
|
||||||
|
|
||||||
process@0.11.10:
|
process@0.11.10:
|
||||||
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
@ -4044,6 +4071,9 @@ packages:
|
|||||||
queue-microtask@1.2.3:
|
queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
|
quick-format-unescaped@4.0.4:
|
||||||
|
resolution: {integrity: sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==}
|
||||||
|
|
||||||
radix-ui@1.4.3:
|
radix-ui@1.4.3:
|
||||||
resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==}
|
resolution: {integrity: sha512-aWizCQiyeAenIdUbqEpXgRA1ya65P13NKn/W8rWkcN0OPkRDxdBVLWnIEDsS2RpwCK2nobI7oMUSmexzTDyAmA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -4146,6 +4176,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
|
resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
real-require@0.2.0:
|
||||||
|
resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==}
|
||||||
|
engines: {node: '>= 12.13.0'}
|
||||||
|
|
||||||
recast@0.23.11:
|
recast@0.23.11:
|
||||||
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
|
resolution: {integrity: sha512-YTUo+Flmw4ZXiWfQKGcwwc11KnoRAYgzAE2E7mXKCjSviTKShtxBsN6YUUBB2gtaBzKzeKunxhUwNHQuRryhWA==}
|
||||||
engines: {node: '>= 4'}
|
engines: {node: '>= 4'}
|
||||||
@ -4251,6 +4285,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
|
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
|
||||||
engines: {node: '>= 0.4'}
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
safe-stable-stringify@2.5.0:
|
||||||
|
resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
safer-buffer@2.1.2:
|
safer-buffer@2.1.2:
|
||||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
@ -4342,6 +4380,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
|
resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
sonic-boom@4.2.1:
|
||||||
|
resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==}
|
||||||
|
|
||||||
sonner@2.0.7:
|
sonner@2.0.7:
|
||||||
resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
|
resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@ -4356,6 +4397,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
split2@4.2.0:
|
||||||
|
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
|
||||||
|
engines: {node: '>= 10.x'}
|
||||||
|
|
||||||
sshpk@1.18.0:
|
sshpk@1.18.0:
|
||||||
resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==}
|
resolution: {integrity: sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
@ -4490,6 +4535,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
thread-stream@4.0.0:
|
||||||
|
resolution: {integrity: sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==}
|
||||||
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
throttleit@1.0.1:
|
throttleit@1.0.1:
|
||||||
resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==}
|
resolution: {integrity: sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==}
|
||||||
|
|
||||||
@ -4872,7 +4921,7 @@ snapshots:
|
|||||||
'@babel/types': 7.29.0
|
'@babel/types': 7.29.0
|
||||||
'@jridgewell/remapping': 2.3.5
|
'@jridgewell/remapping': 2.3.5
|
||||||
convert-source-map: 2.0.0
|
convert-source-map: 2.0.0
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
gensync: 1.0.0-beta.2
|
gensync: 1.0.0-beta.2
|
||||||
json5: 2.2.3
|
json5: 2.2.3
|
||||||
semver: 6.3.1
|
semver: 6.3.1
|
||||||
@ -5030,7 +5079,7 @@ snapshots:
|
|||||||
'@babel/parser': 7.29.2
|
'@babel/parser': 7.29.2
|
||||||
'@babel/template': 7.28.6
|
'@babel/template': 7.28.6
|
||||||
'@babel/types': 7.29.0
|
'@babel/types': 7.29.0
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
@ -5140,7 +5189,7 @@ snapshots:
|
|||||||
'@eslint/config-array@0.21.1':
|
'@eslint/config-array@0.21.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint/object-schema': 2.1.7
|
'@eslint/object-schema': 2.1.7
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
minimatch: 3.1.2
|
minimatch: 3.1.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -5148,7 +5197,7 @@ snapshots:
|
|||||||
'@eslint/config-array@0.21.2':
|
'@eslint/config-array@0.21.2':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@eslint/object-schema': 2.1.7
|
'@eslint/object-schema': 2.1.7
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
minimatch: 3.1.5
|
minimatch: 3.1.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -5164,7 +5213,7 @@ snapshots:
|
|||||||
'@eslint/eslintrc@3.3.1':
|
'@eslint/eslintrc@3.3.1':
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
espree: 10.4.0
|
espree: 10.4.0
|
||||||
globals: 14.0.0
|
globals: 14.0.0
|
||||||
ignore: 5.3.2
|
ignore: 5.3.2
|
||||||
@ -5178,7 +5227,7 @@ snapshots:
|
|||||||
'@eslint/eslintrc@3.3.5':
|
'@eslint/eslintrc@3.3.5':
|
||||||
dependencies:
|
dependencies:
|
||||||
ajv: 6.14.0
|
ajv: 6.14.0
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
espree: 10.4.0
|
espree: 10.4.0
|
||||||
globals: 14.0.0
|
globals: 14.0.0
|
||||||
ignore: 5.3.2
|
ignore: 5.3.2
|
||||||
@ -5484,6 +5533,8 @@ snapshots:
|
|||||||
|
|
||||||
'@open-draft/until@2.1.0': {}
|
'@open-draft/until@2.1.0': {}
|
||||||
|
|
||||||
|
'@pinojs/redact@0.4.0': {}
|
||||||
|
|
||||||
'@radix-ui/number@1.1.1': {}
|
'@radix-ui/number@1.1.1': {}
|
||||||
|
|
||||||
'@radix-ui/primitive@1.1.3': {}
|
'@radix-ui/primitive@1.1.3': {}
|
||||||
@ -6506,7 +6557,7 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.50.0
|
'@typescript-eslint/types': 8.50.0
|
||||||
'@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.2)
|
'@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.2)
|
||||||
'@typescript-eslint/visitor-keys': 8.50.0
|
'@typescript-eslint/visitor-keys': 8.50.0
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
eslint: 9.39.1(jiti@2.6.1)
|
eslint: 9.39.1(jiti@2.6.1)
|
||||||
typescript: 5.9.2
|
typescript: 5.9.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@ -6518,7 +6569,7 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.50.0
|
'@typescript-eslint/types': 8.50.0
|
||||||
'@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/visitor-keys': 8.50.0
|
'@typescript-eslint/visitor-keys': 8.50.0
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
eslint: 9.39.4(jiti@2.6.1)
|
eslint: 9.39.4(jiti@2.6.1)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@ -6528,7 +6579,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.2)
|
'@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.2)
|
||||||
'@typescript-eslint/types': 8.50.0
|
'@typescript-eslint/types': 8.50.0
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
typescript: 5.9.2
|
typescript: 5.9.2
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -6537,7 +6588,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3)
|
'@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/types': 8.50.0
|
'@typescript-eslint/types': 8.50.0
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- supports-color
|
- supports-color
|
||||||
@ -6560,7 +6611,7 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.50.0
|
'@typescript-eslint/types': 8.50.0
|
||||||
'@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.2)
|
'@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.2)
|
||||||
'@typescript-eslint/utils': 8.50.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.2)
|
'@typescript-eslint/utils': 8.50.0(eslint@9.39.1(jiti@2.6.1))(typescript@5.9.2)
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
eslint: 9.39.1(jiti@2.6.1)
|
eslint: 9.39.1(jiti@2.6.1)
|
||||||
ts-api-utils: 2.1.0(typescript@5.9.2)
|
ts-api-utils: 2.1.0(typescript@5.9.2)
|
||||||
typescript: 5.9.2
|
typescript: 5.9.2
|
||||||
@ -6572,7 +6623,7 @@ snapshots:
|
|||||||
'@typescript-eslint/types': 8.50.0
|
'@typescript-eslint/types': 8.50.0
|
||||||
'@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3)
|
'@typescript-eslint/typescript-estree': 8.50.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/utils': 8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
|
'@typescript-eslint/utils': 8.50.0(eslint@9.39.4(jiti@2.6.1))(typescript@5.9.3)
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
eslint: 9.39.4(jiti@2.6.1)
|
eslint: 9.39.4(jiti@2.6.1)
|
||||||
ts-api-utils: 2.1.0(typescript@5.9.3)
|
ts-api-utils: 2.1.0(typescript@5.9.3)
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
@ -6587,7 +6638,7 @@ snapshots:
|
|||||||
'@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.2)
|
'@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.2)
|
||||||
'@typescript-eslint/types': 8.50.0
|
'@typescript-eslint/types': 8.50.0
|
||||||
'@typescript-eslint/visitor-keys': 8.50.0
|
'@typescript-eslint/visitor-keys': 8.50.0
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
semver: 7.7.3
|
semver: 7.7.3
|
||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
@ -6602,7 +6653,7 @@ snapshots:
|
|||||||
'@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3)
|
'@typescript-eslint/tsconfig-utils': 8.50.0(typescript@5.9.3)
|
||||||
'@typescript-eslint/types': 8.50.0
|
'@typescript-eslint/types': 8.50.0
|
||||||
'@typescript-eslint/visitor-keys': 8.50.0
|
'@typescript-eslint/visitor-keys': 8.50.0
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
minimatch: 9.0.5
|
minimatch: 9.0.5
|
||||||
semver: 7.7.3
|
semver: 7.7.3
|
||||||
tinyglobby: 0.2.15
|
tinyglobby: 0.2.15
|
||||||
@ -6851,6 +6902,8 @@ snapshots:
|
|||||||
|
|
||||||
at-least-node@1.0.0: {}
|
at-least-node@1.0.0: {}
|
||||||
|
|
||||||
|
atomic-sleep@1.0.0: {}
|
||||||
|
|
||||||
available-typed-arrays@1.0.7:
|
available-typed-arrays@1.0.7:
|
||||||
dependencies:
|
dependencies:
|
||||||
possible-typed-array-names: 1.1.0
|
possible-typed-array-names: 1.1.0
|
||||||
@ -6883,7 +6936,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
bytes: 3.1.2
|
bytes: 3.1.2
|
||||||
content-type: 1.0.5
|
content-type: 1.0.5
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
http-errors: 2.0.1
|
http-errors: 2.0.1
|
||||||
iconv-lite: 0.7.2
|
iconv-lite: 0.7.2
|
||||||
on-finished: 2.4.1
|
on-finished: 2.4.1
|
||||||
@ -7476,7 +7529,7 @@ snapshots:
|
|||||||
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)):
|
eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.4(jiti@2.6.1)):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@nolyfill/is-core-module': 1.0.39
|
'@nolyfill/is-core-module': 1.0.39
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
eslint: 9.39.4(jiti@2.6.1)
|
eslint: 9.39.4(jiti@2.6.1)
|
||||||
get-tsconfig: 4.13.7
|
get-tsconfig: 4.13.7
|
||||||
is-bun-module: 2.0.0
|
is-bun-module: 2.0.0
|
||||||
@ -7640,7 +7693,7 @@ snapshots:
|
|||||||
ajv: 6.12.6
|
ajv: 6.12.6
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
eslint-scope: 8.4.0
|
eslint-scope: 8.4.0
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
@ -7681,7 +7734,7 @@ snapshots:
|
|||||||
ajv: 6.14.0
|
ajv: 6.14.0
|
||||||
chalk: 4.1.2
|
chalk: 4.1.2
|
||||||
cross-spawn: 7.0.6
|
cross-spawn: 7.0.6
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
escape-string-regexp: 4.0.0
|
escape-string-regexp: 4.0.0
|
||||||
eslint-scope: 8.4.0
|
eslint-scope: 8.4.0
|
||||||
eslint-visitor-keys: 4.2.1
|
eslint-visitor-keys: 4.2.1
|
||||||
@ -7793,7 +7846,7 @@ snapshots:
|
|||||||
content-type: 1.0.5
|
content-type: 1.0.5
|
||||||
cookie: 0.7.2
|
cookie: 0.7.2
|
||||||
cookie-signature: 1.2.2
|
cookie-signature: 1.2.2
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
depd: 2.0.0
|
depd: 2.0.0
|
||||||
encodeurl: 2.0.0
|
encodeurl: 2.0.0
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
@ -7891,7 +7944,7 @@ snapshots:
|
|||||||
|
|
||||||
finalhandler@2.1.1:
|
finalhandler@2.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
encodeurl: 2.0.0
|
encodeurl: 2.0.0
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
on-finished: 2.4.1
|
on-finished: 2.4.1
|
||||||
@ -8095,6 +8148,13 @@ snapshots:
|
|||||||
jsprim: 2.0.2
|
jsprim: 2.0.2
|
||||||
sshpk: 1.18.0
|
sshpk: 1.18.0
|
||||||
|
|
||||||
|
https-proxy-agent@7.0.6:
|
||||||
|
dependencies:
|
||||||
|
agent-base: 7.1.4
|
||||||
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
https-proxy-agent@7.0.6(supports-color@10.2.2):
|
https-proxy-agent@7.0.6(supports-color@10.2.2):
|
||||||
dependencies:
|
dependencies:
|
||||||
agent-base: 7.1.4
|
agent-base: 7.1.4
|
||||||
@ -8708,6 +8768,8 @@ snapshots:
|
|||||||
define-properties: 1.2.1
|
define-properties: 1.2.1
|
||||||
es-object-atoms: 1.1.1
|
es-object-atoms: 1.1.1
|
||||||
|
|
||||||
|
on-exit-leak-free@2.1.2: {}
|
||||||
|
|
||||||
on-finished@2.4.1:
|
on-finished@2.4.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
ee-first: 1.1.1
|
ee-first: 1.1.1
|
||||||
@ -8839,6 +8901,26 @@ snapshots:
|
|||||||
|
|
||||||
pify@2.3.0: {}
|
pify@2.3.0: {}
|
||||||
|
|
||||||
|
pino-abstract-transport@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
split2: 4.2.0
|
||||||
|
|
||||||
|
pino-std-serializers@7.1.0: {}
|
||||||
|
|
||||||
|
pino@10.3.1:
|
||||||
|
dependencies:
|
||||||
|
'@pinojs/redact': 0.4.0
|
||||||
|
atomic-sleep: 1.0.0
|
||||||
|
on-exit-leak-free: 2.1.2
|
||||||
|
pino-abstract-transport: 3.0.0
|
||||||
|
pino-std-serializers: 7.1.0
|
||||||
|
process-warning: 5.0.0
|
||||||
|
quick-format-unescaped: 4.0.4
|
||||||
|
real-require: 0.2.0
|
||||||
|
safe-stable-stringify: 2.5.0
|
||||||
|
sonic-boom: 4.2.1
|
||||||
|
thread-stream: 4.0.0
|
||||||
|
|
||||||
pkce-challenge@5.0.1: {}
|
pkce-challenge@5.0.1: {}
|
||||||
|
|
||||||
pluralize@8.0.0: {}
|
pluralize@8.0.0: {}
|
||||||
@ -8880,6 +8962,8 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
parse-ms: 4.0.0
|
parse-ms: 4.0.0
|
||||||
|
|
||||||
|
process-warning@5.0.0: {}
|
||||||
|
|
||||||
process@0.11.10: {}
|
process@0.11.10: {}
|
||||||
|
|
||||||
prompts@2.4.2:
|
prompts@2.4.2:
|
||||||
@ -8917,6 +9001,8 @@ snapshots:
|
|||||||
|
|
||||||
queue-microtask@1.2.3: {}
|
queue-microtask@1.2.3: {}
|
||||||
|
|
||||||
|
quick-format-unescaped@4.0.4: {}
|
||||||
|
|
||||||
radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
radix-ui@1.4.3(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@radix-ui/primitive': 1.1.3
|
'@radix-ui/primitive': 1.1.3
|
||||||
@ -9058,6 +9144,8 @@ snapshots:
|
|||||||
|
|
||||||
react@19.2.4: {}
|
react@19.2.4: {}
|
||||||
|
|
||||||
|
real-require@0.2.0: {}
|
||||||
|
|
||||||
recast@0.23.11:
|
recast@0.23.11:
|
||||||
dependencies:
|
dependencies:
|
||||||
ast-types: 0.16.1
|
ast-types: 0.16.1
|
||||||
@ -9156,7 +9244,7 @@ snapshots:
|
|||||||
|
|
||||||
router@2.2.0:
|
router@2.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
depd: 2.0.0
|
depd: 2.0.0
|
||||||
is-promise: 4.0.0
|
is-promise: 4.0.0
|
||||||
parseurl: 1.3.3
|
parseurl: 1.3.3
|
||||||
@ -9195,6 +9283,8 @@ snapshots:
|
|||||||
es-errors: 1.3.0
|
es-errors: 1.3.0
|
||||||
is-regex: 1.2.1
|
is-regex: 1.2.1
|
||||||
|
|
||||||
|
safe-stable-stringify@2.5.0: {}
|
||||||
|
|
||||||
safer-buffer@2.1.2: {}
|
safer-buffer@2.1.2: {}
|
||||||
|
|
||||||
scheduler@0.27.0: {}
|
scheduler@0.27.0: {}
|
||||||
@ -9205,7 +9295,7 @@ snapshots:
|
|||||||
|
|
||||||
send@1.2.1:
|
send@1.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
debug: 4.4.3(supports-color@10.2.2)
|
debug: 4.4.3(supports-color@8.1.1)
|
||||||
encodeurl: 2.0.0
|
encodeurl: 2.0.0
|
||||||
escape-html: 1.0.3
|
escape-html: 1.0.3
|
||||||
etag: 1.8.1
|
etag: 1.8.1
|
||||||
@ -9273,7 +9363,7 @@ snapshots:
|
|||||||
fast-glob: 3.3.3
|
fast-glob: 3.3.3
|
||||||
fs-extra: 11.3.4
|
fs-extra: 11.3.4
|
||||||
fuzzysort: 3.1.0
|
fuzzysort: 3.1.0
|
||||||
https-proxy-agent: 7.0.6(supports-color@10.2.2)
|
https-proxy-agent: 7.0.6
|
||||||
kleur: 4.1.5
|
kleur: 4.1.5
|
||||||
msw: 2.12.14(@types/node@25.5.0)(typescript@5.9.3)
|
msw: 2.12.14(@types/node@25.5.0)(typescript@5.9.3)
|
||||||
node-fetch: 3.3.2
|
node-fetch: 3.3.2
|
||||||
@ -9381,6 +9471,10 @@ snapshots:
|
|||||||
astral-regex: 2.0.0
|
astral-regex: 2.0.0
|
||||||
is-fullwidth-code-point: 3.0.0
|
is-fullwidth-code-point: 3.0.0
|
||||||
|
|
||||||
|
sonic-boom@4.2.1:
|
||||||
|
dependencies:
|
||||||
|
atomic-sleep: 1.0.0
|
||||||
|
|
||||||
sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
|
||||||
dependencies:
|
dependencies:
|
||||||
react: 19.2.4
|
react: 19.2.4
|
||||||
@ -9390,6 +9484,8 @@ snapshots:
|
|||||||
|
|
||||||
source-map@0.6.1: {}
|
source-map@0.6.1: {}
|
||||||
|
|
||||||
|
split2@4.2.0: {}
|
||||||
|
|
||||||
sshpk@1.18.0:
|
sshpk@1.18.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
asn1: 0.2.6
|
asn1: 0.2.6
|
||||||
@ -9530,6 +9626,10 @@ snapshots:
|
|||||||
|
|
||||||
tapable@2.3.0: {}
|
tapable@2.3.0: {}
|
||||||
|
|
||||||
|
thread-stream@4.0.0:
|
||||||
|
dependencies:
|
||||||
|
real-require: 0.2.0
|
||||||
|
|
||||||
throttleit@1.0.1: {}
|
throttleit@1.0.1: {}
|
||||||
|
|
||||||
through@2.3.8: {}
|
through@2.3.8: {}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user