From 020ffccfd614f784a73ba18e9fc4250481fcbb8e Mon Sep 17 00:00:00 2001 From: Mohammad Khyata Date: Mon, 13 Apr 2026 18:48:15 +0300 Subject: [PATCH] finish bills , po , jobcards relational fields creation --- .../app/(authenticated)/items/parts/page.tsx | 73 +- .../purchase/purchase-order/[id]/layout.tsx | 44 + .../purchase/purchase-order/[id]/page.tsx | 21 + .../purchase/purchase-order/page.tsx | 6 +- apps/dashboard/modules/bills/bill-form.tsx | 77 +- apps/dashboard/modules/bills/bill.schema.ts | 36 +- .../modules/customers/customer-form.tsx | 6 +- .../customers/rhf-customer-select-field.tsx | 71 +- .../modules/estimates/estimate-form.tsx | 56 +- .../modules/estimates/estimate.schema.ts | 16 +- .../expense-items/expense-items-columns.tsx | 27 + .../expense-items-selector-field.tsx | 140 + apps/dashboard/modules/parts/part-form.tsx | 16 + apps/dashboard/modules/parts/part.schema.ts | 1 + .../dashboard/modules/parts/parts-columns.tsx | 71 + .../modules/parts/parts-selector-field.tsx | 142 + .../create-bill-from-po-button.tsx | 80 + .../purchase-order-actions.tsx | 58 + .../purchase-order-context.tsx | 29 + .../purchase-orders/purchase-order-form.tsx | 23 +- .../purchase-order-general-info.tsx | 288 ++ .../purchase-orders/purchase-order.schema.ts | 18 +- .../modules/services/services-columns.tsx | 37 + .../services/services-selector-field.tsx | 140 + .../vehicles/rhf-vehicle-select-field.tsx | 72 +- .../modules/vehicles/vehicle-form.tsx | 6 +- .../form/fields/rhf-auto-generate-field.tsx | 4 +- .../shared/components/form/rhform.tsx | 3 +- .../components/resource-selector/index.ts | 2 + .../resource-selector-dialog.tsx | 93 + .../resource-selector/rhf-resource-field.tsx | 131 + .../data-view/resource-page/crud-resource.tsx | 9 +- .../data-view/table-view/data-table.tsx | 86 +- .../shared/data-view/table-view/types.ts | 7 + docs/dashboard/feature-checklist.md | 265 +- docs/dashboard/phase-1-comparison.md | 147 + .../swagger-client-frontend-gap-report.md | 237 ++ packages/api/open-api/schema.json | 3790 ++++++++++++++++- packages/api/postman/collection.json | 3145 +++++++++++++- packages/api/src/clients/purchase-orders.ts | 5 + packages/api/types/index.ts | 2962 ++++++++++++- 41 files changed, 11973 insertions(+), 467 deletions(-) create mode 100644 apps/dashboard/app/(authenticated)/purchase/purchase-order/[id]/layout.tsx create mode 100644 apps/dashboard/app/(authenticated)/purchase/purchase-order/[id]/page.tsx create mode 100644 apps/dashboard/modules/expense-items/expense-items-columns.tsx create mode 100644 apps/dashboard/modules/expense-items/expense-items-selector-field.tsx create mode 100644 apps/dashboard/modules/parts/parts-columns.tsx create mode 100644 apps/dashboard/modules/parts/parts-selector-field.tsx create mode 100644 apps/dashboard/modules/purchase-orders/create-bill-from-po-button.tsx create mode 100644 apps/dashboard/modules/purchase-orders/purchase-order-actions.tsx create mode 100644 apps/dashboard/modules/purchase-orders/purchase-order-context.tsx create mode 100644 apps/dashboard/modules/purchase-orders/purchase-order-general-info.tsx create mode 100644 apps/dashboard/modules/services/services-columns.tsx create mode 100644 apps/dashboard/modules/services/services-selector-field.tsx create mode 100644 apps/dashboard/shared/components/resource-selector/index.ts create mode 100644 apps/dashboard/shared/components/resource-selector/resource-selector-dialog.tsx create mode 100644 apps/dashboard/shared/components/resource-selector/rhf-resource-field.tsx create mode 100644 docs/dashboard/phase-1-comparison.md create mode 100644 docs/dashboard/swagger-client-frontend-gap-report.md diff --git a/apps/dashboard/app/(authenticated)/items/parts/page.tsx b/apps/dashboard/app/(authenticated)/items/parts/page.tsx index 054d1cc..d3806d0 100644 --- a/apps/dashboard/app/(authenticated)/items/parts/page.tsx +++ b/apps/dashboard/app/(authenticated)/items/parts/page.tsx @@ -1,12 +1,11 @@ "use client" import { ResourcePage } from "@/shared/data-view/resource-page" -import { ColumnHeader } from "@/shared/data-view/table-view" import FormDialog from "@/shared/components/form-dialog" import { ImportDataButton } from "@/shared/components/import-data-button" import { ExportDataButton } from "@/shared/components/export-data-button" import { PartForm } from "@/modules/parts/part-form" -import { Badge } from "@/shared/components/ui/badge" +import { partColumns } from "@/modules/parts/parts-columns" import { useAuthApi } from "@/shared/useApi" import { PARTS_ROUTES } from "@garage/api" import type { PartsClient } from "@garage/api" @@ -43,69 +42,17 @@ export default function PartsPage() { ), })} columns={({ actionsColumn }) => [ - { - accessorKey: "title", - header: ({ column }) => , - cell: ({ row }) => { - const r = row.original as any - return ( -
- {r.title || "—"} - {r.sku && ( - {r.sku} - )} -
- ) - }, - }, - { - accessorKey: "part_number", - header: ({ column }) => , - cell: ({ row }) => (row.original as any).part_number || "—", - }, - { - accessorKey: "manufactured_by", - header: ({ column }) => , - cell: ({ row }) => (row.original as any).manufactured_by || "—", - }, - { - accessorKey: "selling_price", - header: ({ column }) => , - cell: ({ row }) => { - const val = (row.original as any).selling_price - return val != null ? `$${Number(val).toFixed(2)}` : "—" - }, - }, - { - accessorKey: "purchase_price", - header: ({ column }) => , - cell: ({ row }) => { - const val = (row.original as any).purchase_price - return val != null ? `$${Number(val).toFixed(2)}` : "—" - }, - }, - { - accessorKey: "is_active", - header: ({ column }) => , - cell: ({ row }) => { - const active = (row.original as any).is_active - return ( - - {active ? "Active" : "Inactive"} - - ) - }, - }, - { - accessorKey: "created_at", - header: ({ column }) => , - cell: ({ row }) => { - const val = (row.original as any).created_at - return val ? new Date(val).toLocaleDateString() : "—" - }, - }, + partColumns.title, + partColumns.partNumber, + partColumns.manufacturer, + partColumns.sellingPrice, + partColumns.purchasePrice, + partColumns.stock, + partColumns.status, + partColumns.createdAt, actionsColumn(), ]} + /> ) } diff --git a/apps/dashboard/app/(authenticated)/purchase/purchase-order/[id]/layout.tsx b/apps/dashboard/app/(authenticated)/purchase/purchase-order/[id]/layout.tsx new file mode 100644 index 0000000..8ce7e11 --- /dev/null +++ b/apps/dashboard/app/(authenticated)/purchase/purchase-order/[id]/layout.tsx @@ -0,0 +1,44 @@ +import { DashboardDetailsPage } from '@/base/components/layout/dashboard' +import { getServerApi } from '@garage/api/server' +import { PurchaseOrderActions } from '@/modules/purchase-orders/purchase-order-actions' +import { PurchaseOrderProvider } from '@/modules/purchase-orders/purchase-order-context' +import { CreateBillFromPOButton } from '@/modules/purchase-orders/create-bill-from-po-button' +import { ClipboardList } from 'lucide-react' +import React from 'react' + +export default async function layout(props: { + params: Promise<{ id: string }> + children: React.ReactNode +}) { + const { id } = await props.params + const api = await getServerApi() + const purchaseOrder = (await api.purchaseOrders.getById(id)) as any + + const data = purchaseOrder?.data ?? purchaseOrder + const title = data?.title || data?.order_number || 'Purchase Order' + const orderNumber = data?.order_number + const description = orderNumber ? `Order #: ${orderNumber}` : undefined + + return ( + + } + title={title} + description={description} + backHref="/purchase/purchase-order" + actions={ +
+ + +
+ } + tabs={[ + { href: `/purchase/purchase-order/${id}`, label: 'Details' }, + ]} + > + {props.children} +
+
+ ) +} diff --git a/apps/dashboard/app/(authenticated)/purchase/purchase-order/[id]/page.tsx b/apps/dashboard/app/(authenticated)/purchase/purchase-order/[id]/page.tsx new file mode 100644 index 0000000..f6d77de --- /dev/null +++ b/apps/dashboard/app/(authenticated)/purchase/purchase-order/[id]/page.tsx @@ -0,0 +1,21 @@ +import { getServerApi } from '@garage/api/server' +import { PurchaseOrderGeneralInfo } from '@/modules/purchase-orders/purchase-order-general-info' +import DashboardPage from '@/base/components/layout/dashboard/dashboard-page' + +export default async function page(props: { params: Promise<{ id: string }> }) { + const { id } = await props.params + const api = await getServerApi() + const response = await api.purchaseOrders.getById(id) + + const purchaseOrder = (response as any)?.data ?? response + + if (!purchaseOrder) { + return
Purchase order not found.
+ } + + return ( + + + + ) +} diff --git a/apps/dashboard/app/(authenticated)/purchase/purchase-order/page.tsx b/apps/dashboard/app/(authenticated)/purchase/purchase-order/page.tsx index 28fa0d6..94911ec 100644 --- a/apps/dashboard/app/(authenticated)/purchase/purchase-order/page.tsx +++ b/apps/dashboard/app/(authenticated)/purchase/purchase-order/page.tsx @@ -1,5 +1,6 @@ "use client" +import { useRouter } from "next/navigation" import { ResourcePage } from "@/shared/data-view/resource-page" import { ColumnHeader } from "@/shared/data-view/table-view" import FormDialog from "@/shared/components/form-dialog" @@ -8,14 +9,17 @@ import { PURCHASE_ORDER_ROUTES } from "@garage/api" import type { PurchaseOrdersClient } from "@garage/api" export default function PurchaseOrdersPage() { + const router = useRouter() + return ( pageTitle="Purchase Orders" routeKey={PURCHASE_ORDER_ROUTES.INDEX} getClient={(api) => api.purchaseOrders} + onRowClick={(row) => router.push(`/purchase/purchase-order/${(row as any).id}`)} headerProps={({ selectedItem, invalidateQuery }) => ({ actions: ( - + {(resourceId) => ( ({ @@ -56,14 +64,37 @@ function mapToFormValues(data: unknown): BillFormValues { return { vendor: toRelation(d.vendor_id, d.vendor_name), + purchase_order: toRelation(d.purchase_order_id, d.purchase_order_number ?? d.purchase_order_title), job_card: toRelation(d.job_card_id, d.job_card_number ?? d.job_card_name), payment_term: toRelation(d.payment_terms_id, d.payment_terms_name), department: toRelation(d.department_id, d.department_name), title: d.title || "", + bill_number: d.bill_number || "", bill_date: d.bill_date || "", bill_due_date: d.bill_due_date || "", status: d.status || "draft", notes: d.notes || "", + part_items: (d.parts ?? []).map((p: any) => ({ + part_id: p.part_id ?? p.id, + title: p.part?.title ?? p.title ?? "", + quantity: Number(p.quantity) || 1, + rate: Number(p.rate) || 0, + description: p.description ?? "", + })), + service_items: (d.services ?? []).map((s: any) => ({ + service_id: s.service_id ?? s.id, + title: s.service?.labor_name ?? s.labor_name ?? s.title ?? "", + quantity: Number(s.quantity) || 1, + rate: Number(s.rate) || 0, + description: s.description ?? "", + })), + expense_items: (d.expenses ?? []).map((e: any) => ({ + expense_id: e.expense_id ?? e.id, + title: e.expense?.item_name ?? e.item_name ?? e.title ?? "", + quantity: Number(e.quantity) || 1, + rate: Number(e.rate) || 0, + description: e.description ?? "", + })), } } @@ -71,13 +102,33 @@ function mapFormToPayload(values: BillFormValues) { return { title: values.title, vendor_id: toId(values.vendor), + purchase_order_id: toId(values.purchase_order), job_card_id: toId(values.job_card), payment_terms_id: toId(values.payment_term), department_id: toId(values.department), + bill_number: values.bill_number || undefined, bill_date: values.bill_date || undefined, bill_due_date: values.bill_due_date || undefined, status: values.status || undefined, notes: values.notes || undefined, + part_items: (values.part_items ?? []).map((item) => ({ + part_id: item.part_id, + quantity: item.quantity, + rate: item.rate, + description: item.description || undefined, + })), + service_items: (values.service_items ?? []).map((item) => ({ + service_id: item.service_id, + quantity: item.quantity, + rate: item.rate, + description: item.description || undefined, + })), + expense_items: (values.expense_items ?? []).map((item) => ({ + expense_id: item.expense_id, + quantity: item.quantity, + rate: item.rate, + description: item.description || undefined, + })), } } @@ -126,6 +177,11 @@ export function BillForm({ resourceId, initialData, onSuccess }: BillFormProps) +
+ + +
+
@@ -173,9 +229,25 @@ export function BillForm({ resourceId, initialData, onSuccess }: BillFormProps) />
- + api.purchaseOrders.list()} + mapOption={(item: any) => ({ + value: String(item.id), + label: item.order_number || item.title || `#${item.id}`, + })} + {...STORE_OBJECT} + /> + + name="part_items" /> + name="service_items" /> + name="expense_items" /> + + + )} + {combobox} + {description && {description}} + {error?.message && {error.message}} + + { if (!v) setIsCreateOpen(false) }}> + + + + Add {label} + + + + + + + + ) } diff --git a/apps/dashboard/modules/estimates/estimate-form.tsx b/apps/dashboard/modules/estimates/estimate-form.tsx index 547cc94..4f94f4e 100644 --- a/apps/dashboard/modules/estimates/estimate-form.tsx +++ b/apps/dashboard/modules/estimates/estimate-form.tsx @@ -4,14 +4,13 @@ import { AlertTriangle, Plus, Save } from "lucide-react" import { Button } from "@/shared/components/ui/button" import { Alert, AlertTitle } from "@/shared/components/ui/alert" -import { FieldGroup } from "@/shared/components/ui/field" import { Rhform, RhfTextField, - RhfTextareaField, RhfCheckboxField, RhfAsyncSelectField, - RhfAsyncMultiSelectField, + RhfDateField, + RhfAutoGenerateField, } from "@/shared/components/form" import { toast } from "sonner" import { useAuthApi } from "@/shared/useApi" @@ -23,9 +22,11 @@ import { estimateFormSchema, type EstimateFormValues, } from "./estimate.schema" -import { ESTIMATE_ROUTES, DEPARTMENT_ROUTES, LABEL_ROUTES } from "@garage/api" +import { ESTIMATE_ROUTES, DEPARTMENT_ROUTES } from "@garage/api" import { RhfCustomerSelectField } from "@/modules/customers/rhf-customer-select-field" import { RhfVehicleSelectField } from "@/modules/vehicles/rhf-vehicle-select-field" +import { RhfLabelPickerField, type LabelItem } from "@/modules/labels/rhf-label-picker-field" +import { RhfCustomerRemarksField } from "./rhf-customer-remarks-field" // ── Props ── @@ -46,7 +47,7 @@ const DEFAULT_VALUES: EstimateFormValues = { estimate_number: "", date: "", has_insurance: false, - remarks: "", + remarks: [], labels: [], } @@ -61,12 +62,16 @@ function mapToFormValues(data: unknown): EstimateFormValues { vehicle: toRelation(d.vehicle_id, d.vehicle_name), department: toRelation(d.department_id, d.department_name), estimate_number: d.estimate_number || "", - date: d.date || "", + date: d.date ? d.date.split("T")[0] : "", has_insurance: d.has_insurance ?? false, - remarks: Array.isArray(d.remarks) ? d.remarks.join("\n") : d.remarks || "", - labels: Array.isArray(d.labels) - ? d.labels.map((l: any) => ({ value: String(l.id), label: l.name })) + remarks: Array.isArray(d.remarks) + ? d.remarks.map((r: any) => (typeof r === "string" ? r : (r?.remark ?? ""))).filter(Boolean) : [], + labels: (d.labels ?? []).map((l: any): LabelItem => ({ + id: l.id, + title: l.title ?? l.name ?? "", + color_code: l.color_code ?? "", + })), } } @@ -79,10 +84,8 @@ function mapFormToPayload(values: EstimateFormValues) { estimate_number: values.estimate_number || undefined, date: values.date || undefined, has_insurance: values.has_insurance, - remarks: values.remarks - ? values.remarks.split("\n").filter(Boolean) - : [], - label_ids: values.labels.map((l) => Number(l.value)), + remarks: values.remarks?.filter(Boolean) ?? [], + label_ids: values.labels?.map((l) => l.id) ?? [], } } @@ -140,20 +143,17 @@ export function EstimateForm({ resourceId, initialData, onSuccess }: EstimateFor )} - - +
+ -
- - -
+
-
+
- api.labels.list()} - mapOption={mapLookupOption} - {...STORE_OBJECT} - /> + +
- + - +
) } diff --git a/apps/dashboard/modules/estimates/estimate.schema.ts b/apps/dashboard/modules/estimates/estimate.schema.ts index b59b956..b6d6a47 100644 --- a/apps/dashboard/modules/estimates/estimate.schema.ts +++ b/apps/dashboard/modules/estimates/estimate.schema.ts @@ -17,12 +17,20 @@ const estimateFormSchema = z.object({ estimate_number: z.string().optional(), date: z.string().optional(), has_insurance: z.boolean().default(false), - remarks: z.string().optional(), - // ── Multi-select relations ── + // ── Remarks (array of strings) ── + remarks: z.array(z.string()).optional(), + + // ── Labels ── labels: z - .array(z.object({ value: z.string(), label: z.string() })) - .default([]), + .array( + z.object({ + id: z.number(), + title: z.string(), + color_code: z.string(), + }), + ) + .optional(), }) type EstimateFormValues = z.infer diff --git a/apps/dashboard/modules/expense-items/expense-items-columns.tsx b/apps/dashboard/modules/expense-items/expense-items-columns.tsx new file mode 100644 index 0000000..86daeed --- /dev/null +++ b/apps/dashboard/modules/expense-items/expense-items-columns.tsx @@ -0,0 +1,27 @@ +import { ColumnHeader } from "@/shared/data-view/table-view" +import type { ColumnDef } from "@tanstack/react-table" + +/** Core expense-item columns shared between the expense items page and selector dialogs. */ +export const expenseItemColumns = { + name: { + accessorKey: "item_name", + header: ({ column }) => , + cell: ({ row }) => { + const r = row.original as any + return {r.item_name || "—"} + }, + }, + purchasePrice: { + accessorKey: "purchase_price", + header: ({ column }) => , + cell: ({ row }) => { + const val = (row.original as any).purchase_price + return val != null ? `$${Number(val).toFixed(2)}` : "—" + }, + }, + chartOfAccount: { + accessorKey: "purchase_chart_of_account", + header: ({ column }) => , + cell: ({ row }) => (row.original as any).purchase_chart_of_account || "—", + }, +} satisfies Record> diff --git a/apps/dashboard/modules/expense-items/expense-items-selector-field.tsx b/apps/dashboard/modules/expense-items/expense-items-selector-field.tsx new file mode 100644 index 0000000..aeea3bb --- /dev/null +++ b/apps/dashboard/modules/expense-items/expense-items-selector-field.tsx @@ -0,0 +1,140 @@ +"use client" + +import type { FieldValues, FieldPath } from "react-hook-form" +import { Trash2 } from "lucide-react" +import { Button } from "@/shared/components/ui/button" +import { Input } from "@/shared/components/ui/input" +import { + Table, + TableHeader, + TableBody, + TableHead, + TableRow, + TableCell, +} from "@/shared/components/ui/table" +import { RhfResourceField } from "@/shared/components/resource-selector" +import { expenseItemColumns } from "./expense-items-columns" +import { EXPENSE_ITEM_ROUTES } from "@garage/api" +import type { ExpenseItemsClient } from "@garage/api" + +type ExpenseLineItem = { + expense_id: number + title: string + quantity: number + rate: number + description?: string +} + +type ExpenseItemsFieldConstraint = ExpenseLineItem[] | undefined + +export type ExpenseItemsSelectorFieldProps< + TValues extends FieldValues, + TName extends FieldPath, +> = { + name: TName & (TValues[TName] extends ExpenseItemsFieldConstraint ? TName : never) + label?: string + triggerLabel?: string +} + +export function ExpenseItemsSelectorField< + TValues extends FieldValues, + TName extends FieldPath, +>({ + name, + label = "Expense Items", + triggerLabel = "Add Expense Items", +}: ExpenseItemsSelectorFieldProps) { + return ( + + name={name} + label={label} + triggerLabel={triggerLabel} + itemKey="expense_id" + dialogProps={{ + title: "Select Expense Items", + crudProps: { + routeKey: EXPENSE_ITEM_ROUTES.INDEX, + getClient: (api) => api.expenseItems, + columns: [ + expenseItemColumns.name, + expenseItemColumns.purchasePrice, + expenseItemColumns.chartOfAccount, + ], + }, + }} + mapSelected={(row) => { + const r = row as any + return { + expense_id: r.id, + title: r.item_name || "", + quantity: 1, + rate: Number(r.purchase_price) || 0, + description: "", + } as any + }} + renderItems={(items, { remove, update }) => ( + + + + Expense Item + Qty + Rate + Description + + + + + {((items as ExpenseLineItem[] | undefined) ?? []).map((item, index) => ( + + {item.title} + + + update(index, { ...item, quantity: Number(e.target.value) || 1 } as any) + } + className="h-8 w-20" + /> + + + + update(index, { ...item, rate: Number(e.target.value) || 0 } as any) + } + className="h-8 w-24" + /> + + + + update(index, { ...item, description: e.target.value } as any) + } + placeholder="Optional description" + className="h-8" + /> + + + + + + ))} + +
+ )} + /> + ) +} diff --git a/apps/dashboard/modules/parts/part-form.tsx b/apps/dashboard/modules/parts/part-form.tsx index 3f7869f..ed61758 100644 --- a/apps/dashboard/modules/parts/part-form.tsx +++ b/apps/dashboard/modules/parts/part-form.tsx @@ -44,6 +44,8 @@ const DEFAULT_VALUES: PartFormValues = { description: "", selling_price: undefined, purchase_price: undefined, + opening_stock: undefined, + } // ── Mapping helpers ── @@ -68,6 +70,7 @@ function mapToFormValues(data: unknown): PartFormValues { description: d.description ?? "", selling_price: d.selling_price ?? undefined, purchase_price: d.purchase_price ?? undefined, + opening_stock: d.opening_stock ?? undefined, } } @@ -82,6 +85,8 @@ function mapCreatePayload(values: PartFormValues) { description: values.description || undefined, selling_price: values.selling_price, purchase_price: values.purchase_price, + opening_stock: values.opening_stock, + available_stock: values.opening_stock, } } @@ -221,6 +226,17 @@ export function PartForm({ resourceId, initialData, onSuccess }: PartFormProps)
+ {!isEditing && ( +
+ +
+ )} + diff --git a/apps/dashboard/modules/parts/parts-columns.tsx b/apps/dashboard/modules/parts/parts-columns.tsx new file mode 100644 index 0000000..e736927 --- /dev/null +++ b/apps/dashboard/modules/parts/parts-columns.tsx @@ -0,0 +1,71 @@ +import { ColumnHeader } from "@/shared/data-view/table-view" +import { Badge } from "@/shared/components/ui/badge" +import type { ColumnDef } from "@tanstack/react-table" + +/** Core part columns shared between the parts page and selector dialogs. */ +export const partColumns = { + title: { + accessorKey: "title", + header: ({ column }) => , + cell: ({ row }) => { + const r = row.original as any + return ( +
+ {r.title || "—"} + {r.sku && {r.sku}} +
+ ) + }, + }, + partNumber: { + accessorKey: "part_number", + header: ({ column }) => , + cell: ({ row }) => (row.original as any).part_number || "—", + }, + manufacturer: { + accessorKey: "manufactured_by", + header: ({ column }) => , + cell: ({ row }) => (row.original as any).manufactured_by || "—", + }, + sellingPrice: { + accessorKey: "selling_price", + header: ({ column }) => , + cell: ({ row }) => { + const val = (row.original as any).selling_price + return val != null ? `$${Number(val).toFixed(2)}` : "—" + }, + }, + purchasePrice: { + accessorKey: "purchase_price", + header: ({ column }) => , + cell: ({ row }) => { + const val = (row.original as any).purchase_price + return val != null ? `$${Number(val).toFixed(2)}` : "—" + }, + }, + stock: { + accessorKey: "available_stock", + header: ({ column }) => , + cell: ({ row }) => (row.original as any).available_stock ?? "—", + }, + status: { + accessorKey: "is_active", + header: ({ column }) => , + cell: ({ row }) => { + const active = (row.original as any).is_active + return ( + + {active ? "Active" : "Inactive"} + + ) + }, + }, + createdAt: { + accessorKey: "created_at", + header: ({ column }) => , + cell: ({ row }) => { + const val = (row.original as any).created_at + return val ? new Date(val).toLocaleDateString() : "—" + }, + }, +} satisfies Record> diff --git a/apps/dashboard/modules/parts/parts-selector-field.tsx b/apps/dashboard/modules/parts/parts-selector-field.tsx new file mode 100644 index 0000000..10e5d9f --- /dev/null +++ b/apps/dashboard/modules/parts/parts-selector-field.tsx @@ -0,0 +1,142 @@ +"use client" + +import type { FieldValues, FieldPath } from "react-hook-form" +import { Trash2 } from "lucide-react" +import { Button } from "@/shared/components/ui/button" +import { Input } from "@/shared/components/ui/input" +import { + Table, + TableHeader, + TableBody, + TableHead, + TableRow, + TableCell, +} from "@/shared/components/ui/table" +import { RhfResourceField } from "@/shared/components/resource-selector" +import { partColumns } from "./parts-columns" +import { PARTS_ROUTES } from "@garage/api" +import type { PartsClient } from "@garage/api" + +type PartItem = { + part_id: number + title: string + quantity: number + rate: number + description?: string +} + +type PartsItemsFieldConstraint = PartItem[] | undefined + +export type PartsSelectorFieldProps< + TValues extends FieldValues, + TName extends FieldPath, +> = { + name: TName & (TValues[TName] extends PartsItemsFieldConstraint ? TName : never) + label?: string + triggerLabel?: string +} + +export function PartsSelectorField< + TValues extends FieldValues, + TName extends FieldPath, +>({ + name, + label = "Parts", + triggerLabel = "Add Parts", +}: PartsSelectorFieldProps) { + return ( + + name={name} + label={label} + triggerLabel={triggerLabel} + itemKey="part_id" + dialogProps={{ + title: "Select Parts", + crudProps: { + routeKey: PARTS_ROUTES.INDEX, + getClient: (api) => api.parts, + columns: [ + partColumns.title, + partColumns.partNumber, + partColumns.manufacturer, + partColumns.purchasePrice, + partColumns.stock, + ], + }, + }} + mapSelected={(row) => { + const r = row as any + return { + part_id: r.id, + title: r.title || "", + quantity: 1, + rate: Number(r.purchase_price) || 0, + description: "", + } as any + }} + renderItems={(items, { remove, update }) => ( + + + + Part + Qty + Rate + Description + + + + + {((items as PartItem[] | undefined) ?? []).map((item, index) => ( + + {item.title} + + + update(index, { ...item, quantity: Number(e.target.value) || 1 } as any) + } + className="h-8 w-20" + /> + + + + update(index, { ...item, rate: Number(e.target.value) || 0 } as any) + } + className="h-8 w-24" + /> + + + + update(index, { ...item, description: e.target.value } as any) + } + placeholder="Optional description" + className="h-8" + /> + + + + + + ))} + +
+ )} + /> + ) +} diff --git a/apps/dashboard/modules/purchase-orders/create-bill-from-po-button.tsx b/apps/dashboard/modules/purchase-orders/create-bill-from-po-button.tsx new file mode 100644 index 0000000..2effd8c --- /dev/null +++ b/apps/dashboard/modules/purchase-orders/create-bill-from-po-button.tsx @@ -0,0 +1,80 @@ +"use client" + +import { useState } from "react" +import { FileText } from "lucide-react" +import { Button } from "@/shared/components/ui/button" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from "@/shared/components/ui/dialog" +import { ScrollArea } from "@/shared/components/ui/scroll-area" +import { BillForm } from "@/modules/bills/bill-form" +import { toRelation } from "@/shared/lib/utils" +import { usePurchaseOrder } from "./purchase-order-context" + +/** + * Maps a Purchase Order data object to a complete BillFormValues shape so that + * useResourceForm's shallow spread (`{ ...defaultValues, ...initialData }`) correctly + * pre-fills all relational and line-item fields. + */ +function mapPOToBillInitialData(po: Record) { + return { + // Text fields + title: po.title ?? "", + notes: po.notes ?? "", + + // Relation fields — must be { value, label } objects for RhfAsyncSelectField + vendor: toRelation(po.vendor_id, po.vendor_name), + department: toRelation(po.department_id, po.department_name), + job_card: toRelation(po.job_card_id, po.job_card_name ?? po.job_card_number), + // Link bill back to the source PO + purchase_order: toRelation(po.id, po.order_number ?? po.title), + + // Parts pre-filled from PO items + part_items: (po.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 and expenses start empty (PO doesn't have them) + service_items: [], + expense_items: [], + } +} + +export function CreateBillFromPOButton() { + const [open, setOpen] = useState(false) + const poContext = usePurchaseOrder() + + if (!poContext) return null + + const initialData = poContext.data ? mapPOToBillInitialData(poContext.data) : undefined + + return ( + <> + + + + + + Create Bill from Purchase Order + + + setOpen(false)} + /> + + + + + ) +} diff --git a/apps/dashboard/modules/purchase-orders/purchase-order-actions.tsx b/apps/dashboard/modules/purchase-orders/purchase-order-actions.tsx new file mode 100644 index 0000000..7c5cbef --- /dev/null +++ b/apps/dashboard/modules/purchase-orders/purchase-order-actions.tsx @@ -0,0 +1,58 @@ +"use client" + +import { useAuthApi } from "@/shared/useApi" +import { useRouter } from "next/navigation" +import { Button } from "@/shared/components/ui/button" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/shared/components/ui/dropdown-menu" +import { confirm } from "@/shared/components/confirm-dialog" +import { Ellipsis, Pencil, Trash2 } from "lucide-react" +import { toast } from "sonner" + +type PurchaseOrderActionsProps = { + purchaseOrderId: string +} + +export function PurchaseOrderActions({ purchaseOrderId }: PurchaseOrderActionsProps) { + const api = useAuthApi() + const router = useRouter() + + const handleDelete = async () => { + const confirmed = await confirm({ + title: "Delete this purchase order?", + description: "This action cannot be undone.", + confirmLabel: "Delete", + variant: "destructive", + }) + if (!confirmed) return + + const promise = api.purchaseOrders.destroy(purchaseOrderId) + toast.promise(promise, { + loading: "Deleting purchase order...", + success: "Purchase order deleted", + error: "Failed to delete purchase order", + }) + await promise + router.push("/purchase/purchase-order") + } + + return ( + + + + + + + + Delete + + + + ) +} diff --git a/apps/dashboard/modules/purchase-orders/purchase-order-context.tsx b/apps/dashboard/modules/purchase-orders/purchase-order-context.tsx new file mode 100644 index 0000000..24f8090 --- /dev/null +++ b/apps/dashboard/modules/purchase-orders/purchase-order-context.tsx @@ -0,0 +1,29 @@ +"use client" + +import { createContext, useContext } from "react" + +export type PurchaseOrderContextValue = { + id: string + label: string + data?: Record +} + +const PurchaseOrderContext = createContext(null) + +export function PurchaseOrderProvider({ + purchaseOrder, + children, +}: { + purchaseOrder: PurchaseOrderContextValue + children: React.ReactNode +}) { + return ( + + {children} + + ) +} + +export function usePurchaseOrder() { + return useContext(PurchaseOrderContext) +} diff --git a/apps/dashboard/modules/purchase-orders/purchase-order-form.tsx b/apps/dashboard/modules/purchase-orders/purchase-order-form.tsx index 817e2d6..410b395 100644 --- a/apps/dashboard/modules/purchase-orders/purchase-order-form.tsx +++ b/apps/dashboard/modules/purchase-orders/purchase-order-form.tsx @@ -12,6 +12,7 @@ import { RhfAsyncSelectField, RhfDateField, } from "@/shared/components/form" +import { PartsSelectorField } from "@/modules/parts/parts-selector-field" import { toast } from "sonner" import { useAuthApi } from "@/shared/useApi" import { useResourceForm } from "@/shared/hooks/use-resource-form" @@ -37,12 +38,14 @@ export type PurchaseOrderFormProps = { const DEFAULT_VALUES: PurchaseOrderFormValues = { vendor: null, + order_number: "" , job_card: null, department: null, title: "", order_date: new Date().toISOString().split("T")[0], delivery_date: "", notes: "", + items: [], } // ── Mapping helpers ── @@ -57,7 +60,15 @@ function mapToFormValues(data: unknown): PurchaseOrderFormValues { title: d.title || "", order_date: d.order_date || "", delivery_date: d.delivery_date || "", + order_number: d.order_number || "" as any, notes: d.notes || "", + items: (d.parts ?? []).map((p: any) => ({ + part_id: p.part_id ?? p.id, + title: p.part?.title ?? p.title ?? "", + quantity: p.quantity ?? 1, + rate: Number(p.rate) || 0, + description: p.description ?? "", + })), } } @@ -68,8 +79,15 @@ function mapFormToPayload(values: PurchaseOrderFormValues) { department_id: toId(values.department), title: values.title, order_date: values.order_date || undefined, + order_number: values.order_number, delivery_date: values.delivery_date || undefined, notes: values.notes || undefined, + items: (values.items ?? []).map((item) => ({ + part_id: item.part_id, + quantity: item.quantity, + rate: item.rate, + description: item.description || undefined, + })), } } @@ -127,9 +145,10 @@ export function PurchaseOrderForm({ resourceId, initialData, onSuccess }: Purcha )} -
+ +
@@ -170,6 +189,8 @@ export function PurchaseOrderForm({ resourceId, initialData, onSuccess }: Purcha + name="items" /> + + + )} + {combobox} + {description && {description}} + {error?.message && {error.message}} + + { if (!v) setIsCreateOpen(false) }}> + + + + Add {label} + + + + + + + + ) } diff --git a/apps/dashboard/modules/vehicles/vehicle-form.tsx b/apps/dashboard/modules/vehicles/vehicle-form.tsx index a0729c7..aa7f149 100644 --- a/apps/dashboard/modules/vehicles/vehicle-form.tsx +++ b/apps/dashboard/modules/vehicles/vehicle-form.tsx @@ -32,7 +32,7 @@ import { VEHICLE_ROUTES } from "@garage/api" export type VehicleFormProps = { resourceId?: string | null initialData?: unknown - onSuccess?: () => void + onSuccess?: (data?: any) => void } // ── Default values ── @@ -138,9 +138,9 @@ export function VehicleForm({ resourceId, initialData, onSuccess }: VehicleFormP }) return promise }, - onSuccess: () => { + onSuccess: (data) => { form.reset() - onSuccess?.() + onSuccess?.(data) }, }) diff --git a/apps/dashboard/shared/components/form/fields/rhf-auto-generate-field.tsx b/apps/dashboard/shared/components/form/fields/rhf-auto-generate-field.tsx index 14badda..ce2333c 100644 --- a/apps/dashboard/shared/components/form/fields/rhf-auto-generate-field.tsx +++ b/apps/dashboard/shared/components/form/fields/rhf-auto-generate-field.tsx @@ -6,7 +6,7 @@ import { useQuery } from "@tanstack/react-query" import { RefreshCw } from "lucide-react" import { useAuthApi } from "@/shared/useApi" -import { AUTO_GENERATE_ROUTES } from "@garage/api" +import { AUTO_GENERATE_ROUTES, Tables } from "@garage/api" import { FieldShell } from "../field-shell" import { Input } from "@/shared/components/ui/input" import { Button } from "@/shared/components/ui/button" @@ -21,7 +21,7 @@ type RhfAutoGenerateFieldProps< required?: boolean disabled?: boolean placeholder?: string - table: string + table: Tables /** When true, fetches the next code immediately on mount */ autoFetch?: boolean } diff --git a/apps/dashboard/shared/components/form/rhform.tsx b/apps/dashboard/shared/components/form/rhform.tsx index f235b9c..cac2f02 100644 --- a/apps/dashboard/shared/components/form/rhform.tsx +++ b/apps/dashboard/shared/components/form/rhform.tsx @@ -3,6 +3,7 @@ import type { FieldValues } from "react-hook-form" import { FormProvider } from "react-hook-form" import type { RhformProps } from "./types" +import { cn } from "@/shared/lib/utils" export function Rhform({ form, @@ -15,7 +16,7 @@ export function Rhform({
{ e.stopPropagation(); form.handleSubmit(onSubmit)(e) }} noValidate - className={className} + className={cn('p-1',className)} > {children}
diff --git a/apps/dashboard/shared/components/resource-selector/index.ts b/apps/dashboard/shared/components/resource-selector/index.ts new file mode 100644 index 0000000..5feb919 --- /dev/null +++ b/apps/dashboard/shared/components/resource-selector/index.ts @@ -0,0 +1,2 @@ +export { ResourceSelectorDialog, type ResourceSelectorDialogProps } from "./resource-selector-dialog" +export { RhfResourceField, type RhfResourceFieldProps } from "./rhf-resource-field" diff --git a/apps/dashboard/shared/components/resource-selector/resource-selector-dialog.tsx b/apps/dashboard/shared/components/resource-selector/resource-selector-dialog.tsx new file mode 100644 index 0000000..f481ebc --- /dev/null +++ b/apps/dashboard/shared/components/resource-selector/resource-selector-dialog.tsx @@ -0,0 +1,93 @@ +"use client" + +import { useState, useRef } from "react" +import { Button } from "@/shared/components/ui/button" +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from "@/shared/components/ui/dialog" +import { CrudResource, type CrudResourceProps } from "@/shared/data-view/resource-page" +import type { ResourcePageClient, ResourceItem } from "@/shared/data-view/resource-page" +import { Card, CardContent } from "@/shared/components/ui/card" + +export type ResourceSelectorDialogProps = { + /** Dialog title shown in the header */ + title: string + /** Opens/closes the dialog */ + open: boolean + onOpenChange: (open: boolean) => void + /** Called when user confirms selection */ + onConfirm: (selectedRows: ResourceItem[]) => void + /** CrudResource config for the table (columns, routeKey, getClient, etc.) */ + crudProps: Omit, "render" | "tableProps"> + /** Optional rowKey for selection identity. Defaults to "id" */ + rowKey?: keyof ResourceItem +} + +export function ResourceSelectorDialog({ + title, + open, + onOpenChange, + onConfirm, + crudProps, + rowKey, +}: ResourceSelectorDialogProps) { + type TItem = ResourceItem + + const selectedRef = useRef([]) + const [count, setCount] = useState(0) + + const handleSelectionChange = (rows: TItem[]) => { + selectedRef.current = rows + setCount(rows.length) + } + + const handleConfirm = () => { + onConfirm(selectedRef.current) + selectedRef.current = [] + setCount(0) + onOpenChange(false) + } + + const handleCancel = () => { + selectedRef.current = [] + setCount(0) + onOpenChange(false) + } + + return ( + { if (!v) handleCancel() }}> + + + {title} + +
+ + + + {...crudProps} + tableProps={{ + selection: { + onSelectionChange: handleSelectionChange, + ...(rowKey ? { rowKey } : {}), + }, + }} + /> + + +
+ + + + +
+
+ ) +} diff --git a/apps/dashboard/shared/components/resource-selector/rhf-resource-field.tsx b/apps/dashboard/shared/components/resource-selector/rhf-resource-field.tsx new file mode 100644 index 0000000..ff0f596 --- /dev/null +++ b/apps/dashboard/shared/components/resource-selector/rhf-resource-field.tsx @@ -0,0 +1,131 @@ +"use client" + +import { useState, type ReactNode } from "react" +import { useController, useFormContext, type FieldValues, type FieldPath } from "react-hook-form" +import { Button } from "@/shared/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/shared/components/ui/card" +import { Field, FieldError } from "@/shared/components/ui/field" +import { Plus } from "lucide-react" +import { + ResourceSelectorDialog, + type ResourceSelectorDialogProps, +} from "./resource-selector-dialog" +import type { ResourcePageClient, ResourceItem } from "@/shared/data-view/resource-page" + +export type RhfResourceFieldProps< + TValues extends FieldValues, + TName extends FieldPath, + TClient extends ResourcePageClient, +> = { + /** RHF field name — value is an array of items after mapping */ + name: TName + /** Label displayed on the card header */ + label: string + /** Button text for the trigger */ + triggerLabel?: string + /** Map a selected resource row into the form item shape */ + mapSelected: (row: ResourceItem) => TValues[TName][number] + /** Render the list of selected items inside the card */ + renderItems: ( + items: TValues[TName], + helpers: { + remove: (index: number) => void + update: (index: number, item: TValues[TName][number]) => void + replace: (items: TValues[TName]) => void + }, + ) => ReactNode + /** Props forwarded to ResourceSelectorDialog (columns, routeKey, getClient, etc.) */ + dialogProps: Omit< + ResourceSelectorDialogProps, + "open" | "onOpenChange" | "onConfirm" + > + /** Deduplicate by this key when merging new selections. Defaults to "id" */ + itemKey?: string +} + +export function RhfResourceField< + TValues extends FieldValues, + TName extends FieldPath, + TClient extends ResourcePageClient, +>({ + name, + label, + triggerLabel, + mapSelected, + renderItems, + dialogProps, + itemKey = "id", +}: RhfResourceFieldProps) { + const { control } = useFormContext() + const { + field, + fieldState: { error }, + } = useController({ name, control }) + + const [open, setOpen] = useState(false) + + const items: TValues[TName] = field.value ?? ([] as unknown as TValues[TName]) + + const handleConfirm = (rows: ResourceItem[]) => { + const mapped = rows.map(mapSelected) + // Merge: keep existing items, add new ones (deduplicate by itemKey) + const existingKeys = new Set( + (items as any[]).map((item: any) => String(item[itemKey])), + ) + const newItems = mapped.filter( + (item: any) => !existingKeys.has(String(item[itemKey])), + ) + field.onChange([...items, ...newItems]) + } + + const helpers = { + remove: (index: number) => { + const next = [...(items as any[])] + next.splice(index, 1) + field.onChange(next) + }, + update: (index: number, item: TValues[TName][number]) => { + const next = [...(items as any[])] + next[index] = item + field.onChange(next) + }, + replace: (newItems: TValues[TName]) => { + field.onChange(newItems) + }, + } + + return ( + + + + {label} + + + + {(items as any[]).length > 0 + ? renderItems(items, helpers) + : ( +

+ No items added yet. +

+ )} +
+
+ {error && {error.message}} + + {...dialogProps} + open={open} + onOpenChange={setOpen} + onConfirm={handleConfirm} + /> +
+ ) +} diff --git a/apps/dashboard/shared/data-view/resource-page/crud-resource.tsx b/apps/dashboard/shared/data-view/resource-page/crud-resource.tsx index 286a1cb..937924f 100644 --- a/apps/dashboard/shared/data-view/resource-page/crud-resource.tsx +++ b/apps/dashboard/shared/data-view/resource-page/crud-resource.tsx @@ -1,7 +1,7 @@ "use client" import React from "react" -import { DataTable, type ActionsColumnOptions } from "@/shared/data-view/table-view" +import { DataTable, type ActionsColumnOptions, type DataViewProps } from "@/shared/data-view/table-view" import { useResourcePage, type UseResourcePageOptions, type ResourceItem, type ResourcePageClient } from "./use-resource-page" import type { ColumnDef } from "@tanstack/react-table" @@ -28,10 +28,13 @@ type ReactNodeOrRender = | React.ReactNode | ((context: CrudResourceContext) => React.ReactNode) +type ManagedTableProps = "columns" | "data" | "pagination" | "sorting" | "onChange" | "isLoading" + export type CrudResourceProps = UseResourcePageOptions & { columns: ColumnDef>[] | ((helpers: CrudResourceColumnHelpers) => ColumnDef>[]) onRowClick?: (row: ResourceItem) => void tableHeader?: ReactNodeOrRender + tableProps?: Omit>>, ManagedTableProps> render?: (table: React.ReactElement, context: CrudResourceContext) => React.ReactElement } @@ -44,6 +47,7 @@ export function CrudResource({ extraParams, onRowClick, tableHeader, + tableProps, render, }: CrudResourceProps) { type TItem = ResourceItem @@ -79,13 +83,14 @@ export function CrudResource({ <> {tableHeader && (typeof tableHeader === "function" ? tableHeader(context) : tableHeader)} ) diff --git a/apps/dashboard/shared/data-view/table-view/data-table.tsx b/apps/dashboard/shared/data-view/table-view/data-table.tsx index 68b10d4..12b03ef 100644 --- a/apps/dashboard/shared/data-view/table-view/data-table.tsx +++ b/apps/dashboard/shared/data-view/table-view/data-table.tsx @@ -1,10 +1,12 @@ "use client" +import { useState, useMemo, useEffect, useRef } from "react" import { useReactTable, getCoreRowModel, flexRender, type ColumnDef, + type RowSelectionState, } from "@tanstack/react-table" import { Table, @@ -15,8 +17,9 @@ import { TableRow, TableCell, } from "@/shared/components/ui/table" +import { Checkbox } from "@/shared/components/ui/checkbox" import { DataViewProvider } from "./data-view-context" - import type { DataViewProps } from "./types" +import type { DataViewProps } from "./types" import { DataViewPagination } from "./data-view-pagination" import { Skeleton } from "@/shared/components/ui/skeleton" @@ -29,20 +32,83 @@ export function DataTable({ isLoading = false, onRowClick, slots, + selection, }: DataViewProps) { + const rowKeyStr = (selection?.rowKey as string) ?? "id" + + // Persisted map of id → original row data across all pages + const persistedMap = useRef>(new Map()) + + // Current-page selection state that TanStack Table controls + const [rowSelection, setRowSelection] = useState({}) + + // When the page/data changes, restore selection state for the new page from the map + useEffect(() => { + if (!selection) return + const restored: RowSelectionState = {} + data.forEach((row) => { + const id = String((row as Record)[rowKeyStr]) + if (persistedMap.current.has(id)) restored[id] = true + }) + setRowSelection(restored) + }, [data]) // eslint-disable-line react-hooks/exhaustive-deps + + const selectionColumn: ColumnDef = useMemo( + () => ({ + id: "__select__", + header: ({ table }) => ( + table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> + ), + cell: ({ row }) => ( + row.toggleSelected(!!value)} + aria-label="Select row" + onClick={(e) => e.stopPropagation()} + /> + ), + size: 40, + enableSorting: false, + }), + [], + ) + + const resolvedColumns = useMemo( + () => + selection + ? [selectionColumn, ...(columns as ColumnDef[])] + : (columns as ColumnDef[]), + [selection, columns, selectionColumn], + ) + const table = useReactTable({ data, - columns: columns as ColumnDef[], + columns: resolvedColumns, getCoreRowModel: getCoreRowModel(), manualPagination: true, manualSorting: true, pageCount: pagination.pageCount, + enableRowSelection: !!selection, + getRowId: selection + ? (row) => String((row as Record)[(selection.rowKey as string) ?? "id"]) + : undefined, state: { sorting, pagination: { pageIndex: pagination.page - 1, pageSize: pagination.pageSize, }, + rowSelection, }, onSortingChange: (updater) => { const next = typeof updater === "function" ? updater(sorting) : updater @@ -61,6 +127,22 @@ export function DataTable({ }, }) }, + onRowSelectionChange: (updater) => { + const next = typeof updater === "function" ? updater(rowSelection) : updater + setRowSelection(next) + if (selection) { + // Sync current page into the persisted map + data.forEach((row) => { + const id = String((row as Record)[rowKeyStr]) + if (next[id]) { + persistedMap.current.set(id, row) + } else { + persistedMap.current.delete(id) + } + }) + selection.onSelectionChange(Array.from(persistedMap.current.values())) + } + }, }) return ( diff --git a/apps/dashboard/shared/data-view/table-view/types.ts b/apps/dashboard/shared/data-view/table-view/types.ts index cb2b28c..212a2a7 100644 --- a/apps/dashboard/shared/data-view/table-view/types.ts +++ b/apps/dashboard/shared/data-view/table-view/types.ts @@ -26,6 +26,12 @@ export type DataViewSlots = { footer?: ReactNode } +export type DataViewSelection = { + onSelectionChange: (selectedRows: TData[]) => void + /** Field used as the unique row identifier. Defaults to "id". */ + rowKey?: keyof TData +} + export type DataViewProps = { columns: ColumnDef[] data: TData[] @@ -35,4 +41,5 @@ export type DataViewProps = { isLoading?: boolean onRowClick?: (row: TData) => void slots?: DataViewSlots + selection?: DataViewSelection } diff --git a/docs/dashboard/feature-checklist.md b/docs/dashboard/feature-checklist.md index 6b21aad..6ef558a 100644 --- a/docs/dashboard/feature-checklist.md +++ b/docs/dashboard/feature-checklist.md @@ -1,20 +1,24 @@ # Garage Management System — Feature Implementation Checklist -> **Generated**: April 3, 2026 +> **Generated**: April 11, 2026 > **Dependency Order Reference**: Postman API Collection (`packages/api/postman/collection.json`) -> **Status Source of Truth**: Implemented API clients in `packages/api/src/clients` and implemented dashboard modules in `apps/dashboard/modules` +> **Status Source of Truth**: API clients in `packages/api/src/clients`, route pages in `apps/dashboard/app`, and dashboard modules in `apps/dashboard/modules` > **Ordered by**: Dependency level (no dependencies → most complex relations) --- ## How to Read This Checklist -- **✅ Client + UI Module** = matching API client exists and a matching dashboard module exists -- **🔧 API Client Only** = matching API client exists, but no dedicated dashboard module was found -- **🧩 UI Module Only** = matching dashboard module exists, but no matching API client was found -- **⬜ Not Started** = no matching client or module was found -- **Module** = a dedicated top-level form, inline form, or resource subform inside `apps/dashboard/modules` -- **Lookup usage only** (for example an async select inside another form) does **not** count as a module +- **✅ Client + UI** = a matching API client exists and at least one dedicated route page or module exists +- **🔧 Client Only** = a matching API client exists, but no dedicated route page or module was found +- **🧩 UI Only** = a dedicated route page or module exists, but no matching API client was found +- **⬜ Not Started** = no client, page, or module was found +- **Client** paths are relative to `packages/api/src/clients` +- **Page** paths are relative to `apps/dashboard/app` +- **Module** paths are relative to `apps/dashboard/modules` +- **Module** includes top-level forms, CRUD dialogs, detail subforms, and inline forms +- **Lookup usage only** (for example an async select or picker field) does **not** count as module coverage +- **Page coverage** only counts when that route actively manages the resource itself; a parent route that happens to exist for a neighboring resource does not count - **Depends on** = other resources that must exist before this one (based on foreign keys in the Postman collection) --- @@ -23,39 +27,39 @@ These resources have no foreign key references. They are the foundation. -| # | Resource | Status | Implementation Details | -|---|----------|--------|----------------------| -| 1 | Auth (Login / Profile / Logout) | ✅ Client + UI Module | Client: `AuthClient` · Module: `auth/login-form.tsx` | -| 2 | Countries | 🔧 API Client Only | Client: `GeoClient.countries()` · Module: none | -| 3 | Customer Types | 🔧 API Client Only | Client: `CustomersClient.listCustomerTypes()` · Module: none | -| 4 | Referral Sources | 🔧 API Client Only | Client: `ReferralSourcesClient` · Module: none | -| 5 | Payment Terms | 🔧 API Client Only | Client: `PaymentTermsClient` · Module: none | -| 6 | Payment Modes | 🔧 API Client Only | Client: `PaymentsClient.listModes()` · Module: none | -| 7 | Shop Types | ✅ Client + UI Module | Client: `ShopTypesClient` · Module: `settings/shop-type/shop-type-form.tsx` | -| 8 | Vehicle Body Types | ✅ Client + UI Module | Client: `VehicleAttributesClient.listBodyTypes()` · Module: `vehicles/inline-forms/body-type-inline-form.tsx` | -| 9 | Vehicle Fuel Types | ✅ Client + UI Module | Client: `VehicleAttributesClient.listFuelTypes()` · Module: `vehicles/inline-forms/fuel-type-inline-form.tsx` | -| 10 | Vehicle Transmissions | ✅ Client + UI Module | Client: `VehicleAttributesClient.listTransmissions()` · Module: `vehicles/inline-forms/transmission-inline-form.tsx` | -| 11 | Vehicle Colors | ✅ Client + UI Module | Client: `VehicleAttributesClient.listColors()` · Module: `vehicles/inline-forms/color-inline-form.tsx` | -| 12 | Document Types | ✅ Client + UI Module | Client: `VehicleDocumentsClient.listDocumentTypes()` · Module: `vehicles/inline-forms/document-type-inline-form.tsx` | -| 13 | Unit Types | ✅ Client + UI Module | Client: `InventoryClient.listUnitTypes()` · Module: `services/inline-forms/unit-type-inline-form.tsx` | -| 14 | Labels | 🔧 API Client Only | Client: `LabelsClient` · Module: none | -| 15 | Insurance Types | 🔧 API Client Only | Client: `InsuranceTypesClient` · Module: none | -| 16 | Inspection Categories | ✅ Client + UI Module | Client: `InspectionsClient.listCategories()` · Module: `inspections/inline-forms/inspection-category-inline-form.tsx` | -| 17 | Check Point Labels | 🔧 API Client Only | Client: `InspectionsClient.listCheckpointLabels()` · Module: none | -| 18 | Quick Remarks | 🔧 API Client Only | Client: `EstimatesClient.listQuickRemarks()` · Module: none | -| 19 | Quick Notes | 🔧 API Client Only | Client: `EstimatesClient.listQuickNotes()` · Module: none | -| 20 | Reasons | ⬜ Not Started | Client: none found · Module: none | -| 21 | Task Types | 🔧 API Client Only | Client: `TasksClient.listTypes()` · Module: none | -| 22 | Task Sections | 🔧 API Client Only | Client: `TasksClient.listSections()` · Module: none | -| 23 | Invoice Labels | 🔧 API Client Only | Client: `InvoicesClient.listLabels()` · Module: none | -| 24 | Holiday Years | ✅ Client + UI Module | Client: `HolidayYearsClient` · Module: `settings/holiday-year/holiday-year-form.tsx` | -| 25 | Taxes | ✅ Client + UI Module | Client: `TaxesClient` · Module: `settings/tax-rates/tax-form.tsx` | -| 26 | Departments | ✅ Client + UI Module | Client: `DepartmentsClient` · Module: `services/inline-forms/department-inline-form.tsx` | -| 27 | Labor Rates | 🔧 API Client Only | Client: `InventoryClient.listLaborRates()` · Module: none | -| 28 | Vendors | ✅ Client + UI Module | Client: `VendorsClient` · Module: `vendors/vendor-form.tsx` | -| 29 | Shop Calendars | ✅ Client + UI Module | Client: `ShopCalendarsClient` · Module: `shop-calendars/shop-calendar-form.tsx` | -| 30 | Shop Timings | ✅ Client + UI Module | Client: `ShopTimingsClient` · Module: `shop-timings/shop-timing-form.tsx` | -| 31 | Settings | ⬜ Not Started | Client: none in `packages/api/src/clients` · Module: none (tracked setting resources are listed separately) | +| # | Resource | Status | Client | Page | Module | +|---|----------|--------|--------|------|--------| +| 1 | Auth (Login / Profile / Logout) | ✅ Client + UI | `auth.ts` | `(auth)/login/page.tsx` | `auth/login-form.tsx` | +| 2 | Countries | 🔧 Client Only | `geo.ts` | — | — | +| 3 | Customer Types | 🔧 Client Only | `customers.ts` | — | — | +| 4 | Referral Sources | 🔧 Client Only | `referral-sources.ts` | — | — | +| 5 | Payment Terms | 🔧 Client Only | `payment-terms.ts` | — | — | +| 6 | Payment Modes | 🔧 Client Only | `payment-modes.ts` | — | — | +| 7 | Shop Types | ✅ Client + UI | `shop-types.ts` | `(authenticated)/settings/shop-type/page.tsx` | `settings/shop-type/shop-type-form.tsx` | +| 8 | Vehicle Body Types | ✅ Client + UI | `vehicle-attributes.ts` | — | `vehicles/inline-forms/body-type-inline-form.tsx` | +| 9 | Vehicle Fuel Types | ✅ Client + UI | `vehicle-attributes.ts` | — | `vehicles/inline-forms/fuel-type-inline-form.tsx` | +| 10 | Vehicle Transmissions | ✅ Client + UI | `vehicle-attributes.ts` | — | `vehicles/inline-forms/transmission-inline-form.tsx` | +| 11 | Vehicle Colors | ✅ Client + UI | `vehicle-attributes.ts` | — | `vehicles/inline-forms/color-inline-form.tsx` | +| 12 | Document Types | ✅ Client + UI | `vehicle-documents.ts` | — | `vehicles/inline-forms/document-type-inline-form.tsx` | +| 13 | Unit Types | ✅ Client + UI | `inventory.ts` | — | `services/inline-forms/unit-type-inline-form.tsx` | +| 14 | Labels | 🔧 Client Only | `labels.ts` | — | — | +| 15 | Insurance Types | ✅ Client + UI | `insurance-types.ts` | `(authenticated)/settings/insurance-types/page.tsx` | `settings/insurance-types/insurance-type-form.tsx` | +| 16 | Inspection Categories | ✅ Client + UI | `inspections.ts` | — | `inspections/inline-forms/inspection-category-inline-form.tsx` | +| 17 | Check Point Labels | 🔧 Client Only | `inspections.ts` | — | — | +| 18 | Quick Remarks | 🔧 Client Only | `quick-remarks.ts` | — | — | +| 19 | Quick Notes | 🔧 Client Only | `quick-notes.ts` | — | — | +| 20 | Reasons | 🔧 Client Only | `reasons.ts` | — | — | +| 21 | Task Types | ✅ Client + UI | `task-types.ts` | — | `tasks/task-type-form.tsx`, `tasks/task-type-crud-dialog.tsx` | +| 22 | Task Sections | ✅ Client + UI | `task-sections.ts` | — | `tasks/task-section-form.tsx`, `tasks/task-section-crud-dialog.tsx` | +| 23 | Invoice Labels | 🔧 Client Only | `invoices.ts` | — | — | +| 24 | Holiday Years | ✅ Client + UI | `holiday-years.ts` | `(authenticated)/productivity/holidays/page.tsx` | `settings/holiday-year/holiday-year-form.tsx` | +| 25 | Taxes | ✅ Client + UI | `taxes.ts` | `(authenticated)/settings/tax-rates/page.tsx` | `settings/tax-rates/tax-form.tsx` | +| 26 | Departments | ✅ Client + UI | `departments.ts` | `(authenticated)/settings/departments/page.tsx` | `settings/departments/department-form.tsx` | +| 27 | Labor Rates | 🔧 Client Only | `inventory.ts` | — | — | +| 28 | Vendors | ✅ Client + UI | `vendors.ts` | `(authenticated)/purchase/vendor/page.tsx` | `vendors/vendor-form.tsx` | +| 29 | Shop Calendars | ✅ Client + UI | `shop-calendars.ts` | `(authenticated)/productivity/shop-calendars/page.tsx` | `shop-calendars/shop-calendar-form.tsx` | +| 30 | Shop Timings | ✅ Client + UI | `shop-timings.ts` | `(authenticated)/productivity/shop-timings/page.tsx` | `shop-timings/shop-timing-form.tsx` | +| 31 | Settings | ✅ Client + UI | `settings.ts`, `configurations.ts` | `(authenticated)/settings/company/page.tsx`, `(authenticated)/settings/configurations/preferences/*/page.tsx` | `settings/company/settings-form.tsx`, `settings/configurations/*-form.tsx` | --- @@ -63,13 +67,13 @@ These resources have no foreign key references. They are the foundation. These depend only on Level 0 resources. -| # | Resource | Status | Depends On | Implementation Details | -|---|----------|--------|------------|----------------------| -| 32 | States | 🔧 API Client Only | Countries | Client: `GeoClient.states()` · Module: none | -| 33 | Inventory Categories | ✅ Client + UI Module | Shop Types | Client: `InventoryClient.listCategories()` · Module: `services/inline-forms/inventory-category-inline-form.tsx` | -| 34 | Vendor Addresses | 🔧 API Client Only | Vendors, Countries, States | Client: `VendorsClient.createAddress()` / `getAddress()` · Module: none | -| 35 | Holidays | ⬜ Not Started | Holiday Years | Client: none dedicated · Module: none (`HolidayYearForm` covers years only) | -| 36 | Make and Models | ⬜ Not Started | Shop Types, Body Types, Fuel Types, Transmissions | Client: none dedicated · Module: none (vehicle form uses plain make/model fields) | +| # | Resource | Status | Depends On | Client | Page | Module | +|---|----------|--------|------------|--------|------|--------| +| 32 | States | 🔧 Client Only | Countries | `geo.ts` | — | — | +| 33 | Inventory Categories | ✅ Client + UI | Shop Types | `inventory-categories.ts` | — | `expense-items/inventory-category-form.tsx`, `expense-items/inventory-category-crud-dialog.tsx`, `services/inline-forms/inventory-category-inline-form.tsx` | +| 34 | Vendor Addresses | 🔧 Client Only | Vendors, Countries, States | `vendors.ts` | — | — | +| 35 | Holidays | 🔧 Client Only | Holiday Years | `holidays.ts` | — | — | +| 36 | Make and Models | ✅ Client + UI | Shop Types, Body Types, Fuel Types, Transmissions | `make-and-models.ts` | `(authenticated)/settings/make-and-models/page.tsx` | `settings/make-and-models/make-and-model-form.tsx` | --- @@ -77,11 +81,11 @@ These depend only on Level 0 resources. These depend on Level 0 + Level 1 resources and are used by many higher-level features. -| # | Resource | Status | Depends On | Implementation Details | -|---|----------|--------|------------|----------------------| -| 37 | Customers | ✅ Client + UI Module | Customer Types, Referral Sources, Payment Terms, Countries, States | Client: `CustomersClient` · Module: `customers/customer-form.tsx` | -| 38 | Vehicles | ✅ Client + UI Module | Shop Types, Body Types, Fuel Types, Transmissions, Colors | Client: `VehiclesClient` · Module: `vehicles/vehicle-form.tsx` | -| 39 | Expense Items | 🔧 API Client Only | Inventory Categories, Unit Types, Departments | Client: `ExpensesClient.listItems()` / `createItem()` · Module: none | +| # | Resource | Status | Depends On | Client | Page | Module | +|---|----------|--------|------------|--------|------|--------| +| 37 | Customers | ✅ Client + UI | Customer Types, Referral Sources, Payment Terms, Countries, States | `customers.ts` | `(authenticated)/sales/customers/page.tsx` | `customers/customer-form.tsx` | +| 38 | Vehicles | ✅ Client + UI | Shop Types, Body Types, Fuel Types, Transmissions, Colors | `vehicles.ts` | `(authenticated)/sales/vehicles/page.tsx` | `vehicles/vehicle-form.tsx` | +| 39 | Expense Items | ✅ Client + UI | Inventory Categories, Unit Types, Departments | `expense-items.ts` | `(authenticated)/items/expense-item/page.tsx` | `expense-items/expense-item-form.tsx` | --- @@ -89,15 +93,15 @@ These depend on Level 0 + Level 1 resources and are used by many higher-level fe These depend on Level 0–2 resources. -| # | Resource | Status | Depends On | Implementation Details | -|---|----------|--------|------------|----------------------| -| 40 | Employees | ✅ Client + UI Module | Departments, Shop Calendars, Shop Timings | Client: `EmployeesClient` · Module: `employees/employee-form.tsx` | -| 41 | Parts | ✅ Client + UI Module | Shop Types, Inventory Categories, Unit Types, Departments, Vendors | Client: `PartsClient` · Module: `parts/part-form.tsx` | -| 42 | Services | ✅ Client + UI Module | Shop Types, Inventory Categories, Unit Types, Departments | Client: `ServicesClient` · Module: `services/service-form.tsx` | -| 43 | Vehicle Documents | ✅ Client + UI Module | Vehicles, Document Types | Client: `VehicleDocumentsClient.listDocuments()` / `createDocument()` · Module: `vehicles/vehicle-document-form.tsx` | -| 44 | Vehicle Mileage | ✅ Client + UI Module | Vehicles | Client: `VehicleDocumentsClient.listMileage()` / `createMileage()` · Module: `vehicles/mileage-form.tsx` | -| 45 | Time Sheets | ⬜ Not Started | Employees | Client: none found · Module: none | -| 46 | Invoice Sequences | ⬜ Not Started | Departments | Client: none found · Module: none | +| # | Resource | Status | Depends On | Client | Page | Module | +|---|----------|--------|------------|--------|------|--------| +| 40 | Employees | ✅ Client + UI | Departments, Shop Calendars, Shop Timings | `employees.ts` | `(authenticated)/productivity/employees/page.tsx` | `employees/employee-form.tsx` | +| 41 | Parts | ✅ Client + UI | Shop Types, Inventory Categories, Unit Types, Departments, Vendors | `parts.ts` | `(authenticated)/items/parts/page.tsx` | `parts/part-form.tsx` | +| 42 | Services | ✅ Client + UI | Shop Types, Inventory Categories, Unit Types, Departments | `services.ts` | `(authenticated)/items/services/page.tsx` | `services/service-form.tsx` | +| 43 | Vehicle Documents | ✅ Client + UI | Vehicles, Document Types | `vehicle-documents.ts` | `(authenticated)/sales/vehicles/[id]/documents/page.tsx` | `vehicles/vehicle-document-form.tsx` | +| 44 | Vehicle Mileage | ✅ Client + UI | Vehicles | `vehicle-documents.ts` | `(authenticated)/sales/vehicles/[id]/mileage/page.tsx` | `vehicles/mileage-form.tsx` | +| 45 | Time Sheets | 🔧 Client Only | Employees | `time-sheets.ts` | — | — | +| 46 | Invoice Sequences | 🔧 Client Only | Departments | `invoice-sequences.ts` | — | — | --- @@ -105,13 +109,13 @@ These depend on Level 0–2 resources. These depend on Level 0–3 resources. -| # | Resource | Status | Depends On | Implementation Details | -|---|----------|--------|------------|----------------------| -| 47 | Service Groups | ✅ Client + UI Module | Shop Types, Inventory Categories, Unit Types, Departments | Client: `ServiceGroupsClient` · Module: `service-groups/service-group-form.tsx` | -| 48 | Service Group Includes | ⬜ Not Started | Service Groups | Client: none dedicated in `ServiceGroupsClient` · Module: none | -| 49 | Service Group Services | ⬜ Not Started | Service Groups, Services, Labor Rates, Taxes | Client: none dedicated in `ServiceGroupsClient` · Module: none | -| 50 | Service Group Parts | ⬜ Not Started | Service Groups, Parts, Taxes | Client: none dedicated in `ServiceGroupsClient` · Module: none | -| 51 | Service Group Pricings | ⬜ Not Started | Service Groups, Shop Types, Labor Rates, Fuel Types, Body Types | Client: none dedicated in `ServiceGroupsClient` · Module: none | +| # | Resource | Status | Depends On | Client | Page | Module | +|---|----------|--------|------------|--------|------|--------| +| 47 | Service Groups | ✅ Client + UI | Shop Types, Inventory Categories, Unit Types, Departments | `service-groups.ts` | `(authenticated)/items/service-group/page.tsx` | `service-groups/service-group-form.tsx` | +| 48 | Service Group Includes | 🔧 Client Only | Service Groups | `service-group-includes.ts` | — | — | +| 49 | Service Group Services | 🔧 Client Only | Service Groups, Services, Labor Rates, Taxes | `service-group-services.ts` | — | — | +| 50 | Service Group Parts | 🔧 Client Only | Service Groups, Parts, Taxes | `service-group-parts.ts` | — | — | +| 51 | Service Group Pricings | 🔧 Client Only | Service Groups, Shop Types, Labor Rates, Fuel Types, Body Types | `service-group-pricings.ts` | — | — | --- @@ -119,12 +123,12 @@ These depend on Level 0–3 resources. These are core garage workflow features depending on customers, vehicles, employees, etc. -| # | Resource | Status | Depends On | Implementation Details | -|---|----------|--------|------------|----------------------| -| 52 | Inspections | ✅ Client + UI Module | Customers, Vehicles, Departments, Inspection Categories, Employees | Client: `InspectionsClient` · Module: `inspections/inspection-form.tsx` | -| 53 | Inspection Check Points | 🔧 API Client Only | Inspections, Check Point Labels | Client: `InspectionsClient.listCheckpoints()` / `createCheckpoint()` · Module: none | -| 54 | Estimates | ✅ Client + UI Module | Customers, Vehicles, Departments, Labels | Client: `EstimatesClient` · Module: `estimates/estimate-form.tsx` | -| 55 | Job Cards | ✅ Client + UI Module | Customers, Vehicles, Departments, Labels, Employees | Client: `JobCardsClient` · Module: `job-cards/job-card-form.tsx` + remark/recommendation subforms | +| # | Resource | Status | Depends On | Client | Page | Module | +|---|----------|--------|------------|--------|------|--------| +| 52 | Inspections | ✅ Client + UI | Customers, Vehicles, Departments, Inspection Categories, Employees | `inspections.ts` | `(authenticated)/sales/inspections/page.tsx` | `inspections/inspection-form.tsx` | +| 53 | Inspection Check Points | ✅ Client + UI | Inspections, Check Point Labels | `inspections.ts` | `(authenticated)/sales/inspections/[id]/checkpoints/page.tsx` | — | +| 54 | Estimates | ✅ Client + UI | Customers, Vehicles, Departments, Labels | `estimates.ts` | `(authenticated)/sales/estimates/page.tsx` | `estimates/estimate-form.tsx` | +| 55 | Job Cards | ✅ Client + UI | Customers, Vehicles, Departments, Labels, Employees | `job-cards.ts` | `(authenticated)/sales/job-cards/page.tsx` | `job-cards/job-card-form.tsx` + related subforms | --- @@ -132,15 +136,15 @@ These are core garage workflow features depending on customers, vehicles, employ These depend on Job Cards and other Level 5 resources. -| # | Resource | Status | Depends On | Implementation Details | -|---|----------|--------|------------|----------------------| -| 56 | Appointments | ✅ Client + UI Module | Customers, Vehicles, Departments, Job Cards, Employees, Labels | Client: `AppointmentsClient` · Module: `appointments/appointment-form.tsx` | -| 57 | Tasks | 🔧 API Client Only | Task Types, Task Sections, Job Cards, Employees, Departments | Client: `TasksClient` · Module: none | -| 58 | Purchase Orders | 🔧 API Client Only | Job Cards, Vendors, Departments, Labels, Parts | Client: `PurchaseOrdersClient` · Module: none | -| 59 | Bills | 🔧 API Client Only | Job Cards, Vendors, Vendor Addresses, Payment Terms, Departments, Labels, Parts | Client: `ExpensesClient.listBills()` / `createBill()` · Module: none | -| 60 | Expenses | ✅ Client + UI Module | Job Cards, Expense Items, Vendors, Departments, Labels | Client: `ExpensesClient.listExpenses()` / `createExpense()` · Module: `expenses/expense-form.tsx` | -| 61 | Payment Received | ✅ Client + UI Module | Job Cards, Payment Modes, Customers | Client: `PaymentsClient.listReceived()` / `createReceived()` · Module: `payment-received/payment-received-form.tsx` | -| 62 | Inventory Adjustments | ⬜ Not Started | Parts, Job Cards, Invoices, Reasons | Client: none found · Module: none | +| # | Resource | Status | Depends On | Client | Page | Module | +|---|----------|--------|------------|--------|------|--------| +| 56 | Appointments | ✅ Client + UI | Customers, Vehicles, Departments, Job Cards, Employees, Labels | `appointments.ts` | `(authenticated)/calendar/appointment/list/page.tsx` | `appointments/appointment-form.tsx` | +| 57 | Tasks | ✅ Client + UI | Task Types, Task Sections, Job Cards, Employees, Departments | `tasks.ts` | `(authenticated)/productivity/tasks/page.tsx` | `tasks/task-form.tsx` | +| 58 | Purchase Orders | ✅ Client + UI | Job Cards, Vendors, Departments, Labels, Parts | `purchase-orders.ts` | `(authenticated)/purchase/purchase-order/page.tsx` | `purchase-orders/purchase-order-form.tsx` | +| 59 | Bills | ✅ Client + UI | Job Cards, Vendors, Vendor Addresses, Payment Terms, Departments, Labels, Parts | `bills.ts` | `(authenticated)/purchase/bill/page.tsx` | `bills/bill-form.tsx` | +| 60 | Expenses | ✅ Client + UI | Job Cards, Expense Items, Vendors, Departments, Labels | `expenses.ts` | `(authenticated)/purchase/expense/page.tsx` | `expenses/expense-form.tsx` | +| 61 | Payment Received | ✅ Client + UI | Job Cards, Payment Modes, Customers | `payment-received.ts` | `(authenticated)/sales/payment-received/page.tsx` | `payment-received/payment-received-form.tsx` | +| 62 | Inventory Adjustments | ✅ Client + UI | Parts, Job Cards, Invoices, Reasons | `inventory-adjustments.ts` | `(authenticated)/items/adjustment/page.tsx` | `inventory-adjustments/inventory-adjustment-form.tsx` | --- @@ -148,14 +152,14 @@ These depend on Job Cards and other Level 5 resources. These are the most complex resources with the deepest dependency chains. -| # | Resource | Status | Depends On | Implementation Details | -|---|----------|--------|------------|----------------------| -| 63 | Invoices | ✅ Client + UI Module | Customers, Vehicles, Departments, Invoice Sequences, Labels, Inspection Categories, Parts, Services, Expense Items, Service Groups | Client: `InvoicesClient` · Module: `invoices/invoice-form.tsx` | -| 64 | Invoice Documents | ✅ Client + UI Module | Invoices, Customers, Vehicles, Document Types | Client: `InvoicesClient.listDocuments()` / `createDocument()` · Module: `invoices/invoice-document-form.tsx` | -| 65 | Invoice Notes | ✅ Client + UI Module | Invoices | Client: `InvoicesClient.listNotes()` / `createNote()` · Module: `invoices/invoice-note-form.tsx` | -| 66 | Credit Notes | ⬜ Not Started | Customers, Parts, Services, Expenses, Inspection Categories, Labels | Client: none found · Module: none | -| 67 | Payment Mades | ⬜ Not Started | Vendors, Employees, Bills, Expenses, Payment Modes | Client: none found · Module: none | -| 68 | Vendor Credits | ⬜ Not Started | Vendors, Departments, Parts, Services, Expenses, Labels | Client: none found · Module: none | +| # | Resource | Status | Depends On | Client | Page | Module | +|---|----------|--------|------------|--------|------|--------| +| 63 | Invoices | ✅ Client + UI | Customers, Vehicles, Departments, Invoice Sequences, Labels, Inspection Categories, Parts, Services, Expense Items, Service Groups | `invoices.ts` | `(authenticated)/sales/invoice/page.tsx` | `invoices/invoice-form.tsx` | +| 64 | Invoice Documents | ✅ Client + UI | Invoices, Customers, Vehicles, Document Types | `invoices.ts` | `(authenticated)/sales/invoice/[id]/documents/page.tsx` | `invoices/invoice-document-form.tsx` | +| 65 | Invoice Notes | ✅ Client + UI | Invoices | `invoices.ts` | `(authenticated)/sales/invoice/[id]/notes/page.tsx` | `invoices/invoice-note-form.tsx` | +| 66 | Credit Notes | ✅ Client + UI | Customers, Parts, Services, Expenses, Inspection Categories, Labels | `credit-notes.ts` | `(authenticated)/sales/credit-notes/page.tsx` | `credit-notes/credit-note-form.tsx` + document/note subforms | +| 67 | Payment Mades | ✅ Client + UI | Vendors, Employees, Bills, Expenses, Payment Modes | `payment-mades.ts` | `(authenticated)/purchase/payments-made/page.tsx` | `payment-mades/payment-made-form.tsx` | +| 68 | Vendor Credits | ✅ Client + UI | Vendors, Departments, Parts, Services, Expenses, Labels | `vendor-credits.ts` | `(authenticated)/purchase/vendor-credit/page.tsx` | `vendor-credits/vendor-credit-form.tsx` | --- @@ -165,52 +169,63 @@ These are the most complex resources with the deepest dependency chains. | Status | Count | Percentage | |--------|-------|------------| -| ✅ Client + UI Module | 33 | 48.5% | -| 🔧 API Client Only | 21 | 30.9% | -| 🧩 UI Module Only | 0 | 0.0% | -| ⬜ Not Started | 14 | 20.6% | +| ✅ Client + UI | 47 | 69.1% | +| 🔧 Client Only | 21 | 30.9% | +| 🧩 UI Only | 0 | 0.0% | +| ⬜ Not Started | 0 | 0.0% | -No UI-only resources were found under the current `packages/api/src/clients` and `apps/dashboard/modules` source-of-truth rules. +No UI-only resources were found under the current `packages/api/src/clients`, `apps/dashboard/app`, and `apps/dashboard/modules` source-of-truth rules. ### Implementation Progress -| Category | Total | ✅ Client + UI Module | 🔧 API Client Only | 🧩 UI Module Only | ⬜ Not Started | -|----------|-------|-----------------------|--------------------|------------------|----------------| -| Level 0 — Standalone | 31 | 15 | 14 | 0 | 2 | -| Level 1 — Single Dep | 5 | 1 | 2 | 0 | 2 | -| Level 2 — Core Entities | 3 | 2 | 1 | 0 | 0 | -| Level 3 — Operational | 7 | 5 | 0 | 0 | 2 | -| Level 4 — Composite | 5 | 1 | 0 | 0 | 4 | -| Level 5 — Workflows | 4 | 3 | 1 | 0 | 0 | -| Level 6 — Financial | 7 | 3 | 3 | 0 | 1 | -| Level 7 — Invoicing | 6 | 3 | 0 | 0 | 3 | -| **Total** | **68** | **33** | **21** | **0** | **14** | +| Category | Total | ✅ Client + UI | 🔧 Client Only | 🧩 UI Only | ⬜ Not Started | +|----------|-------|----------------|----------------|-----------|----------------| +| Level 0 — Standalone | 31 | 19 | 12 | 0 | 0 | +| Level 1 — Single Dep | 5 | 2 | 3 | 0 | 0 | +| Level 2 — Core Entities | 3 | 3 | 0 | 0 | 0 | +| Level 3 — Operational | 7 | 5 | 2 | 0 | 0 | +| Level 4 — Composite | 5 | 1 | 4 | 0 | 0 | +| Level 5 — Workflows | 4 | 4 | 0 | 0 | 0 | +| Level 6 — Financial | 7 | 7 | 0 | 0 | 0 | +| Level 7 — Invoicing | 6 | 6 | 0 | 0 | 0 | +| **Total** | **68** | **47** | **21** | **0** | **0** | -### Resources with Client + UI Module (33 total) +### UI Coverage Shape -- Top-level modules (20): Auth, Shop Types, Holiday Years, Taxes, Vendors, Shop Calendars, Shop Timings, Customers, Vehicles, Employees, Parts, Services, Service Groups, Inspections, Estimates, Job Cards, Appointments, Expenses, Payment Received, Invoices -- Inline modules (9): Vehicle Body Types, Vehicle Fuel Types, Vehicle Transmissions, Vehicle Colors, Document Types, Unit Types, Inspection Categories, Departments, Inventory Categories -- Detail subforms (4): Vehicle Documents, Vehicle Mileage, Invoice Documents, Invoice Notes +| Coverage Shape | Count | +|----------------|-------| +| Client + Page + Module | 36 | +| Client + Module Only | 10 | +| Client + Page Only | 1 | -### API Clients Without UI Modules (21 total) +The single page-only resource is **Inspection Check Points**, which is implemented directly in its route page without a dedicated module file. -- Master data and reference resources: Countries, Customer Types, Referral Sources, Payment Terms, Payment Modes, Labels, Insurance Types, Check Point Labels, Quick Remarks, Quick Notes, Task Types, Task Sections, Invoice Labels, Labor Rates, States, Vendor Addresses -- Workflow and transactional resources: Expense Items, Inspection Check Points, Tasks, Purchase Orders, Bills +### Status Changes Since April 3, 2026 -### Not Started (14 total) +- Moved to **✅ Client + UI**: Insurance Types, Task Types, Task Sections, Settings, Make and Models, Expense Items, Inspection Check Points, Tasks, Purchase Orders, Bills, Inventory Adjustments, Credit Notes, Payment Mades, Vendor Credits +- Moved to **🔧 Client Only** from **⬜ Not Started**: Reasons, Holidays, Time Sheets, Invoice Sequences, Service Group Includes, Service Group Services, Service Group Parts, Service Group Pricings -- Reasons, Settings, Holidays, Make and Models, Time Sheets, Invoice Sequences, Service Group Includes, Service Group Services, Service Group Parts, Service Group Pricings, Inventory Adjustments, Credit Notes, Payment Mades, Vendor Credits +### Resources with Client + UI Coverage (47 total) -### API Clients Without UI Modules — Priority Recommendations +- Page + module coverage (36): Auth, Shop Types, Insurance Types, Holiday Years, Taxes, Departments, Vendors, Shop Calendars, Shop Timings, Settings, Make and Models, Customers, Vehicles, Expense Items, Employees, Parts, Services, Vehicle Documents, Vehicle Mileage, Service Groups, Inspections, Estimates, Job Cards, Appointments, Tasks, Purchase Orders, Bills, Expenses, Payment Received, Inventory Adjustments, Invoices, Invoice Documents, Invoice Notes, Credit Notes, Payment Mades, Vendor Credits +- Module-only coverage (10): Vehicle Body Types, Vehicle Fuel Types, Vehicle Transmissions, Vehicle Colors, Document Types, Unit Types, Inspection Categories, Task Types, Task Sections, Inventory Categories +- Page-only coverage (1): Inspection Check Points -Based on current implementation depth and operational value, the highest-leverage missing UI modules are: +### API Clients Without UI Coverage (21 total) -1. **Purchase Orders** — existing client, central purchasing workflow -2. **Bills** — existing client, complements Vendors and Expenses -3. **Tasks** — existing client, operational workflow layer is in place but no module exists -4. **Expense Items** — existing client, currently blocks full expense master-data management -5. **Payment Modes / Payment Terms** — existing clients, important finance reference data -6. **Referral Sources / Customer Types** — existing clients, useful master-data UI for sales flows +- Master data and reference resources: Countries, Customer Types, Referral Sources, Payment Terms, Payment Modes, Labels, Check Point Labels, Quick Remarks, Quick Notes, Reasons, Invoice Labels, Labor Rates, States +- Operational and nested resources: Vendor Addresses, Holidays, Time Sheets, Invoice Sequences, Service Group Includes, Service Group Services, Service Group Parts, Service Group Pricings + +### API Clients Without UI Coverage — Priority Recommendations + +Based on current implementation depth and dependency weight, the highest-leverage missing UI coverage is: + +1. **Service Group Includes / Services / Parts / Pricings** — the client layer exists, but the service-group composition workflow is still missing from the dashboard +2. **Invoice Sequences** — invoices are implemented, but their setup dependency still has no route or module +3. **Holidays** — the current `productivity/holidays` route manages Holiday Years, not Holiday entries +4. **Payment Modes / Payment Terms** — still important finance reference data with no dedicated UI +5. **Referral Sources / Customer Types / Reasons / Labels** — useful master-data UI for sales and workflow flows +6. **Time Sheets / Vendor Addresses** — operational subresources still have API support only --- diff --git a/docs/dashboard/phase-1-comparison.md b/docs/dashboard/phase-1-comparison.md new file mode 100644 index 0000000..4d54486 --- /dev/null +++ b/docs/dashboard/phase-1-comparison.md @@ -0,0 +1,147 @@ +# Phase 1 Comparison + +Date: 2026-04-11 + +This document compares the current dashboard implementation against Phase 1, Garage Operations (Sellable MVP). + +## How This Was Evaluated + +- Source of truth for resource coverage: `docs/dashboard/feature-checklist.md` +- Validation method: direct inspection of current pages, modules, and API clients +- `Ready` means the feature is clearly exposed in the current UI and backed by implementation +- `Missing or Partial` means the workflow is absent, incomplete, or not clearly surfaced in the current UI + +## Ready Features + +### Dashboard + +- Operational dashboard overview is present +- Work orders by status are present +- Today or upcoming appointments are present +- Revenue and financial snapshot widgets are present +- Customer, vehicle, item, and sales or purchase summary cards are present + +### Customers and Vehicles + +- Customer management is implemented +- Customer notes are implemented +- Customer vehicle linking is implemented +- Vehicle profile management is implemented +- Vehicle mileage tracking is implemented +- Vehicle documents are implemented + +### Services, Parts, and Inventory Basics + +- Service catalog is implemented +- Service groups are implemented +- Service pricing is implemented +- Parts catalog is implemented +- SKU support is implemented for parts +- Inventory adjustments are implemented + +### Suppliers and Purchasing Basics + +- Vendor management is implemented +- Purchase orders are implemented +- Bills are implemented +- Vendor credits are implemented +- Payments made are implemented + +### Appointments and Inspections + +- Appointment list and detail flow is implemented +- Appointment status handling is implemented +- Technician assignment on appointments is implemented +- Inspection creation and detail flow is implemented +- Inspection checkpoints are implemented +- Inspection notes are implemented +- Inspection photo and file attachments are implemented + +### Estimates and Job Cards + +- Estimate create, edit, and detail flow is implemented at a basic level +- Job card creation and detail flow is implemented +- Technician assignment on job cards is implemented +- Service writer assignment on job cards is implemented +- Job card parts flow is implemented +- Job card services flow is implemented +- Job card attachments are implemented +- Job card inspections, expenses, tasks, appointments, bills, and purchase order tabs are implemented +- Job card status progression is implemented through draft, check-in, in progress, on hold, ready to delivery, and delivered + +### Employees, Permissions, and Settings + +- Employee management is implemented +- Employee attendance-related fields exist +- Per-employee permissions management is implemented +- Company profile settings are implemented +- Tax settings are implemented +- Sales and purchase tax or discount preference forms are implemented + +## Missing or Partial Features + +### Dashboard Gaps + +- Technician workload is not clearly implemented as a dashboard widget +- A dedicated active repairs workload view by technician is not clearly implemented + +### Customer and Vehicle Gaps + +- Fleet customer workflow is not clearly implemented +- Insurance customer workflow is not clearly implemented as a full customer segment workflow +- Customer service history view is not clearly implemented +- Vehicle service history view is not clearly implemented +- Vehicle purchase history view is not clearly implemented + +### Services, Parts, and Inventory Gaps + +- Service duration is not clearly implemented +- Barcode support for parts is not clearly implemented +- On-hand stock visibility is not clearly implemented in the UI +- Low-stock alerts are not clearly implemented +- Reorder alert workflow is not clearly implemented + +### Supplier Gaps + +- Supplier purchase history is not clearly implemented as a vendor-level detail workflow + +### Appointment Gaps + +- A true appointment calendar or scheduler view is not clearly implemented +- The current appointment area is list and detail oriented, not a full calendar board + +### Inspection Gaps + +- Inspection report generation is not clearly implemented +- Inspection print, export, or PDF output is not clearly implemented + +### Estimate Gaps + +- Estimate approval workflow is not clearly implemented +- Customer approval flow for estimates is not clearly implemented +- Estimate line items for services and parts are not clearly surfaced in the current detail UI +- Estimate totals or summary calculations are not clearly surfaced in the current detail UI + +### Job Card Gaps + +- A `Closed` status is not clearly implemented in the visible job card status flow +- Full digital authorisation workflow is only partially represented by settings fields and flags +- Parts issuing workflow appears partially represented by settings flags, but not clearly surfaced as a complete operational flow + +### Employees and Access Control Gaps + +- Time clock UI is not implemented +- Clock in or clock out screens are not implemented +- Technician hours tracking UI is not clearly implemented +- Named role management for Admin, Manager, Service Advisor, and Technician is not clearly implemented as a dedicated role system + +### Settings Gaps + +- Currency configuration is not clearly implemented +- Email system configuration is not clearly implemented beyond company email fields + +## Overall Assessment + +Phase 1 is not fully complete yet. + +The project already covers most of the core CRUD and workshop operations foundation, especially around customers, vehicles, appointments, inspections, job cards, and purchasing resources. The largest remaining gaps are in deeper operational workflows: history views, estimate approval and totals, stock intelligence, technician time tracking, calendar-style scheduling, inspection reporting, and role-based access structure. \ No newline at end of file diff --git a/docs/dashboard/swagger-client-frontend-gap-report.md b/docs/dashboard/swagger-client-frontend-gap-report.md new file mode 100644 index 0000000..78ccfef --- /dev/null +++ b/docs/dashboard/swagger-client-frontend-gap-report.md @@ -0,0 +1,237 @@ +# Phase 1 Backend and Frontend Gap Report + +Date: 2026-04-11 + +This report answers the two deliverables requested for Phase 1: + +1. Backend todos, measured against the Phase 1 roadmap using the Swagger contract in `packages/api/types/index.ts` +2. Frontend todos, measured by comparing the domain client layer in `packages/api/src/clients` and `packages/api/src/api.ts` against real dashboard usage + +The frontend section still uses the contract -> client -> usage audit. The backend section has been remapped to the business roadmap instead of treating missing client wrappers as missing backend work. + +## Rules Used + +- Backend ready = the Phase 1 capability is clearly represented by an endpoint or response shape in `packages/api/types/index.ts` +- Backend todo = the Phase 1 workflow is missing from Swagger, or only partially represented by base CRUD without the required workflow depth +- Frontend implemented = any client method is used somewhere in dashboard source +- Shared CRUD usage counts as implementation: + - `getClient={(api) => api.foo}` on `ResourcePage` or `CrudResource` counts as `list` and `destroy` + - explicit `api.foo.bar()` calls count as direct method usage +- Generated output under `.next` was excluded + +## Executive Summary + +Backend contract coverage is stronger than the earlier UI-only Phase 1 comparison suggested. The API already covers the main CRUD and operational foundation for dashboard metrics, customers, vehicles, services, parts, vendors, appointments, inspections, job cards, employees, permissions, roles, time sheets, and workshop settings. + +The remaining backend work is concentrated in a smaller set of deeper workflows: history and reporting views, estimate approval and estimate line-item depth, stock intelligence such as low-stock or reorder alerts, technician-specific workload data, a visible final `closed` job-card state, and clearer system email or currency configuration. + +Frontend audit counts: + +- Swagger paths found: 260 +- Distinct client route strings found: 232 +- Swagger paths with no client wrapper: 28 +- Clients exposed by `createApi()`: 57 +- Clients used somewhere in dashboard source: 50 +- Completely unused clients: 7 +- Partially used clients: 44 +- Fully wired clients under the current rule: 6 + +## Backend Todos + +These are the Phase 1 backend todos after checking the roadmap against the actual Swagger contract. + +### Already ready in backend contract + +- Dashboard `/api/home` already returns financial charts, work-order status cards, appointment KPIs, upcoming appointments, inventory counts, customer segmentation counts, and vehicle breakdowns. +- Customers already support CRUD, customer types, notes, `company_name`, and insurance-related fields. +- Vehicles already support CRUD, make/model/year, VIN, plate, mileage, owner linking, documents, and mileage logs. +- Services already expose duration-related data through `labor_hours`. +- Parts and inventory already expose SKU, opening stock, and min/max stock, plus inventory-adjustment resources. +- Vendors, purchase orders, bills, vendor credits, and payment flows already exist in the contract. +- Appointments already support CRUD, status changes, and job-card unlinking. +- Inspections already support CRUD, checkpoints, labels, media, and attachments. +- Job cards already support technician and service-writer assignment, parts, services, expense items, attachments, check-in, delivery, digital-authorisation flags, and parts-issuing flags. +- Employees already support CRUD, per-employee permissions, and attendance or time-sheet related fields. +- Roles already exist in Swagger through `/api/roles` and `/api/roles/{id}`. +- Time tracking already exists in Swagger through `/api/time-sheets`, `/api/time-sheet/clock-in`, and `/api/time-sheet/clock-out`. +- Workshop settings and operational configuration already exist through `/api/settings` and `/api/configurations...`. + +### Real backend todos by Phase 1 area + +| Phase 1 area | Contract status | Backend todo | +|---|---|---| +| Dashboard workload | Partial | `/api/home` covers generic work-order KPIs, but there is no clear technician-workload or active-repairs-by-technician feed. | +| Customer, vehicle, and supplier history | Missing | No dedicated customer service-history, vehicle service-history, vehicle purchase-history, or vendor purchase-history routes were found. | +| Estimates | Partial | Swagger only exposes `/api/estimates` and `/api/estimates/{id}`. The `{id}` route has no `GET` in schema, and there are no approval, customer-authorisation, service-line, part-line, or totals-specific routes. | +| Inspection reporting | Missing | No inspection report, print, export, or PDF endpoints were found. | +| Inventory intelligence | Partial | Parts expose `opening_stock`, `min_stock`, and `max_stock`, but there is no barcode field and no low-stock, reorder, or alert workflow in the contract. | +| Job card final closure | Partial | Contract evidence shows `draft`, `check_in`, `in_progress`, `on_hold`, `ready_to_delivery`, and `delivered`, but no visible `closed` status. | +| System email and currency config | Partial | Settings and configuration endpoints cover workshop profile, tax or discount behavior, digital authorisation, and parts-issuing flags, but there is no clear email-delivery configuration endpoint and no clear currency-setting field even though currency is returned in dashboard responses. | + +### Not backend todos anymore + +These Phase 1 items are already represented in Swagger and should be treated as client or frontend work instead of backend work: + +- fleet, company, and insurer customer segmentation +- service duration +- roles and permissions +- time clock, clock-in, and clock-out data layer +- digital authorisation and parts-issuing configuration flags +- appointment calendar or scheduler presentation, which looks more like a frontend presentation gap than a contract gap + +### Client-layer Gaps Against Existing Swagger + +These are not missing backend capabilities. They are existing Swagger routes that still do not have matching domain-client wrappers. + +#### 1. Entire backend resources missing from the client layer + +| Area | Missing Swagger routes | Notes | +|---|---|---| +| Roles | `/api/roles`, `/api/roles/{id}` | Swagger exposes role CRUD, but there is no `RolesClient` and no `api.roles` entry. | + +#### 2. Existing client families with missing route coverage + +| Area | Missing Swagger routes | Notes | +|---|---|---| +| Configurations | `/api/configurations` | `ConfigurationsClient` only wraps the specialized subroutes, not the aggregate base route. Review whether the base GET is required. | +| Make and Models | `/api/get-makes`, `/api/get-models`, `/api/toggle-make-and-model-status` | `MakeAndModelsClient` currently wraps only CRUD. Helper lookups and the status toggle are missing. | +| Invoice Sequences | `/api/remove-default-invoice-sequence`, `/api/set-default-invoice-sequence` | `InvoiceSequencesClient` currently wraps only CRUD. Default-sequence workflow is missing. | + +#### 3. Inspection routes missing from `InspectionsClient` + +- `/api/inspections/{id}/add-attachment` +- `/api/inspections/{id}/add-label` +- `/api/inspections/{id}/delete-attachment` +- `/api/inspections/{id}/delete-label` +- `/api/inspections/{id}/get-attachment` + +Current `InspectionsClient` wraps checkpoint-level attachment and media helpers, but not these inspection-level attachment and label routes. + +#### 4. Job card routes missing from `JobCardsClient` + +- `/api/job-cards/{id}/add-expense-item` +- `/api/job-cards/{id}/add-expense-item-attachment` +- `/api/job-cards/{id}/add-internal-note` +- `/api/job-cards/{id}/add-part-attachment` +- `/api/job-cards/{id}/delete-expense-item` +- `/api/job-cards/{id}/delete-expense-item-attachment` +- `/api/job-cards/{id}/delete-internal-note` +- `/api/job-cards/{id}/delete-part-attachment` +- `/api/job-cards/{id}/edit-internal-note` +- `/api/job-cards/{id}/get-expense-item-attachment` +- `/api/job-cards/{id}/get-expense-items` +- `/api/job-cards/{id}/get-internal-notes` +- `/api/job-cards/{id}/get-part-attachment` +- `/api/job-cards/{id}/get-service-attachment` +- `/api/job-cards/{id}/update-expense-item` + +Current `JobCardsClient` already covers remarks, recommendations, parts, services, and service attachments, but it does not wrap expense-item subresources, part attachments, internal notes, or attachment retrieval endpoints. + +## Frontend Todos + +These are client capabilities that are already available in the API layer, but are still missing or only partially wired in dashboard source. + +### 1. Completely unused clients + +These clients have zero usage anywhere in `apps/dashboard/app`, `apps/dashboard/modules`, or `apps/dashboard/shared`. + +| Client | Gap | +|---|---| +| `holidays` | Entire client unused | +| `invoiceSequences` | Entire client unused | +| `serviceGroupIncludes` | Entire client unused, including `changeArrangement` | +| `serviceGroupParts` | Entire client unused | +| `serviceGroupPricings` | Entire client unused | +| `serviceGroupServices` | Entire client unused | +| `timeSheets` | Entire client unused, including `clockIn` and `clockOut` | + +### 2. High-signal partial clients + +These clients are used, but important workflows are still not wired in the frontend. + +| Client | Already used | Still missing in frontend | +|---|---|---| +| `auth` | `login` | `logout`, `profile` | +| `expenses` | base CRUD plus `listItems` | bill subflow, expense-item subflow, item status toggle | +| `inspections` | base CRUD, categories, checkpoints, checkpoint media | inspection-level attachments, inspection-level labels, checkpoint label management | +| `inventory` | category and unit-type create/list, labor-rate list | labor-rate CRUD/defaults, category and unit-type update/destroy/defaulting | +| `invoices` | base CRUD, documents, notes | label CRUD/list, attachment helpers, document or note update helpers not used | +| `jobCards` | base CRUD, date/status flow, check-in, delivery, parts, services, attachments, remarks, recommendations | service-attachment UI flow, edit remark path, edit recommendation path, import/export | +| `labels` | `list`, `create` | `update`, `destroy`, `show`, import/export | +| `paymentModes` | `list` | create, update, delete, show, import/export | +| `paymentTerms` | `list` | create, update, delete, show, default-setting, import/export | +| `referralSources` | `list` | create, update, delete, show, default-setting, import/export | +| `reasons` | `list` | create, update, delete, show, import/export | +| `serviceGroups` | base CRUD | label workflows, status toggle, show, import/export | +| `shopCalendars` | `list`, `create`, `destroy` | `setDefault`, `removeDefault`, `updateDayType` | +| `tasks` | base CRUD | `complete` | +| `taskSections` | base CRUD | arrangement change, default toggles, `show`, import/export | +| `taskTypes` | base CRUD | default toggles, `show`, import/export | +| `vehicleAttributes` | create/list for body type, fuel type, transmission, color | update and destroy for all lookup records | +| `vehicleDocuments` | document and mileage flows, document-type create/list | document-type update and destroy | +| `vendors` | base CRUD | vendor address workflows, status toggle, `show`, import/export | +| `vendorCredits` | base CRUD | attachments and internal notes | + +### 3. Lower-priority partials + +These clients are already functionally present, but still miss mostly generic `show`, import, export, or default-toggle wiring. + +- `appointments`: missing `show`, `importData`, `exportData` +- `bills`: missing `show`, `importData`, `exportData` +- `creditNotes`: missing `editInternalNote`, `importData`, `exportData` +- `customers`: the UI uses inherited `importData` and `exportData`, but the direct `import()` and `export()` alias methods are unused +- `departments`: missing `show`, `importData`, `exportData` +- `employees`: missing `importData`, `exportData` +- `estimates`: missing `show`, `importData`, `exportData` +- `expenseItems`: missing `importData`, `exportData` +- `insuranceTypes`: missing `show`, `importData`, `exportData` +- `inventoryAdjustments`: missing `show`, `importData`, `exportData` +- `inventoryCategories`: missing `show`, `importData`, `exportData` +- `makeAndModels`: missing `importData`, `exportData` +- `parts`: missing `show`, `toggleStatus` +- `paymentMades`: missing `show`, `importData`, `exportData` +- `paymentReceived`: missing `show`, `importData`, `exportData` +- `purchaseOrders`: missing `show`, `importData`, `exportData` +- `quickNotes`: missing `show`, `importData`, `exportData` +- `quickRemarks`: missing `show`, `importData`, `exportData` +- `services`: missing `show` +- `shopRecommendations`: missing `show`, `importData`, `exportData` +- `shopTimings`: missing `setDefault`, `removeDefault`, `importData`, `exportData` +- `shopTypes`: missing `show`, `importData`, `exportData` +- `taxes`: missing `show`, `importData`, `exportData` +- `vehicles`: the UI uses inherited `importData` and `exportData`, but the direct `import()` and `export()` alias methods are unused + +## Corrections Versus `feature-checklist.md` + +Under the stricter checklist rule, lookup-only usage did not count as UI coverage. Under the user rule for this audit, it does. + +That changes the outcome materially: + +- The old checklist had 21 `Client Only` resources +- The actual zero-usage client list under the current rule is 7 clients + +Previously marked as `Client Only`, but already exercised somewhere in dashboard source under this rule: + +- countries and states through `geo` +- customer types through `customers.listCustomerTypes` +- referral sources +- payment terms +- payment modes +- labels +- quick notes +- quick remarks +- reasons +- labor rates through `inventory.listLaborRates` + +The remaining true zero-usage areas are concentrated in: + +- holidays +- invoice sequences +- time sheets +- service-group includes, parts, pricings, and services + +## Recommended Order + +1. Close the real backend Phase 1 gaps first: estimate workflow depth, history or reporting endpoints, stock intelligence, technician-workload feeds, visible job-card closure, and missing email or currency configuration. +2. Close the client-layer omissions against existing Swagger: roles, invoice-sequence defaults, make/model helper routes, inspection-level attachment or label routes, and job-card expense or internal-note routes. +3. Build UI coverage for the 7 completely unused clients, then finish the high-signal partial workflows, especially expenses, inspections, inventory, job cards, vendors, service groups, shop calendars, and tasks. diff --git a/packages/api/open-api/schema.json b/packages/api/open-api/schema.json index a46ae2a..ccd71b1 100644 --- a/packages/api/open-api/schema.json +++ b/packages/api/open-api/schema.json @@ -365,6 +365,9 @@ }, { "name": "Home" + }, + { + "name": "Document print / download" } ], "paths": { @@ -19642,6 +19645,1502 @@ } } }, + "/api/estimates/{id}/link-appointment": { + "post": { + "tags": [ + "Appointments & estimate attachments" + ], + "summary": "POST /api/estimates/{id}/link-appointment", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "appointment_id": { + "type": "integer" + } + } + }, + "example": { + "appointment_id": 1 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimates/{id}/unlink-appointment": { + "post": { + "tags": [ + "Appointments & estimate attachments" + ], + "summary": "POST /api/estimates/{id}/unlink-appointment", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "appointment_id": { + "type": "integer" + } + } + }, + "example": { + "appointment_id": 1 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimates/{id}/store-authorisation": { + "post": { + "tags": [ + "Appointments & estimate attachments" + ], + "summary": "POST /api/estimates/{id}/store-authorisation", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "services_status": { + "type": "string" + }, + "parts_status": { + "type": "string" + }, + "inspections_status": { + "type": "string" + }, + "expense_items_status": { + "type": "string" + }, + "authorisation_method": { + "type": "string" + }, + "employee_id": { + "type": "integer" + } + } + }, + "example": { + "services_status": "accepted", + "parts_status": "accepted", + "inspections_status": "accepted", + "expense_items_status": "accepted", + "authorisation_method": "in_person", + "employee_id": 1 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "estimate": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "is_authorisation": { + "type": "boolean" + }, + "authorisation_date": { + "type": "string" + }, + "authorisation_time": { + "type": "string" + }, + "authorisation_method": { + "type": "string" + }, + "total_accepted": { + "type": "integer" + } + } + }, + "updated_counts": { + "type": "object", + "properties": { + "services": { + "type": "integer" + }, + "parts": { + "type": "integer" + }, + "inspections": { + "type": "integer" + }, + "expense_items": { + "type": "integer" + } + } + }, + "histories_count": { + "type": "integer" + } + } + } + } + }, + "example": { + "message": "Estimate authorisation stored successfully", + "data": { + "estimate": { + "id": 1, + "is_authorisation": true, + "authorisation_date": "2026-04-13", + "authorisation_time": "14:30:00", + "authorisation_method": "in_person", + "total_accepted": 8 + }, + "updated_counts": { + "services": 3, + "parts": 2, + "inspections": 1, + "expense_items": 2 + }, + "histories_count": 9 + } + } + } + } + } + } + } + }, + "/api/estimates/{id}/update-invoice": { + "post": { + "tags": [ + "Appointments & estimate attachments" + ], + "summary": "POST /api/estimates/{id}/update-invoice", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "invoice_id": { + "type": "integer" + } + } + }, + "example": { + "invoice_id": 1 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "invoice": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "estimate_id": { + "type": "integer" + }, + "invoice_parts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "string" + }, + "rate": { + "type": "string" + } + } + } + }, + "invoice_inpsection_categories": { + "type": "array", + "items": { + "type": "object", + "properties": { + "inspection_category_id": { + "type": "integer" + }, + "rate_type": { + "type": "string" + }, + "rate": { + "type": "string" + } + } + } + }, + "invoice_services": { + "type": "array", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "integer" + }, + "quantity": { + "type": "string" + }, + "rate": { + "type": "string" + } + } + } + }, + "invoice_expenses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "expense_id": { + "type": "integer" + }, + "quantity": { + "type": "string" + }, + "rate": { + "type": "string" + } + } + } + } + } + }, + "synced_counts": { + "type": "object", + "properties": { + "services": { + "type": "integer" + }, + "parts": { + "type": "integer" + }, + "inspections": { + "type": "integer" + }, + "expense_items": { + "type": "integer" + } + } + } + } + } + } + }, + "example": { + "message": "Invoice synced from estimate successfully.", + "data": { + "invoice": { + "id": 1, + "estimate_id": 1, + "invoice_parts": [ + { + "part_id": 1, + "quantity": "2.00", + "rate": "45.50" + } + ], + "invoice_inpsection_categories": [ + { + "inspection_category_id": 1, + "rate_type": "flat_rate", + "rate": "80.00" + } + ], + "invoice_services": [ + { + "service_id": 1, + "quantity": "1.00", + "rate": "120.00" + } + ], + "invoice_expenses": [ + { + "expense_id": 1, + "quantity": "1.00", + "rate": "25.00" + } + ] + }, + "synced_counts": { + "services": 1, + "parts": 1, + "inspections": 1, + "expense_items": 1 + } + } + } + } + } + } + } + } + }, + "/api/estimates/{id}/add-attachment": { + "post": { + "tags": [ + "Appointments & estimate attachments" + ], + "summary": "POST /api/estimates/{id}/add-attachment", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "attachments[]": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimates/{id}/delete-attachment": { + "delete": { + "tags": [ + "Appointments & estimate attachments" + ], + "summary": "DELETE /api/estimates/{id}/delete-attachment", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimates/{id}/get-attachment": { + "get": { + "tags": [ + "Appointments & estimate attachments" + ], + "summary": "GET /api/estimates/{id}/get-attachment", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/documents": { + "get": { + "tags": [ + "Estimate documents" + ], + "summary": "GET /api/estimate/{id}/documents", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": [ + "Estimate documents" + ], + "summary": "POST /api/estimate/{id}/documents", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "document_type_id": { + "type": "integer" + }, + "customer_id": { + "type": "integer" + }, + "vehicle_id": { + "type": "integer" + } + } + }, + "example": { + "document_type_id": 1, + "customer_id": 1, + "vehicle_id": 1 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/documents/{document_id}": { + "put": { + "tags": [ + "Estimate documents" + ], + "summary": "PUT /api/estimate/{id}/documents/{document_id}", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "document_type_id": { + "type": "integer" + }, + "customer_id": { + "type": "integer" + }, + "vehicle_id": { + "type": "integer" + } + } + }, + "example": { + "document_type_id": 1, + "customer_id": 1, + "vehicle_id": 1 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "document_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "delete": { + "tags": [ + "Estimate documents" + ], + "summary": "DELETE /api/estimate/{id}/documents/{document_id}", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "document_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/services": { + "get": { + "tags": [ + "Estimate services (estimate_services)" + ], + "summary": "GET /api/estimate/{id}/services", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": [ + "Estimate services (estimate_services)" + ], + "summary": "POST /api/estimate/{id}/services", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "service_id": { + "type": "integer" + }, + "rate_type": { + "type": "string" + }, + "labor_rate_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "string" + }, + "working_hours": { + "type": "string" + }, + "labor_hours": { + "type": "string" + }, + "tax": { + "type": "string" + }, + "chart_of_account": { + "type": "string" + }, + "department_id": { + "type": "integer" + }, + "description": { + "type": "string" + } + } + }, + "example": { + "service_id": 1, + "rate_type": "flat_rate", + "labor_rate_id": 1, + "quantity": 1, + "rate": "120.00", + "working_hours": "1.00", + "labor_hours": "1.00", + "tax": "5", + "chart_of_account": "4000", + "department_id": 1, + "description": "Labor line" + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/services/{service_id}": { + "put": { + "tags": [ + "Estimate services (estimate_services)" + ], + "summary": "PUT /api/estimate/{id}/services/{service_id}", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "rate": { + "type": "string" + }, + "quantity": { + "type": "integer" + } + } + }, + "example": { + "rate": "130.00", + "quantity": 2 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "service_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "delete": { + "tags": [ + "Estimate services (estimate_services)" + ], + "summary": "DELETE /api/estimate/{id}/services/{service_id}", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "service_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/services/{service_id}/add-attachment": { + "post": { + "tags": [ + "Estimate services (estimate_services)" + ], + "summary": "POST /api/estimate/{id}/services/{service_id}/add-attachment", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "attachments[]": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "service_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/services/{service_id}/delete-attachment": { + "delete": { + "tags": [ + "Estimate services (estimate_services)" + ], + "summary": "DELETE /api/estimate/{id}/services/{service_id}/delete-attachment", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "service_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/services/{service_id}/get-attachment": { + "get": { + "tags": [ + "Estimate services (estimate_services)" + ], + "summary": "GET /api/estimate/{id}/services/{service_id}/get-attachment", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "service_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/parts": { + "get": { + "tags": [ + "Estimate parts (estimate_parts)" + ], + "summary": "GET /api/estimate/{id}/parts", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": [ + "Estimate parts (estimate_parts)" + ], + "summary": "POST /api/estimate/{id}/parts", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "string" + }, + "tax": { + "type": "string" + }, + "chart_of_account": { + "type": "string" + }, + "department_id": { + "type": "integer" + }, + "description": { + "type": "string" + } + } + }, + "example": { + "part_id": 1, + "quantity": 2, + "rate": "45.50", + "tax": "5", + "chart_of_account": "5000", + "department_id": 1, + "description": "Oil filter" + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/parts/{part_id}": { + "put": { + "tags": [ + "Estimate parts (estimate_parts)" + ], + "summary": "PUT /api/estimate/{id}/parts/{part_id}", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "quantity": { + "type": "integer" + } + } + }, + "example": { + "quantity": 3 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "part_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "delete": { + "tags": [ + "Estimate parts (estimate_parts)" + ], + "summary": "DELETE /api/estimate/{id}/parts/{part_id}", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "part_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/parts/{part_id}/add-attachment": { + "post": { + "tags": [ + "Estimate parts (estimate_parts)" + ], + "summary": "POST /api/estimate/{id}/parts/{part_id}/add-attachment", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "attachments[]": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "part_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/parts/{part_id}/delete-attachment": { + "delete": { + "tags": [ + "Estimate parts (estimate_parts)" + ], + "summary": "DELETE /api/estimate/{id}/parts/{part_id}/delete-attachment", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "part_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/parts/{part_id}/get-attachment": { + "get": { + "tags": [ + "Estimate parts (estimate_parts)" + ], + "summary": "GET /api/estimate/{id}/parts/{part_id}/get-attachment", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "part_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/expense-items": { + "get": { + "tags": [ + "Estimate expense items (estimate_expense_items)" + ], + "summary": "GET /api/estimate/{id}/expense-items", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": [ + "Estimate expense items (estimate_expense_items)" + ], + "summary": "POST /api/estimate/{id}/expense-items", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "expense_item_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "string" + }, + "tax": { + "type": "string" + }, + "chart_of_account": { + "type": "string" + }, + "department_id": { + "type": "integer" + }, + "description": { + "type": "string" + } + } + }, + "example": { + "expense_item_id": 1, + "quantity": 1, + "rate": "25.00", + "tax": "5", + "chart_of_account": "6000", + "department_id": 1, + "description": "Shop supplies" + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/expense-items/{expense_item_id}": { + "put": { + "tags": [ + "Estimate expense items (estimate_expense_items)" + ], + "summary": "PUT /api/estimate/{id}/expense-items/{expense_item_id}", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "quantity": { + "type": "integer" + } + } + }, + "example": { + "quantity": 2 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "expense_item_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + }, + "delete": { + "tags": [ + "Estimate expense items (estimate_expense_items)" + ], + "summary": "DELETE /api/estimate/{id}/expense-items/{expense_item_id}", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "expense_item_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/expense-items/{expense_item_id}/add-attachment": { + "post": { + "tags": [ + "Estimate expense items (estimate_expense_items)" + ], + "summary": "POST /api/estimate/{id}/expense-items/{expense_item_id}/add-attachment", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "attachments[]": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "expense_item_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/expense-items/{expense_item_id}/delete-attachment": { + "delete": { + "tags": [ + "Estimate expense items (estimate_expense_items)" + ], + "summary": "DELETE /api/estimate/{id}/expense-items/{expense_item_id}/delete-attachment", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "expense_item_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, + "/api/estimate/{id}/expense-items/{expense_item_id}/get-attachment": { + "get": { + "tags": [ + "Estimate expense items (estimate_expense_items)" + ], + "summary": "GET /api/estimate/{id}/expense-items/{expense_item_id}/get-attachment", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "expense_item_id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + } + } + }, "/api/estimates": { "get": { "tags": [ @@ -23004,6 +24503,9 @@ "vehicle_id": { "type": "integer" }, + "estimate_id": { + "type": "integer" + }, "order_number": { "type": "string" }, @@ -23132,6 +24634,7 @@ "title": "Job Card 001", "customer_id": 1, "vehicle_id": 1, + "estimate_id": 1, "order_number": "ORD-001", "estimate_number": "EST-001", "order_date": "2026-03-31", @@ -23205,6 +24708,9 @@ "vehicle_id": { "type": "integer" }, + "estimate_id": { + "type": "integer" + }, "sales_person_id": { "type": "integer" }, @@ -23420,6 +24926,7 @@ "title": "Job Card 001", "customer_id": 1, "vehicle_id": 1, + "estimate_id": 1, "sales_person_id": 3, "order_number": "ORD-001", "estimate_number": "EST-001", @@ -28650,6 +30157,81 @@ } } }, + "/api/job-cards/{id}/change-technician-id": { + "post": { + "tags": [ + "Job Cards" + ], + "summary": "POST /api/job-cards/{id}/change-technician-id", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "technician_id": { + "type": "integer" + } + } + }, + "example": { + "technician_id": 1 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "technician_id": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + } + } + }, + "example": { + "message": "Technician updated successfully", + "data": { + "id": 1, + "technician_id": 1, + "title": "Job Card 001" + } + } + } + } + } + } + } + }, "/api/payment-mode": { "get": { "tags": [ @@ -29460,6 +31042,9 @@ "opening_stock": { "type": "integer" }, + "available_stock": { + "type": "integer" + }, "as_on_date": { "type": "string" }, @@ -29552,6 +31137,7 @@ "purchase_price": 15, "track_inventory": false, "opening_stock": 1, + "available_stock": 1, "as_on_date": "2026-03-31", "min_stock": 1, "max_stock": 1, @@ -29593,18 +31179,36 @@ "schema": { "type": "object", "properties": { + "shop_type_id": { + "type": "integer" + }, + "category_id": { + "type": "integer" + }, "title": { "type": "string" }, "sku": { "type": "string" }, - "category_id": { - "type": "integer" + "part_number": { + "type": "string" }, "unit_type_id": { "type": "integer" }, + "manufactured_by": { + "type": "string" + }, + "description": { + "type": "string" + }, + "location": { + "type": "string" + }, + "pricing_matrix": { + "type": "string" + }, "department_id": { "type": "integer" }, @@ -29614,39 +31218,85 @@ "selling_price": { "type": "integer" }, + "sales_chart_of_account": { + "type": "integer" + }, "purchase_information": { "type": "boolean" }, + "purchase_chart_of_account": { + "type": "integer" + }, + "purchase_preferred_vendor_id": { + "type": "integer" + }, "purchase_price": { "type": "integer" }, "track_inventory": { "type": "boolean" }, - "is_active": { - "type": "boolean" + "opening_stock": { + "type": "integer" + }, + "available_stock": { + "type": "integer" + }, + "as_on_date": { + "type": "string" + }, + "min_stock": { + "type": "integer" + }, + "max_stock": { + "type": "integer" + }, + "inventory_chart_of_account": { + "type": "integer" + }, + "tracking_type": { + "type": "integer" + }, + "inventory_purchase_price": { + "type": "integer" } } }, "example": { + "shop_type_id": 1, + "category_id": 1, "title": "Engine Oil Filter", "sku": "SKU-001", - "category_id": 1, + "part_number": "PRT-001", "unit_type_id": 1, + "manufactured_by": "Toyota", + "description": "Premium quality oil filter", + "location": "Shelf A1", + "pricing_matrix": "retail", "department_id": 1, "sales_information": true, "selling_price": 25, + "sales_chart_of_account": 101, "purchase_information": true, + "purchase_chart_of_account": 201, + "purchase_preferred_vendor_id": 1, "purchase_price": 15, - "track_inventory": false, - "is_active": true + "track_inventory": true, + "opening_stock": 10, + "available_stock": 10, + "as_on_date": "2026-03-31", + "min_stock": 2, + "max_stock": 100, + "inventory_chart_of_account": 301, + "tracking_type": 1, + "inventory_purchase_price": 15 } } } }, "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "content": { "application/json": { "schema": { @@ -29721,6 +31371,9 @@ "opening_stock": { "type": "integer" }, + "available_stock": { + "type": "integer" + }, "as_on_date": { "type": "string" }, @@ -29778,6 +31431,7 @@ "purchase_price": 15, "track_inventory": false, "opening_stock": 1, + "available_stock": 1, "as_on_date": "2026-03-31", "min_stock": 1, "max_stock": 1, @@ -29888,6 +31542,9 @@ "opening_stock": { "type": "integer" }, + "available_stock": { + "type": "integer" + }, "as_on_date": { "type": "string" }, @@ -29945,6 +31602,7 @@ "purchase_price": 15, "track_inventory": false, "opening_stock": 1, + "available_stock": 1, "as_on_date": "2026-03-31", "min_stock": 1, "max_stock": 1, @@ -30003,8 +31661,11 @@ "track_inventory": { "type": "boolean" }, - "is_active": { - "type": "boolean" + "opening_stock": { + "type": "integer" + }, + "available_stock": { + "type": "integer" } } }, @@ -30018,8 +31679,9 @@ "selling_price": 25, "purchase_information": true, "purchase_price": 15, - "track_inventory": false, - "is_active": true + "track_inventory": true, + "opening_stock": 10, + "available_stock": 8 } } } @@ -30111,6 +31773,9 @@ "opening_stock": { "type": "integer" }, + "available_stock": { + "type": "integer" + }, "as_on_date": { "type": "string" }, @@ -30168,6 +31833,7 @@ "purchase_price": 15, "track_inventory": false, "opening_stock": 1, + "available_stock": 1, "as_on_date": "2026-03-31", "min_stock": 1, "max_stock": 1, @@ -30574,6 +32240,51 @@ "updated_at": { "type": "string", "format": "date-time" + }, + "parts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "purchase_order_id": { + "type": "integer" + }, + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "string" + }, + "description": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "part": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + } + } + } } } } @@ -30592,23 +32303,12 @@ }, "total": { "type": "integer" - } - } - }, - "links": { - "type": "object", - "properties": { - "first": { - "type": "string" }, - "last": { - "type": "string" + "from": { + "type": "integer" }, - "prev": { - "type": "string" - }, - "next": { - "type": "string" + "to": { + "type": "integer" } } } @@ -30628,20 +32328,32 @@ "notes": "string", "terms_and_conditions": "string", "created_at": "2026-03-31T10:00:00.000000Z", - "updated_at": "2026-03-31T10:00:00.000000Z" + "updated_at": "2026-03-31T10:00:00.000000Z", + "parts": [ + { + "id": 1, + "purchase_order_id": 1, + "part_id": 1, + "quantity": 2, + "rate": "45.50", + "description": "Oil filter", + "created_at": "2026-03-31T10:00:00.000000Z", + "updated_at": "2026-03-31T10:00:00.000000Z", + "part": { + "id": 1, + "title": "Oil Filter" + } + } + ] } ], "meta": { "current_page": 1, "last_page": 5, "per_page": 15, - "total": 75 - }, - "links": { - "first": "https://api.example.com/resource?page=1", - "last": "https://api.example.com/resource?page=5", - "prev": "string", - "next": "https://api.example.com/resource?page=2" + "total": 75, + "from": 1, + "to": 15 } } } @@ -30670,6 +32382,9 @@ "title": { "type": "string" }, + "order_number": { + "type": "string" + }, "order_date": { "type": "string" }, @@ -30681,6 +32396,29 @@ }, "notes": { "type": "string" + }, + "terms_and_conditions": { + "type": "string" + }, + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "number" + }, + "description": { + "type": "string" + } + } + } } } }, @@ -30688,17 +32426,27 @@ "job_card_id": 1, "vendor_id": 1, "title": "Purchase Order for Parts", + "order_number": "PO-001", "order_date": "2026-03-31", "delivery_date": "2026-04-07", "department_id": 1, - "notes": "string" + "notes": "string", + "terms_and_conditions": "Net 30", + "items": [ + { + "part_id": 1, + "quantity": 2, + "rate": 45.5, + "description": "Oil filter" + } + ] } } } }, "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "content": { "application/json": { "schema": { @@ -30747,13 +32495,58 @@ "updated_at": { "type": "string", "format": "date-time" + }, + "parts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "purchase_order_id": { + "type": "integer" + }, + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "string" + }, + "description": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "part": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + } + } + } } } } } }, "example": { - "message": "Operation completed successfully.", + "message": "Purchase order created successfully", "data": { "id": 1, "job_card_id": 1, @@ -30764,9 +32557,25 @@ "delivery_date": "2026-04-07", "department_id": 1, "notes": "string", - "terms_and_conditions": "string", + "terms_and_conditions": "Net 30", "created_at": "2026-03-31T10:00:00.000000Z", - "updated_at": "2026-03-31T10:00:00.000000Z" + "updated_at": "2026-03-31T10:00:00.000000Z", + "parts": [ + { + "id": 1, + "purchase_order_id": 1, + "part_id": 1, + "quantity": 2, + "rate": "45.50", + "description": "Oil filter", + "created_at": "2026-03-31T10:00:00.000000Z", + "updated_at": "2026-03-31T10:00:00.000000Z", + "part": { + "id": 1, + "title": "Oil Filter" + } + } + ] } } } @@ -30788,37 +32597,55 @@ "schema": { "type": "object", "properties": { - "job_card_id": { - "type": "integer" - }, - "vendor_id": { - "type": "integer" - }, "title": { "type": "string" }, - "order_date": { + "order_number": { "type": "string" }, "delivery_date": { "type": "string" }, - "department_id": { - "type": "integer" - }, - "notes": { - "type": "string" + "items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "number" + }, + "description": { + "type": "string" + } + } + } } } }, "example": { - "job_card_id": 1, - "vendor_id": 1, - "title": "Purchase Order for Parts", - "order_date": "2026-03-31", - "delivery_date": "2026-04-07", - "department_id": 1, - "notes": "string" + "title": "Purchase Order for Parts (updated)", + "order_number": "PO-001", + "delivery_date": "2026-04-08", + "items": [ + { + "part_id": 1, + "quantity": 3, + "rate": 45.5, + "description": "Oil filter - updated qty" + }, + { + "part_id": 2, + "quantity": 1, + "rate": 120, + "description": "Air filter" + } + ] } } } @@ -30884,26 +32711,101 @@ "updated_at": { "type": "string", "format": "date-time" + }, + "parts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "purchase_order_id": { + "type": "integer" + }, + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "string" + }, + "description": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + }, + "part": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "title": { + "type": "string" + } + } + } + } + } } } } } }, "example": { - "message": "Operation completed successfully.", + "message": "Purchase order updated successfully", "data": { "id": 1, "job_card_id": 1, "vendor_id": 1, - "title": "Purchase Order for Parts", + "title": "Purchase Order for Parts (updated)", "order_number": "PO-001", "order_date": "2026-03-31", - "delivery_date": "2026-04-07", + "delivery_date": "2026-04-08", "department_id": 1, "notes": "string", - "terms_and_conditions": "string", + "terms_and_conditions": "Net 30", "created_at": "2026-03-31T10:00:00.000000Z", - "updated_at": "2026-03-31T10:00:00.000000Z" + "updated_at": "2026-03-31T10:20:00.000000Z", + "parts": [ + { + "id": 3, + "purchase_order_id": 1, + "part_id": 1, + "quantity": 3, + "rate": "45.50", + "description": "Oil filter - updated qty", + "created_at": "2026-03-31T10:20:00.000000Z", + "updated_at": "2026-03-31T10:20:00.000000Z", + "part": { + "id": 1, + "title": "Oil Filter" + } + }, + { + "id": 4, + "purchase_order_id": 1, + "part_id": 2, + "quantity": 1, + "rate": "120.00", + "description": "Air filter", + "created_at": "2026-03-31T10:20:00.000000Z", + "updated_at": "2026-03-31T10:20:00.000000Z", + "part": { + "id": 2, + "title": "Air Filter" + } + } + ] } } } @@ -30940,7 +32842,522 @@ } }, "example": { - "message": "Deleted successfully." + "message": "Purchase order deleted successfully" + } + } + } + } + } + } + }, + "/api/purchase-orders/{id}/add-attachment": { + "post": { + "tags": [ + "Purchase Orders" + ], + "summary": "POST /api/purchase-orders/{id}/add-attachment", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "attachments[]": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "purchase_order_id": { + "type": "integer" + }, + "file_path": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "example": { + "message": "Purchase order attachments added successfully", + "data": { + "id": 1, + "attachments": [ + { + "id": 1, + "purchase_order_id": 1, + "file_path": "purchase_order_attachments/sample.pdf", + "created_at": "2026-04-13T10:00:00.000000Z", + "updated_at": "2026-04-13T10:00:00.000000Z" + } + ] + } + } + } + } + } + } + } + }, + "/api/purchase-orders/{id}/delete-attachment": { + "delete": { + "tags": [ + "Purchase Orders" + ], + "summary": "DELETE /api/purchase-orders/{id}/delete-attachment", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "attachment_id": { + "type": "integer" + } + } + }, + "example": { + "attachment_id": 1 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "attachments": { + "type": "array", + "items": {} + } + } + } + } + }, + "example": { + "message": "Purchase order attachment deleted successfully", + "data": { + "id": 1, + "attachments": [] + } + } + } + } + } + } + } + }, + "/api/purchase-orders/{id}/get-attachment": { + "get": { + "tags": [ + "Purchase Orders" + ], + "summary": "GET /api/purchase-orders/{id}/get-attachment", + "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" + }, + "purchase_order_id": { + "type": "integer" + }, + "file_path": { + "type": "string" + }, + "url": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + } + }, + "example": { + "data": { + "id": 1, + "purchase_order_id": 1, + "file_path": "purchase_order_attachments/sample.pdf", + "url": "http://localhost/storage/purchase_order_attachments/sample.pdf", + "created_at": "2026-04-13T10:00:00.000000Z", + "updated_at": "2026-04-13T10:00:00.000000Z" + } + } + } + } + } + } + } + }, + "/api/purchase-orders/{id}/add-internal-note": { + "post": { + "tags": [ + "Purchase Orders" + ], + "summary": "POST /api/purchase-orders/{id}/add-internal-note", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "note": { + "type": "string" + } + } + }, + "example": { + "note": "Follow up with vendor on ETA." + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "internal_notes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "purchase_order_id": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "example": { + "message": "Purchase order internal note added successfully", + "data": { + "id": 1, + "internal_notes": [ + { + "id": 1, + "purchase_order_id": 1, + "note": "Follow up with vendor on ETA.", + "created_at": "2026-04-13T10:00:00.000000Z", + "updated_at": "2026-04-13T10:00:00.000000Z" + } + ] + } + } + } + } + } + } + } + }, + "/api/purchase-orders/{id}/edit-internal-note": { + "post": { + "tags": [ + "Purchase Orders" + ], + "summary": "POST /api/purchase-orders/{id}/edit-internal-note", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "internal_note_id": { + "type": "integer" + }, + "note": { + "type": "string" + } + } + }, + "example": { + "internal_note_id": 1, + "note": "Updated internal note text." + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "internal_notes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "purchase_order_id": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "example": { + "message": "Purchase order internal note updated successfully", + "data": { + "id": 1, + "internal_notes": [ + { + "id": 1, + "purchase_order_id": 1, + "note": "Updated internal note text.", + "created_at": "2026-04-13T10:00:00.000000Z", + "updated_at": "2026-04-13T10:20:00.000000Z" + } + ] + } + } + } + } + } + } + } + }, + "/api/purchase-orders/{id}/delete-internal-note": { + "delete": { + "tags": [ + "Purchase Orders" + ], + "summary": "DELETE /api/purchase-orders/{id}/delete-internal-note", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "internal_note_id": { + "type": "integer" + } + } + }, + "example": { + "internal_note_id": 1 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "internal_notes": { + "type": "array", + "items": {} + } + } + } + } + }, + "example": { + "message": "Purchase order internal note deleted successfully", + "data": { + "id": 1, + "internal_notes": [] + } } } } @@ -32428,6 +34845,9 @@ "vendor_address_id": { "type": "integer" }, + "purchase_order_id": { + "type": "integer" + }, "bill_number": { "type": "string" }, @@ -32449,6 +34869,93 @@ "status": { "type": "string" }, + "services": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "service_id": { + "type": "integer" + }, + "quantity": { + "type": "string" + }, + "rate": { + "type": "string" + }, + "chart_of_account": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + }, + "parts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "string" + }, + "chart_of_account": { + "type": "integer" + }, + "description": { + "type": "string" + } + } + } + }, + "expenses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "expense_id": { + "type": "integer" + }, + "quantity": { + "type": "string" + }, + "rate": { + "type": "string" + }, + "chart_of_account": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + }, "created_at": { "type": "string", "format": "date-time" @@ -32500,17 +35007,51 @@ "data": [ { "id": 1, - "title": "Parts Bill", + "title": "Workshop Bill", "job_card_id": 1, "vendor_id": 1, "vendor_address_id": 1, + "purchase_order_id": 1, "bill_number": "BILL-001", "bill_date": "2026-03-31", "bill_due_date": "2026-04-14", "payment_terms_id": 1, "department_id": 1, - "notes": "string", - "status": "draft", + "notes": "Vendor invoice for parts, service and expense lines", + "status": "open", + "services": [ + { + "id": 1, + "bill_id": 1, + "service_id": 1, + "quantity": "2.00", + "rate": "150.00", + "chart_of_account": "COA-401", + "description": "Labor service line" + } + ], + "parts": [ + { + "id": 1, + "bill_id": 1, + "part_id": 1, + "quantity": 3, + "rate": "40.00", + "chart_of_account": 1201, + "description": "Brake pad set" + } + ], + "expenses": [ + { + "id": 1, + "bill_id": 1, + "expense_id": 1, + "quantity": "1.00", + "rate": "75.00", + "chart_of_account": "COA-402", + "description": "Consumables expense line" + } + ], "created_at": "2026-03-31T10:00:00.000000Z", "updated_at": "2026-03-31T10:00:00.000000Z" } @@ -32537,7 +35078,7 @@ "tags": [ "Bills" ], - "summary": "Store a newly created bill with labels and parts.", + "summary": "Store a newly created bill with labels, services and expenses.", "requestBody": { "required": true, "content": { @@ -32557,6 +35098,12 @@ "vendor_address_id": { "type": "integer" }, + "purchase_order_id": { + "type": "integer" + }, + "bill_number": { + "type": "string" + }, "bill_date": { "type": "string" }, @@ -32569,28 +35116,135 @@ "department_id": { "type": "integer" }, - "status": { + "notes": { "type": "string" + }, + "label_ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "part_items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "integer" + }, + "chart_of_account": { + "type": "integer" + }, + "description": { + "type": "string" + } + } + } + }, + "service_items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "integer" + }, + "chart_of_account": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + }, + "expense_items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "expense_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "integer" + }, + "chart_of_account": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } } } }, "example": { - "title": "Parts Bill", + "title": "Workshop Bill", "job_card_id": 1, "vendor_id": 1, "vendor_address_id": 1, + "purchase_order_id": 1, + "bill_number": "BILL-001", "bill_date": "2026-03-31", "bill_due_date": "2026-04-14", "payment_terms_id": 1, "department_id": 1, - "status": "draft" + "notes": "Vendor invoice for parts, service and expense lines", + "label_ids": [ + 1 + ], + "part_items": [ + { + "part_id": 1, + "quantity": 3, + "rate": 40, + "chart_of_account": 1201, + "description": "Brake pad set" + } + ], + "service_items": [ + { + "service_id": 1, + "quantity": 2, + "rate": 150, + "chart_of_account": "COA-401", + "description": "Labor service line" + } + ], + "expense_items": [ + { + "expense_id": 1, + "quantity": 1, + "rate": 75, + "chart_of_account": "COA-402", + "description": "Consumables expense line" + } + ] } } } }, "responses": { - "200": { - "description": "OK", + "201": { + "description": "Created", "content": { "application/json": { "schema": { @@ -32617,6 +35271,9 @@ "vendor_address_id": { "type": "integer" }, + "purchase_order_id": { + "type": "integer" + }, "bill_number": { "type": "string" }, @@ -32638,6 +35295,93 @@ "status": { "type": "string" }, + "services": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "service_id": { + "type": "integer" + }, + "quantity": { + "type": "string" + }, + "rate": { + "type": "string" + }, + "chart_of_account": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + }, + "parts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "string" + }, + "chart_of_account": { + "type": "integer" + }, + "description": { + "type": "string" + } + } + } + }, + "expenses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "expense_id": { + "type": "integer" + }, + "quantity": { + "type": "string" + }, + "rate": { + "type": "string" + }, + "chart_of_account": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + }, "created_at": { "type": "string", "format": "date-time" @@ -32654,17 +35398,51 @@ "message": "Operation completed successfully.", "data": { "id": 1, - "title": "Parts Bill", + "title": "Workshop Bill", "job_card_id": 1, "vendor_id": 1, "vendor_address_id": 1, + "purchase_order_id": 1, "bill_number": "BILL-001", "bill_date": "2026-03-31", "bill_due_date": "2026-04-14", "payment_terms_id": 1, "department_id": 1, - "notes": "string", - "status": "draft", + "notes": "Vendor invoice for parts, service and expense lines", + "status": "open", + "services": [ + { + "id": 1, + "bill_id": 1, + "service_id": 1, + "quantity": "2.00", + "rate": "150.00", + "chart_of_account": "COA-401", + "description": "Labor service line" + } + ], + "parts": [ + { + "id": 1, + "bill_id": 1, + "part_id": 1, + "quantity": 3, + "rate": "40.00", + "chart_of_account": 1201, + "description": "Brake pad set" + } + ], + "expenses": [ + { + "id": 1, + "bill_id": 1, + "expense_id": 1, + "quantity": "1.00", + "rate": "75.00", + "chart_of_account": "COA-402", + "description": "Consumables expense line" + } + ], "created_at": "2026-03-31T10:00:00.000000Z", "updated_at": "2026-03-31T10:00:00.000000Z" } @@ -32700,6 +35478,12 @@ "vendor_address_id": { "type": "integer" }, + "purchase_order_id": { + "type": "integer" + }, + "bill_number": { + "type": "string" + }, "bill_date": { "type": "string" }, @@ -32712,21 +35496,128 @@ "department_id": { "type": "integer" }, - "status": { + "notes": { "type": "string" + }, + "label_ids": { + "type": "array", + "items": { + "type": "integer" + } + }, + "part_items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "integer" + }, + "chart_of_account": { + "type": "integer" + }, + "description": { + "type": "string" + } + } + } + }, + "service_items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "service_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "integer" + }, + "chart_of_account": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + }, + "expense_items": { + "type": "array", + "items": { + "type": "object", + "properties": { + "expense_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "integer" + }, + "chart_of_account": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } } } }, "example": { - "title": "Parts Bill", + "title": "Workshop Bill - Updated", "job_card_id": 1, "vendor_id": 1, "vendor_address_id": 1, + "purchase_order_id": 1, + "bill_number": "BILL-001", "bill_date": "2026-03-31", - "bill_due_date": "2026-04-14", + "bill_due_date": "2026-04-20", "payment_terms_id": 1, "department_id": 1, - "status": "draft" + "notes": "Updated vendor invoice lines", + "label_ids": [ + 1 + ], + "part_items": [ + { + "part_id": 1, + "quantity": 1, + "rate": 45, + "chart_of_account": 1201, + "description": "Updated part line" + } + ], + "service_items": [ + { + "service_id": 1, + "quantity": 1, + "rate": 200, + "chart_of_account": "COA-401", + "description": "Updated labor service line" + } + ], + "expense_items": [ + { + "expense_id": 1, + "quantity": 2, + "rate": 50, + "chart_of_account": "COA-402", + "description": "Updated consumables expense line" + } + ] } } } @@ -32770,6 +35661,9 @@ "vendor_address_id": { "type": "integer" }, + "purchase_order_id": { + "type": "integer" + }, "bill_number": { "type": "string" }, @@ -32791,6 +35685,93 @@ "status": { "type": "string" }, + "services": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "service_id": { + "type": "integer" + }, + "quantity": { + "type": "string" + }, + "rate": { + "type": "string" + }, + "chart_of_account": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + }, + "parts": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "part_id": { + "type": "integer" + }, + "quantity": { + "type": "integer" + }, + "rate": { + "type": "string" + }, + "chart_of_account": { + "type": "integer" + }, + "description": { + "type": "string" + } + } + } + }, + "expenses": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "expense_id": { + "type": "integer" + }, + "quantity": { + "type": "string" + }, + "rate": { + "type": "string" + }, + "chart_of_account": { + "type": "string" + }, + "description": { + "type": "string" + } + } + } + }, "created_at": { "type": "string", "format": "date-time" @@ -32807,19 +35788,53 @@ "message": "Operation completed successfully.", "data": { "id": 1, - "title": "Parts Bill", + "title": "Workshop Bill - Updated", "job_card_id": 1, "vendor_id": 1, "vendor_address_id": 1, + "purchase_order_id": 1, "bill_number": "BILL-001", "bill_date": "2026-03-31", - "bill_due_date": "2026-04-14", + "bill_due_date": "2026-04-20", "payment_terms_id": 1, "department_id": 1, - "notes": "string", - "status": "draft", + "notes": "Updated vendor invoice lines", + "status": "open", + "services": [ + { + "id": 2, + "bill_id": 1, + "service_id": 1, + "quantity": "1.00", + "rate": "200.00", + "chart_of_account": "COA-401", + "description": "Updated labor service line" + } + ], + "parts": [ + { + "id": 2, + "bill_id": 1, + "part_id": 1, + "quantity": 1, + "rate": "45.00", + "chart_of_account": 1201, + "description": "Updated part line" + } + ], + "expenses": [ + { + "id": 2, + "bill_id": 1, + "expense_id": 1, + "quantity": "2.00", + "rate": "50.00", + "chart_of_account": "COA-402", + "description": "Updated consumables expense line" + } + ], "created_at": "2026-03-31T10:00:00.000000Z", - "updated_at": "2026-03-31T10:00:00.000000Z" + "updated_at": "2026-03-31T11:00:00.000000Z" } } } @@ -32864,6 +35879,547 @@ } } }, + "/api/bills/{id}/add-attachment": { + "post": { + "tags": [ + "Bills" + ], + "summary": "POST /api/bills/{id}/add-attachment", + "requestBody": { + "content": { + "multipart/form-data": { + "schema": { + "type": "object", + "properties": { + "attachments[]": { + "type": "string", + "format": "binary" + } + } + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "file_path": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "example": { + "message": "Bill attachments added successfully", + "data": { + "id": 1, + "attachments": [ + { + "id": 1, + "bill_id": 1, + "file_path": "http://localhost/storage/bill_attachments/sample.pdf", + "created_at": "2026-04-13T10:00:00.000000Z", + "updated_at": "2026-04-13T10:00:00.000000Z" + } + ] + } + } + } + } + } + } + } + }, + "/api/bills/{id}/delete-attachment": { + "delete": { + "tags": [ + "Bills" + ], + "summary": "DELETE /api/bills/{id}/delete-attachment", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "attachment_id": { + "type": "integer" + } + } + }, + "example": { + "attachment_id": 1 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "attachments": { + "type": "array", + "items": {} + } + } + } + } + }, + "example": { + "message": "Bill attachment deleted successfully", + "data": { + "id": 1, + "attachments": [] + } + } + } + } + } + } + } + }, + "/api/bills/{id}/internal-note": { + "get": { + "tags": [ + "Bills" + ], + "summary": "GET /api/bills/{id}/internal-note", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + }, + "meta": { + "type": "object", + "properties": { + "current_page": { + "type": "integer" + }, + "last_page": { + "type": "integer" + }, + "per_page": { + "type": "integer" + }, + "total": { + "type": "integer" + }, + "from": { + "type": "integer" + }, + "to": { + "type": "integer" + } + } + } + } + }, + "example": { + "data": [ + { + "id": 1, + "bill_id": 1, + "note": "Requested updated invoice copy.", + "created_at": "2026-04-13T10:00:00.000000Z", + "updated_at": "2026-04-13T10:00:00.000000Z" + } + ], + "meta": { + "current_page": 1, + "last_page": 1, + "per_page": 15, + "total": 1, + "from": 1, + "to": 1 + } + } + } + } + } + } + }, + "post": { + "tags": [ + "Bills" + ], + "summary": "POST /api/bills/{id}/internal-note", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "note": { + "type": "string" + } + } + }, + "example": { + "note": "Requested updated invoice copy." + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "internal_notes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "example": { + "message": "Bill internal note added successfully", + "data": { + "id": 1, + "internal_notes": [ + { + "id": 1, + "bill_id": 1, + "note": "Requested updated invoice copy.", + "created_at": "2026-04-13T10:00:00.000000Z", + "updated_at": "2026-04-13T10:00:00.000000Z" + } + ] + } + } + } + } + } + } + }, + "put": { + "tags": [ + "Bills" + ], + "summary": "PUT /api/bills/{id}/internal-note", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "internal_note_id": { + "type": "integer" + }, + "note": { + "type": "string" + } + } + }, + "example": { + "internal_note_id": 1, + "note": "Updated internal note text." + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "internal_notes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "bill_id": { + "type": "integer" + }, + "note": { + "type": "string" + }, + "created_at": { + "type": "string", + "format": "date-time" + }, + "updated_at": { + "type": "string", + "format": "date-time" + } + } + } + } + } + } + } + }, + "example": { + "message": "Bill internal note updated successfully", + "data": { + "id": 1, + "internal_notes": [ + { + "id": 1, + "bill_id": 1, + "note": "Updated internal note text.", + "created_at": "2026-04-13T10:00:00.000000Z", + "updated_at": "2026-04-13T10:20:00.000000Z" + } + ] + } + } + } + } + } + } + }, + "delete": { + "tags": [ + "Bills" + ], + "summary": "DELETE /api/bills/{id}/internal-note", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "internal_note_id": { + "type": "integer" + } + } + }, + "example": { + "internal_note_id": 1 + } + } + } + }, + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "OK", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "message": { + "type": "string" + }, + "data": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "internal_notes": { + "type": "array", + "items": {} + } + } + } + } + }, + "example": { + "message": "Bill internal note deleted successfully", + "data": { + "id": 1, + "internal_notes": [] + } + } + } + } + } + } + } + }, "/api/expenses": { "get": { "tags": [ @@ -39346,6 +42902,9 @@ "vehicle_id": { "type": "integer" }, + "estimate_id": { + "type": "integer" + }, "invoice_date": { "type": "string" }, @@ -39379,6 +42938,7 @@ "subject": "Invoice for Service", "customer_id": 1, "vehicle_id": 1, + "estimate_id": 1, "invoice_date": "2026-03-31", "due_date": "2026-04-14", "payment_terms_id": 1, @@ -39418,6 +42978,9 @@ "vehicle_id": { "type": "integer" }, + "estimate_id": { + "type": "integer" + }, "kms_in": { "type": "integer" }, @@ -39500,6 +43063,7 @@ "subject": "Invoice for Job Card 001", "customer_id": 1, "vehicle_id": 1, + "estimate_id": 1, "kms_in": 50000, "has_insurance": false, "insurer_id": 1, @@ -49263,6 +52827,56 @@ } } } + }, + "/api/document-print": { + "get": { + "tags": [ + "Document print / download" + ], + "summary": "GET /api/document-print", + "responses": { + "200": { + "description": "OK" + } + } + }, + "post": { + "tags": [ + "Document print / download" + ], + "summary": "POST /api/document-print", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "type": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "mode": { + "type": "string" + } + } + }, + "example": { + "type": "job_card", + "id": 1, + "mode": "print" + } + } + } + }, + "responses": { + "200": { + "description": "OK" + } + } + } } } } \ No newline at end of file diff --git a/packages/api/postman/collection.json b/packages/api/postman/collection.json index 367a4ce..e90f261 100644 --- a/packages/api/postman/collection.json +++ b/packages/api/postman/collection.json @@ -1,6 +1,6 @@ { "info": { - "_postman_id": "a879ecf0-769b-47c2-86ec-dcc11bf1973d", + "_postman_id": "90943e73-b99e-468b-b10b-d3a02f751da9", "name": "Reparee Collection", "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", @@ -13256,6 +13256,1618 @@ { "name": "Estimates", "item": [ + { + "name": "Estimate — nested (appointments, documents, lines, attachments)", + "item": [ + { + "name": "Appointments & estimate attachments", + "item": [ + { + "name": "POST /api/estimates/{id}/link-appointment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"appointment_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimates/{{id}}/link-appointment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimates", + "{{id}}", + "link-appointment" + ] + } + }, + "response": [] + }, + { + "name": "POST /api/estimates/{id}/unlink-appointment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"appointment_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimates/{{id}}/unlink-appointment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimates", + "{{id}}", + "unlink-appointment" + ] + } + }, + "response": [] + }, + { + "name": "POST /api/estimates/{id}/store-authorisation", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"services_status\": \"accepted\",\n \"parts_status\": \"accepted\",\n \"inspections_status\": \"accepted\",\n \"expense_items_status\": \"accepted\",\n \"authorisation_method\": \"in_person\",\n \"employee_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimates/{{id}}/store-authorisation", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimates", + "{{id}}", + "store-authorisation" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"services_status\": \"accepted\",\n \"parts_status\": \"accepted\",\n \"inspections_status\": \"accepted\",\n \"expense_items_status\": \"accepted\",\n \"authorisation_method\": \"in_person\",\n \"employee_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimates/{{id}}/store-authorisation", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimates", + "{{id}}", + "store-authorisation" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Estimate authorisation stored successfully\",\n \"data\": {\n \"estimate\": {\n \"id\": 1,\n \"is_authorisation\": true,\n \"authorisation_date\": \"2026-04-13\",\n \"authorisation_time\": \"14:30:00\",\n \"authorisation_method\": \"in_person\",\n \"total_accepted\": 8\n },\n \"updated_counts\": {\n \"services\": 3,\n \"parts\": 2,\n \"inspections\": 1,\n \"expense_items\": 2\n },\n \"histories_count\": 9\n }\n}" + } + ] + }, + { + "name": "POST /api/estimates/{id}/update-invoice", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"invoice_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimates/{{id}}/update-invoice", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimates", + "{{id}}", + "update-invoice" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"invoice_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimates/{{id}}/update-invoice", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimates", + "{{id}}", + "update-invoice" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Invoice synced from estimate successfully.\",\n \"data\": {\n \"invoice\": {\n \"id\": 1,\n \"estimate_id\": 1,\n \"invoice_parts\": [\n {\n \"part_id\": 1,\n \"quantity\": \"2.00\",\n \"rate\": \"45.50\"\n }\n ],\n \"invoice_inpsection_categories\": [\n {\n \"inspection_category_id\": 1,\n \"rate_type\": \"flat_rate\",\n \"rate\": \"80.00\"\n }\n ],\n \"invoice_services\": [\n {\n \"service_id\": 1,\n \"quantity\": \"1.00\",\n \"rate\": \"120.00\"\n }\n ],\n \"invoice_expenses\": [\n {\n \"expense_id\": 1,\n \"quantity\": \"1.00\",\n \"rate\": \"25.00\"\n }\n ]\n },\n \"synced_counts\": {\n \"services\": 1,\n \"parts\": 1,\n \"inspections\": 1,\n \"expense_items\": 1\n }\n }\n}" + } + ] + }, + { + "name": "POST /api/estimates/{id}/add-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "attachments[]", + "type": "file", + "description": "One or more files; max 5MB each.", + "src": "/path/to/file.pdf" + } + ] + }, + "url": { + "raw": "{{base_url}}/api/estimates/{{id}}/add-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimates", + "{{id}}", + "add-attachment" + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/estimates/{id}/delete-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/estimates/{{id}}/delete-attachment?attachment_id=1", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimates", + "{{id}}", + "delete-attachment" + ], + "query": [ + { + "key": "attachment_id", + "value": "1" + } + ] + } + }, + "response": [] + }, + { + "name": "GET /api/estimates/{id}/get-attachment", + "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}}/get-attachment?attachment_id=1", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimates", + "{{id}}", + "get-attachment" + ], + "query": [ + { + "key": "attachment_id", + "value": "1" + } + ] + } + }, + "response": [] + } + ], + "description": "Link/unlink appointments via appointments.estimate_id. Attachments: multipart form field attachments[] (array of files)." + }, + { + "name": "Estimate documents", + "item": [ + { + "name": "GET /api/estimate/{id}/documents", + "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/estimate/{{id}}/documents", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "documents" + ] + } + }, + "response": [] + }, + { + "name": "POST /api/estimate/{id}/documents", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"document_type_id\": 1,\n \"customer_id\": 1,\n \"vehicle_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/documents", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "documents" + ] + } + }, + "response": [] + }, + { + "name": "PUT /api/estimate/{id}/documents/{document_id}", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"document_type_id\": 1,\n \"customer_id\": 1,\n \"vehicle_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/documents/{{document_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "documents", + "{{document_id}}" + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/estimate/{id}/documents/{document_id}", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/documents/{{document_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "documents", + "{{document_id}}" + ] + } + }, + "response": [] + } + ], + "description": "Rows in estimate_documents: document_type_id (required), customer_id & vehicle_id optional (FKs on document_types, customers, vehicles)." + }, + { + "name": "Estimate services (estimate_services)", + "item": [ + { + "name": "GET /api/estimate/{id}/services", + "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/estimate/{{id}}/services", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "services" + ] + } + }, + "response": [] + }, + { + "name": "POST /api/estimate/{id}/services", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"service_id\": 1,\n \"rate_type\": \"flat_rate\",\n \"labor_rate_id\": 1,\n \"quantity\": 1,\n \"rate\": \"120.00\",\n \"working_hours\": \"1.00\",\n \"labor_hours\": \"1.00\",\n \"tax\": \"5\",\n \"chart_of_account\": \"4000\",\n \"department_id\": 1,\n \"description\": \"Labor line\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/services", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "services" + ] + } + }, + "response": [] + }, + { + "name": "PUT /api/estimate/{id}/services/{service_id}", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"rate\": \"130.00\",\n \"quantity\": 2\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/services/{{service_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "services", + "{{service_id}}" + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/estimate/{id}/services/{service_id}", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/services/{{service_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "services", + "{{service_id}}" + ] + } + }, + "response": [] + }, + { + "name": "POST /api/estimate/{id}/services/{service_id}/add-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "attachments[]", + "type": "file", + "description": "One or more files; max 5MB each.", + "src": "/path/to/file.pdf" + } + ] + }, + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/services/{{service_id}}/add-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "services", + "{{service_id}}", + "add-attachment" + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/estimate/{id}/services/{service_id}/delete-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/services/{{service_id}}/delete-attachment?attachment_id=1", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "services", + "{{service_id}}", + "delete-attachment" + ], + "query": [ + { + "key": "attachment_id", + "value": "1" + } + ] + } + }, + "response": [] + }, + { + "name": "GET /api/estimate/{id}/services/{service_id}/get-attachment", + "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/estimate/{{id}}/services/{{service_id}}/get-attachment?attachment_id=1", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "services", + "{{service_id}}", + "get-attachment" + ], + "query": [ + { + "key": "attachment_id", + "value": "1" + } + ] + } + }, + "response": [] + } + ], + "description": "service_id is catalog service. Path service_id = estimate_services.id (line id). rate_type: flat_rate|hourly. department_id required." + }, + { + "name": "Estimate parts (estimate_parts)", + "item": [ + { + "name": "GET /api/estimate/{id}/parts", + "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/estimate/{{id}}/parts", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "parts" + ] + } + }, + "response": [] + }, + { + "name": "POST /api/estimate/{id}/parts", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"part_id\": 1,\n \"quantity\": 2,\n \"rate\": \"45.50\",\n \"tax\": \"5\",\n \"chart_of_account\": \"5000\",\n \"department_id\": 1,\n \"description\": \"Oil filter\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/parts", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "parts" + ] + } + }, + "response": [] + }, + { + "name": "PUT /api/estimate/{id}/parts/{part_id}", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"quantity\": 3\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/parts/{{part_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "parts", + "{{part_id}}" + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/estimate/{id}/parts/{part_id}", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/parts/{{part_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "parts", + "{{part_id}}" + ] + } + }, + "response": [] + }, + { + "name": "POST /api/estimate/{id}/parts/{part_id}/add-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "attachments[]", + "type": "file", + "description": "One or more files; max 5MB each.", + "src": "/path/to/file.pdf" + } + ] + }, + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/parts/{{part_id}}/add-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "parts", + "{{part_id}}", + "add-attachment" + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/estimate/{id}/parts/{part_id}/delete-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/parts/{{part_id}}/delete-attachment?attachment_id=1", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "parts", + "{{part_id}}", + "delete-attachment" + ], + "query": [ + { + "key": "attachment_id", + "value": "1" + } + ] + } + }, + "response": [] + }, + { + "name": "GET /api/estimate/{id}/parts/{part_id}/get-attachment", + "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/estimate/{{id}}/parts/{{part_id}}/get-attachment?attachment_id=1", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "parts", + "{{part_id}}", + "get-attachment" + ], + "query": [ + { + "key": "attachment_id", + "value": "1" + } + ] + } + }, + "response": [] + } + ], + "description": "part_id → parts.id. Path part_id = estimate_parts.id. tax is string in schema." + }, + { + "name": "Estimate expense items (estimate_expense_items)", + "item": [ + { + "name": "GET /api/estimate/{id}/expense-items", + "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/estimate/{{id}}/expense-items", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "expense-items" + ] + } + }, + "response": [] + }, + { + "name": "POST /api/estimate/{id}/expense-items", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"expense_item_id\": 1,\n \"quantity\": 1,\n \"rate\": \"25.00\",\n \"tax\": \"5\",\n \"chart_of_account\": \"6000\",\n \"department_id\": 1,\n \"description\": \"Shop supplies\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/expense-items", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "expense-items" + ] + } + }, + "response": [] + }, + { + "name": "PUT /api/estimate/{id}/expense-items/{expense_item_id}", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"quantity\": 2\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/expense-items/{{expense_item_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "expense-items", + "{{expense_item_id}}" + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/estimate/{id}/expense-items/{expense_item_id}", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/expense-items/{{expense_item_id}}", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "expense-items", + "{{expense_item_id}}" + ] + } + }, + "response": [] + }, + { + "name": "POST /api/estimate/{id}/expense-items/{expense_item_id}/add-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "attachments[]", + "type": "file", + "description": "One or more files; max 5MB each.", + "src": "/path/to/file.pdf" + } + ] + }, + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/expense-items/{{expense_item_id}}/add-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "expense-items", + "{{expense_item_id}}", + "add-attachment" + ] + } + }, + "response": [] + }, + { + "name": "DELETE /api/estimate/{id}/expense-items/{expense_item_id}/delete-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/estimate/{{id}}/expense-items/{{expense_item_id}}/delete-attachment?attachment_id=1", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "expense-items", + "{{expense_item_id}}", + "delete-attachment" + ], + "query": [ + { + "key": "attachment_id", + "value": "1" + } + ] + } + }, + "response": [] + }, + { + "name": "GET /api/estimate/{id}/expense-items/{expense_item_id}/get-attachment", + "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/estimate/{{id}}/expense-items/{{expense_item_id}}/get-attachment?attachment_id=1", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "estimate", + "{{id}}", + "expense-items", + "{{expense_item_id}}", + "get-attachment" + ], + "query": [ + { + "key": "attachment_id", + "value": "1" + } + ] + } + }, + "response": [] + } + ], + "description": "expense_item_id → expense_items.id. Path expense_item_id = estimate_expense_items.id." + } + ], + "description": "Implements routes/api.php estimate endpoints. Nested paths use singular /estimate/... Path segments service_id, part_id, expense_item_id refer to estimate_services.id, estimate_parts.id, estimate_expense_items.id." + }, { "name": "Display a listing of estimates.", "request": { @@ -16200,7 +17812,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Job Card 001\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"order_number\": \"ORD-001\",\n \"estimate_number\": \"EST-001\",\n \"order_date\": \"2026-03-31\",\n \"service_writer_id\": 1,\n \"primary_technician_id\": 2,\n \"status\": \"check_in\",\n \"footer\": \"Thank you for your business.\",\n \"has_insurance\": false,\n \"insurance_type_id\": null,\n \"insurer_id\": null,\n \"estimate_to\": \"Customer\",\n \"enable_parts_issuing\": false,\n \"enable_digital_authorisation\": false,\n \"department_id\": 1,\n \"check_in_date\": \"2026-03-31\",\n \"check_in_time\": \"09:00\",\n \"km_in\": 50000,\n \"fuel_level\": \"full\",\n \"start_date\": \"2026-03-31\",\n \"start_time\": \"09:00\",\n \"delivery_date\": \"2026-04-05\",\n \"delivery_time\": \"17:00\",\n \"attachments\": null,\n \"tax_inclusive\": \"Tax Exclusive\",\n \"discount_type\": \"no\",\n \"discount_at\": \"inclusive_of_tax\",\n \"label_ids\": [\n 1\n ],\n \"documents\": [\n {\n \"document_type_id\": 1,\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"document_number\": \"DOC-001\",\n \"show_in_invoice\": true,\n \"show_in_estimate\": false,\n \"show_in_statement\": false\n }\n ],\n \"customer_remarks\": [\n \"Customer prefers morning pickup.\"\n ]\n}", + "raw": "{\n \"title\": \"Job Card 001\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"estimate_id\": 1,\n \"order_number\": \"ORD-001\",\n \"estimate_number\": \"EST-001\",\n \"order_date\": \"2026-03-31\",\n \"service_writer_id\": 1,\n \"primary_technician_id\": 2,\n \"status\": \"check_in\",\n \"footer\": \"Thank you for your business.\",\n \"has_insurance\": false,\n \"insurance_type_id\": null,\n \"insurer_id\": null,\n \"estimate_to\": \"Customer\",\n \"enable_parts_issuing\": false,\n \"enable_digital_authorisation\": false,\n \"department_id\": 1,\n \"check_in_date\": \"2026-03-31\",\n \"check_in_time\": \"09:00\",\n \"km_in\": 50000,\n \"fuel_level\": \"full\",\n \"start_date\": \"2026-03-31\",\n \"start_time\": \"09:00\",\n \"delivery_date\": \"2026-04-05\",\n \"delivery_time\": \"17:00\",\n \"attachments\": null,\n \"tax_inclusive\": \"Tax Exclusive\",\n \"discount_type\": \"no\",\n \"discount_at\": \"inclusive_of_tax\",\n \"label_ids\": [\n 1\n ],\n \"documents\": [\n {\n \"document_type_id\": 1,\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"document_number\": \"DOC-001\",\n \"show_in_invoice\": true,\n \"show_in_estimate\": false,\n \"show_in_statement\": false\n }\n ],\n \"customer_remarks\": [\n \"Customer prefers morning pickup.\"\n ]\n}", "options": { "raw": { "language": "json" @@ -16245,7 +17857,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Job Card 001\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"order_number\": \"ORD-001\",\n \"estimate_number\": \"EST-001\",\n \"order_date\": \"2026-03-31\",\n \"service_writer_id\": 1,\n \"primary_technician_id\": 2,\n \"status\": \"check_in\",\n \"footer\": \"Thank you for your business.\",\n \"has_insurance\": false,\n \"insurance_type_id\": null,\n \"insurer_id\": null,\n \"estimate_to\": \"Customer\",\n \"enable_parts_issuing\": false,\n \"enable_digital_authorisation\": false,\n \"department_id\": 1,\n \"check_in_date\": \"2026-03-31\",\n \"check_in_time\": \"09:00\",\n \"km_in\": 50000,\n \"fuel_level\": \"full\",\n \"start_date\": \"2026-03-31\",\n \"start_time\": \"09:00\",\n \"delivery_date\": \"2026-04-05\",\n \"delivery_time\": \"17:00\",\n \"attachments\": null,\n \"tax_inclusive\": \"Tax Exclusive\",\n \"discount_type\": \"no\",\n \"discount_at\": \"inclusive_of_tax\",\n \"label_ids\": [\n 1\n ],\n \"documents\": [\n {\n \"document_type_id\": 1,\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"document_number\": \"DOC-001\",\n \"show_in_invoice\": true,\n \"show_in_estimate\": false,\n \"show_in_statement\": false\n }\n ],\n \"customer_remarks\": [\n \"Customer prefers morning pickup.\"\n ]\n}", + "raw": "{\n \"title\": \"Job Card 001\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"estimate_id\": 1,\n \"order_number\": \"ORD-001\",\n \"estimate_number\": \"EST-001\",\n \"order_date\": \"2026-03-31\",\n \"service_writer_id\": 1,\n \"primary_technician_id\": 2,\n \"status\": \"check_in\",\n \"footer\": \"Thank you for your business.\",\n \"has_insurance\": false,\n \"insurance_type_id\": null,\n \"insurer_id\": null,\n \"estimate_to\": \"Customer\",\n \"enable_parts_issuing\": false,\n \"enable_digital_authorisation\": false,\n \"department_id\": 1,\n \"check_in_date\": \"2026-03-31\",\n \"check_in_time\": \"09:00\",\n \"km_in\": 50000,\n \"fuel_level\": \"full\",\n \"start_date\": \"2026-03-31\",\n \"start_time\": \"09:00\",\n \"delivery_date\": \"2026-04-05\",\n \"delivery_time\": \"17:00\",\n \"attachments\": null,\n \"tax_inclusive\": \"Tax Exclusive\",\n \"discount_type\": \"no\",\n \"discount_at\": \"inclusive_of_tax\",\n \"label_ids\": [\n 1\n ],\n \"documents\": [\n {\n \"document_type_id\": 1,\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"document_number\": \"DOC-001\",\n \"show_in_invoice\": true,\n \"show_in_estimate\": false,\n \"show_in_statement\": false\n }\n ],\n \"customer_remarks\": [\n \"Customer prefers morning pickup.\"\n ]\n}", "options": { "raw": { "language": "json" @@ -16273,7 +17885,7 @@ } ], "cookie": [], - "body": "{\n \"message\": \"Job card created successfully\",\n \"data\": {\n \"id\": 1,\n \"title\": \"Job Card 001\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"sales_person_id\": 3,\n \"order_number\": \"ORD-001\",\n \"estimate_number\": \"EST-001\",\n \"order_date\": \"2026-03-31T00:00:00.000000Z\",\n \"service_writer_id\": 1,\n \"primary_technician_id\": 2,\n \"status\": \"check_in\",\n \"footer\": \"Thank you for your business.\",\n \"has_insurance\": false,\n \"insurance_type_id\": null,\n \"insurer_id\": null,\n \"estimate_to\": \"Customer\",\n \"enable_parts_issuing\": false,\n \"enable_digital_authorisation\": false,\n \"department_id\": 1,\n \"check_in_date\": \"2026-03-31T00:00:00.000000Z\",\n \"check_in_time\": \"09:00\",\n \"km_in\": 50000,\n \"fuel_level\": \"full\",\n \"start_date\": \"2026-03-31T00:00:00.000000Z\",\n \"start_time\": \"09:00\",\n \"delivery_date\": \"2026-04-05T00:00:00.000000Z\",\n \"delivery_time\": \"17:00\",\n \"attachments\": null,\n \"tax_inclusive\": \"Tax Exclusive\",\n \"discount_type\": \"no\",\n \"discount_at\": \"inclusive_of_tax\",\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 \"job_card_id\": 1,\n \"label_id\": 1,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n }\n ],\n \"documents\": [\n {\n \"id\": 1,\n \"job_card_id\": 1,\n \"document_type_id\": 1,\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"document_number\": \"DOC-001\",\n \"show_in_invoice\": true,\n \"show_in_estimate\": false,\n \"show_in_statement\": false,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n ],\n \"customer_remarks\": [\n {\n \"id\": 1,\n \"job_card_id\": 1,\n \"remark\": \"Customer prefers morning pickup.\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n ],\n \"attachment_files\": []\n }\n}" + "body": "{\n \"message\": \"Job card created successfully\",\n \"data\": {\n \"id\": 1,\n \"title\": \"Job Card 001\",\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"estimate_id\": 1,\n \"sales_person_id\": 3,\n \"order_number\": \"ORD-001\",\n \"estimate_number\": \"EST-001\",\n \"order_date\": \"2026-03-31T00:00:00.000000Z\",\n \"service_writer_id\": 1,\n \"primary_technician_id\": 2,\n \"status\": \"check_in\",\n \"footer\": \"Thank you for your business.\",\n \"has_insurance\": false,\n \"insurance_type_id\": null,\n \"insurer_id\": null,\n \"estimate_to\": \"Customer\",\n \"enable_parts_issuing\": false,\n \"enable_digital_authorisation\": false,\n \"department_id\": 1,\n \"check_in_date\": \"2026-03-31T00:00:00.000000Z\",\n \"check_in_time\": \"09:00\",\n \"km_in\": 50000,\n \"fuel_level\": \"full\",\n \"start_date\": \"2026-03-31T00:00:00.000000Z\",\n \"start_time\": \"09:00\",\n \"delivery_date\": \"2026-04-05T00:00:00.000000Z\",\n \"delivery_time\": \"17:00\",\n \"attachments\": null,\n \"tax_inclusive\": \"Tax Exclusive\",\n \"discount_type\": \"no\",\n \"discount_at\": \"inclusive_of_tax\",\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 \"job_card_id\": 1,\n \"label_id\": 1,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n }\n ],\n \"documents\": [\n {\n \"id\": 1,\n \"job_card_id\": 1,\n \"document_type_id\": 1,\n \"customer_id\": 1,\n \"vehicle_id\": 1,\n \"document_number\": \"DOC-001\",\n \"show_in_invoice\": true,\n \"show_in_estimate\": false,\n \"show_in_statement\": false,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n ],\n \"customer_remarks\": [\n {\n \"id\": 1,\n \"job_card_id\": 1,\n \"remark\": \"Customer prefers morning pickup.\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n ],\n \"attachment_files\": []\n }\n}" } ] }, @@ -20740,6 +22352,113 @@ "body": "{\n \"message\": \"Sales person updated successfully\",\n \"data\": {\n \"id\": 1,\n \"sales_person_id\": 1,\n \"title\": \"Job Card 001\"\n }\n}" } ] + }, + { + "name": "POST /api/job-cards/{id}/change-technician-id", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"technician_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/job-cards/{{id}}/change-technician-id", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "job-cards", + "{{id}}", + "change-technician-id" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"technician_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/job-cards/{{id}}/change-technician-id", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "job-cards", + "{{id}}", + "change-technician-id" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Technician updated successfully\",\n \"data\": {\n \"id\": 1,\n \"technician_id\": 1,\n \"title\": \"Job Card 001\"\n }\n}" + } + ] } ] }, @@ -21597,7 +23316,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"id\": 1,\n \"shop_type_id\": 1,\n \"category_id\": 1,\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"part_number\": \"PRT-001\",\n \"unit_type_id\": 1,\n \"manufactured_by\": \"Toyota\",\n \"description\": \"string\",\n \"location\": \"Shelf A1\",\n \"pricing_matrix\": \"string\",\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"sales_chart_of_account\": \"string\",\n \"purchase_information\": true,\n \"purchase_chart_of_account\": \"string\",\n \"purchase_preferred_vendor_id\": 1,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"opening_stock\": 1,\n \"as_on_date\": \"2026-03-31\",\n \"min_stock\": 1,\n \"max_stock\": 1,\n \"inventory_chart_of_account\": \"string\",\n \"tracking_type\": \"string\",\n \"inventory_purchase_price\": 1.0,\n \"is_active\": true,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n ],\n \"meta\": {\n \"current_page\": 1,\n \"last_page\": 5,\n \"per_page\": 15,\n \"total\": 75\n },\n \"links\": {\n \"first\": \"https://api.example.com/resource?page=1\",\n \"last\": \"https://api.example.com/resource?page=5\",\n \"prev\": \"string\",\n \"next\": \"https://api.example.com/resource?page=2\"\n }\n}" + "body": "{\n \"data\": [\n {\n \"id\": 1,\n \"shop_type_id\": 1,\n \"category_id\": 1,\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"part_number\": \"PRT-001\",\n \"unit_type_id\": 1,\n \"manufactured_by\": \"Toyota\",\n \"description\": \"string\",\n \"location\": \"Shelf A1\",\n \"pricing_matrix\": \"string\",\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"sales_chart_of_account\": \"string\",\n \"purchase_information\": true,\n \"purchase_chart_of_account\": \"string\",\n \"purchase_preferred_vendor_id\": 1,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"opening_stock\": 1,\n \"available_stock\": 1,\n \"as_on_date\": \"2026-03-31\",\n \"min_stock\": 1,\n \"max_stock\": 1,\n \"inventory_chart_of_account\": \"string\",\n \"tracking_type\": \"string\",\n \"inventory_purchase_price\": 1.0,\n \"is_active\": true,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n ],\n \"meta\": {\n \"current_page\": 1,\n \"last_page\": 5,\n \"per_page\": 15,\n \"total\": 75\n },\n \"links\": {\n \"first\": \"https://api.example.com/resource?page=1\",\n \"last\": \"https://api.example.com/resource?page=5\",\n \"prev\": \"string\",\n \"next\": \"https://api.example.com/resource?page=2\"\n }\n}" } ] }, @@ -21627,7 +23346,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"category_id\": 1,\n \"unit_type_id\": 1,\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"purchase_information\": true,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"is_active\": true\n}", + "raw": "{\n \"shop_type_id\": 1,\n \"category_id\": 1,\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"part_number\": \"PRT-001\",\n \"unit_type_id\": 1,\n \"manufactured_by\": \"Toyota\",\n \"description\": \"Premium quality oil filter\",\n \"location\": \"Shelf A1\",\n \"pricing_matrix\": \"retail\",\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"sales_chart_of_account\": 101,\n \"purchase_information\": true,\n \"purchase_chart_of_account\": 201,\n \"purchase_preferred_vendor_id\": 1,\n \"purchase_price\": 15,\n \"track_inventory\": true,\n \"opening_stock\": 10,\n \"available_stock\": 10,\n \"as_on_date\": \"2026-03-31\",\n \"min_stock\": 2,\n \"max_stock\": 100,\n \"inventory_chart_of_account\": 301,\n \"tracking_type\": 1,\n \"inventory_purchase_price\": 15\n}", "options": { "raw": { "language": "json" @@ -21647,7 +23366,7 @@ }, "response": [ { - "name": "200 OK", + "name": "201 Created", "originalRequest": { "auth": { "type": "bearer", @@ -21672,7 +23391,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"category_id\": 1,\n \"unit_type_id\": 1,\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"purchase_information\": true,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"is_active\": true\n}", + "raw": "{\n \"shop_type_id\": 1,\n \"category_id\": 1,\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"part_number\": \"PRT-001\",\n \"unit_type_id\": 1,\n \"manufactured_by\": \"Toyota\",\n \"description\": \"Premium quality oil filter\",\n \"location\": \"Shelf A1\",\n \"pricing_matrix\": \"retail\",\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"sales_chart_of_account\": 101,\n \"purchase_information\": true,\n \"purchase_chart_of_account\": 201,\n \"purchase_preferred_vendor_id\": 1,\n \"purchase_price\": 15,\n \"track_inventory\": true,\n \"opening_stock\": 10,\n \"available_stock\": 10,\n \"as_on_date\": \"2026-03-31\",\n \"min_stock\": 2,\n \"max_stock\": 100,\n \"inventory_chart_of_account\": 301,\n \"tracking_type\": 1,\n \"inventory_purchase_price\": 15\n}", "options": { "raw": { "language": "json" @@ -21690,8 +23409,8 @@ ] } }, - "status": "OK", - "code": 200, + "status": "Created", + "code": 201, "_postman_previewlanguage": "json", "header": [ { @@ -21700,7 +23419,7 @@ } ], "cookie": [], - "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"shop_type_id\": 1,\n \"category_id\": 1,\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"part_number\": \"PRT-001\",\n \"unit_type_id\": 1,\n \"manufactured_by\": \"Toyota\",\n \"description\": \"string\",\n \"location\": \"Shelf A1\",\n \"pricing_matrix\": \"string\",\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"sales_chart_of_account\": \"string\",\n \"purchase_information\": true,\n \"purchase_chart_of_account\": \"string\",\n \"purchase_preferred_vendor_id\": 1,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"opening_stock\": 1,\n \"as_on_date\": \"2026-03-31\",\n \"min_stock\": 1,\n \"max_stock\": 1,\n \"inventory_chart_of_account\": \"string\",\n \"tracking_type\": \"string\",\n \"inventory_purchase_price\": 1.0,\n \"is_active\": true,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}" + "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"shop_type_id\": 1,\n \"category_id\": 1,\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"part_number\": \"PRT-001\",\n \"unit_type_id\": 1,\n \"manufactured_by\": \"Toyota\",\n \"description\": \"string\",\n \"location\": \"Shelf A1\",\n \"pricing_matrix\": \"string\",\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"sales_chart_of_account\": \"string\",\n \"purchase_information\": true,\n \"purchase_chart_of_account\": \"string\",\n \"purchase_preferred_vendor_id\": 1,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"opening_stock\": 1,\n \"available_stock\": 1,\n \"as_on_date\": \"2026-03-31\",\n \"min_stock\": 1,\n \"max_stock\": 1,\n \"inventory_chart_of_account\": \"string\",\n \"tracking_type\": \"string\",\n \"inventory_purchase_price\": 1.0,\n \"is_active\": true,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}" } ] }, @@ -21787,7 +23506,7 @@ } ], "cookie": [], - "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"shop_type_id\": 1,\n \"category_id\": 1,\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"part_number\": \"PRT-001\",\n \"unit_type_id\": 1,\n \"manufactured_by\": \"Toyota\",\n \"description\": \"string\",\n \"location\": \"Shelf A1\",\n \"pricing_matrix\": \"string\",\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"sales_chart_of_account\": \"string\",\n \"purchase_information\": true,\n \"purchase_chart_of_account\": \"string\",\n \"purchase_preferred_vendor_id\": 1,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"opening_stock\": 1,\n \"as_on_date\": \"2026-03-31\",\n \"min_stock\": 1,\n \"max_stock\": 1,\n \"inventory_chart_of_account\": \"string\",\n \"tracking_type\": \"string\",\n \"inventory_purchase_price\": 1.0,\n \"is_active\": true,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}" + "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"shop_type_id\": 1,\n \"category_id\": 1,\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"part_number\": \"PRT-001\",\n \"unit_type_id\": 1,\n \"manufactured_by\": \"Toyota\",\n \"description\": \"string\",\n \"location\": \"Shelf A1\",\n \"pricing_matrix\": \"string\",\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"sales_chart_of_account\": \"string\",\n \"purchase_information\": true,\n \"purchase_chart_of_account\": \"string\",\n \"purchase_preferred_vendor_id\": 1,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"opening_stock\": 1,\n \"available_stock\": 1,\n \"as_on_date\": \"2026-03-31\",\n \"min_stock\": 1,\n \"max_stock\": 1,\n \"inventory_chart_of_account\": \"string\",\n \"tracking_type\": \"string\",\n \"inventory_purchase_price\": 1.0,\n \"is_active\": true,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}" } ] }, @@ -21817,7 +23536,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"category_id\": 1,\n \"unit_type_id\": 1,\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"purchase_information\": true,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"is_active\": true\n}", + "raw": "{\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"category_id\": 1,\n \"unit_type_id\": 1,\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"purchase_information\": true,\n \"purchase_price\": 15,\n \"track_inventory\": true,\n \"opening_stock\": 10,\n \"available_stock\": 8\n}", "options": { "raw": { "language": "json" @@ -21863,7 +23582,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"category_id\": 1,\n \"unit_type_id\": 1,\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"purchase_information\": true,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"is_active\": true\n}", + "raw": "{\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"category_id\": 1,\n \"unit_type_id\": 1,\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"purchase_information\": true,\n \"purchase_price\": 15,\n \"track_inventory\": true,\n \"opening_stock\": 10,\n \"available_stock\": 8\n}", "options": { "raw": { "language": "json" @@ -21892,7 +23611,7 @@ } ], "cookie": [], - "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"shop_type_id\": 1,\n \"category_id\": 1,\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"part_number\": \"PRT-001\",\n \"unit_type_id\": 1,\n \"manufactured_by\": \"Toyota\",\n \"description\": \"string\",\n \"location\": \"Shelf A1\",\n \"pricing_matrix\": \"string\",\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"sales_chart_of_account\": \"string\",\n \"purchase_information\": true,\n \"purchase_chart_of_account\": \"string\",\n \"purchase_preferred_vendor_id\": 1,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"opening_stock\": 1,\n \"as_on_date\": \"2026-03-31\",\n \"min_stock\": 1,\n \"max_stock\": 1,\n \"inventory_chart_of_account\": \"string\",\n \"tracking_type\": \"string\",\n \"inventory_purchase_price\": 1.0,\n \"is_active\": true,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}" + "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"shop_type_id\": 1,\n \"category_id\": 1,\n \"title\": \"Engine Oil Filter\",\n \"sku\": \"SKU-001\",\n \"part_number\": \"PRT-001\",\n \"unit_type_id\": 1,\n \"manufactured_by\": \"Toyota\",\n \"description\": \"string\",\n \"location\": \"Shelf A1\",\n \"pricing_matrix\": \"string\",\n \"department_id\": 1,\n \"sales_information\": true,\n \"selling_price\": 25,\n \"sales_chart_of_account\": \"string\",\n \"purchase_information\": true,\n \"purchase_chart_of_account\": \"string\",\n \"purchase_preferred_vendor_id\": 1,\n \"purchase_price\": 15,\n \"track_inventory\": false,\n \"opening_stock\": 1,\n \"available_stock\": 1,\n \"as_on_date\": \"2026-03-31\",\n \"min_stock\": 1,\n \"max_stock\": 1,\n \"inventory_chart_of_account\": \"string\",\n \"tracking_type\": \"string\",\n \"inventory_purchase_price\": 1.0,\n \"is_active\": true,\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}" } ] }, @@ -22403,7 +24122,7 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"id\": 1,\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts\",\n \"order_number\": \"PO-001\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-07\",\n \"department_id\": 1,\n \"notes\": \"string\",\n \"terms_and_conditions\": \"string\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n ],\n \"meta\": {\n \"current_page\": 1,\n \"last_page\": 5,\n \"per_page\": 15,\n \"total\": 75\n },\n \"links\": {\n \"first\": \"https://api.example.com/resource?page=1\",\n \"last\": \"https://api.example.com/resource?page=5\",\n \"prev\": \"string\",\n \"next\": \"https://api.example.com/resource?page=2\"\n }\n}" + "body": "{\n \"data\": [\n {\n \"id\": 1,\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts\",\n \"order_number\": \"PO-001\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-07\",\n \"department_id\": 1,\n \"notes\": \"string\",\n \"terms_and_conditions\": \"string\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\",\n \"parts\": [\n {\n \"id\": 1,\n \"purchase_order_id\": 1,\n \"part_id\": 1,\n \"quantity\": 2,\n \"rate\": \"45.50\",\n \"description\": \"Oil filter\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\",\n \"part\": {\n \"id\": 1,\n \"title\": \"Oil Filter\"\n }\n }\n ]\n }\n ],\n \"meta\": {\n \"current_page\": 1,\n \"last_page\": 5,\n \"per_page\": 15,\n \"total\": 75,\n \"from\": 1,\n \"to\": 15\n }\n}" } ] }, @@ -22433,7 +24152,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-07\",\n \"department_id\": 1,\n \"notes\": \"string\"\n}", + "raw": "{\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts\",\n \"order_number\": \"PO-001\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-07\",\n \"department_id\": 1,\n \"notes\": \"string\",\n \"terms_and_conditions\": \"Net 30\",\n \"items\": [\n {\n \"part_id\": 1,\n \"quantity\": 2,\n \"rate\": 45.5,\n \"description\": \"Oil filter\"\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -22453,7 +24172,7 @@ }, "response": [ { - "name": "200 OK", + "name": "201 Created", "originalRequest": { "auth": { "type": "bearer", @@ -22478,7 +24197,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-07\",\n \"department_id\": 1,\n \"notes\": \"string\"\n}", + "raw": "{\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts\",\n \"order_number\": \"PO-001\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-07\",\n \"department_id\": 1,\n \"notes\": \"string\",\n \"terms_and_conditions\": \"Net 30\",\n \"items\": [\n {\n \"part_id\": 1,\n \"quantity\": 2,\n \"rate\": 45.5,\n \"description\": \"Oil filter\"\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -22496,8 +24215,8 @@ ] } }, - "status": "OK", - "code": 200, + "status": "Created", + "code": 201, "_postman_previewlanguage": "json", "header": [ { @@ -22506,7 +24225,7 @@ } ], "cookie": [], - "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts\",\n \"order_number\": \"PO-001\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-07\",\n \"department_id\": 1,\n \"notes\": \"string\",\n \"terms_and_conditions\": \"string\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}" + "body": "{\n \"message\": \"Purchase order created successfully\",\n \"data\": {\n \"id\": 1,\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts\",\n \"order_number\": \"PO-001\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-07\",\n \"department_id\": 1,\n \"notes\": \"string\",\n \"terms_and_conditions\": \"Net 30\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\",\n \"parts\": [\n {\n \"id\": 1,\n \"purchase_order_id\": 1,\n \"part_id\": 1,\n \"quantity\": 2,\n \"rate\": \"45.50\",\n \"description\": \"Oil filter\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\",\n \"part\": {\n \"id\": 1,\n \"title\": \"Oil Filter\"\n }\n }\n ]\n }\n}" } ] }, @@ -22536,7 +24255,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-07\",\n \"department_id\": 1,\n \"notes\": \"string\"\n}", + "raw": "{\n \"title\": \"Purchase Order for Parts (updated)\",\n \"order_number\": \"PO-001\",\n \"delivery_date\": \"2026-04-08\",\n \"items\": [\n {\n \"part_id\": 1,\n \"quantity\": 3,\n \"rate\": 45.5,\n \"description\": \"Oil filter - updated qty\"\n },\n {\n \"part_id\": 2,\n \"quantity\": 1,\n \"rate\": 120,\n \"description\": \"Air filter\"\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -22582,7 +24301,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-07\",\n \"department_id\": 1,\n \"notes\": \"string\"\n}", + "raw": "{\n \"title\": \"Purchase Order for Parts (updated)\",\n \"order_number\": \"PO-001\",\n \"delivery_date\": \"2026-04-08\",\n \"items\": [\n {\n \"part_id\": 1,\n \"quantity\": 3,\n \"rate\": 45.5,\n \"description\": \"Oil filter - updated qty\"\n },\n {\n \"part_id\": 2,\n \"quantity\": 1,\n \"rate\": 120,\n \"description\": \"Air filter\"\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -22611,7 +24330,7 @@ } ], "cookie": [], - "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts\",\n \"order_number\": \"PO-001\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-07\",\n \"department_id\": 1,\n \"notes\": \"string\",\n \"terms_and_conditions\": \"string\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}" + "body": "{\n \"message\": \"Purchase order updated successfully\",\n \"data\": {\n \"id\": 1,\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"title\": \"Purchase Order for Parts (updated)\",\n \"order_number\": \"PO-001\",\n \"order_date\": \"2026-03-31\",\n \"delivery_date\": \"2026-04-08\",\n \"department_id\": 1,\n \"notes\": \"string\",\n \"terms_and_conditions\": \"Net 30\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:20:00.000000Z\",\n \"parts\": [\n {\n \"id\": 3,\n \"purchase_order_id\": 1,\n \"part_id\": 1,\n \"quantity\": 3,\n \"rate\": \"45.50\",\n \"description\": \"Oil filter - updated qty\",\n \"created_at\": \"2026-03-31T10:20:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:20:00.000000Z\",\n \"part\": {\n \"id\": 1,\n \"title\": \"Oil Filter\"\n }\n },\n {\n \"id\": 4,\n \"purchase_order_id\": 1,\n \"part_id\": 2,\n \"quantity\": 1,\n \"rate\": \"120.00\",\n \"description\": \"Air filter\",\n \"created_at\": \"2026-03-31T10:20:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:20:00.000000Z\",\n \"part\": {\n \"id\": 2,\n \"title\": \"Air Filter\"\n }\n }\n ]\n }\n}" } ] }, @@ -22698,7 +24417,632 @@ } ], "cookie": [], - "body": "{\n \"message\": \"Deleted successfully.\"\n}" + "body": "{\n \"message\": \"Purchase order deleted successfully\"\n}" + } + ] + }, + { + "name": "POST /api/purchase-orders/{id}/add-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "attachments[]", + "type": "file", + "description": "Repeat attachments[] for multiple files (max total 20, 5MB each).", + "value": "" + } + ] + }, + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/add-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "add-attachment" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "attachments[]", + "type": "file", + "src": [ + "" + ] + } + ] + }, + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/add-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "add-attachment" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Purchase order attachments added successfully\",\n \"data\": {\n \"id\": 1,\n \"attachments\": [\n {\n \"id\": 1,\n \"purchase_order_id\": 1,\n \"file_path\": \"purchase_order_attachments/sample.pdf\",\n \"created_at\": \"2026-04-13T10:00:00.000000Z\",\n \"updated_at\": \"2026-04-13T10:00:00.000000Z\"\n }\n ]\n }\n}" + } + ] + }, + { + "name": "DELETE /api/purchase-orders/{id}/delete-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"attachment_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/delete-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "delete-attachment" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"attachment_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/delete-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "delete-attachment" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Purchase order attachment deleted successfully\",\n \"data\": {\n \"id\": 1,\n \"attachments\": []\n }\n}" + } + ] + }, + { + "name": "GET /api/purchase-orders/{id}/get-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/get-attachment?attachment_id=1", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "get-attachment" + ], + "query": [ + { + "key": "attachment_id", + "value": "1" + } + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/get-attachment?attachment_id=1", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "get-attachment" + ], + "query": [ + { + "key": "attachment_id", + "value": "1" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": {\n \"id\": 1,\n \"purchase_order_id\": 1,\n \"file_path\": \"purchase_order_attachments/sample.pdf\",\n \"url\": \"http://localhost/storage/purchase_order_attachments/sample.pdf\",\n \"created_at\": \"2026-04-13T10:00:00.000000Z\",\n \"updated_at\": \"2026-04-13T10:00:00.000000Z\"\n }\n}" + } + ] + }, + { + "name": "POST /api/purchase-orders/{id}/add-internal-note", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"note\": \"Follow up with vendor on ETA.\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/add-internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "add-internal-note" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"note\": \"Follow up with vendor on ETA.\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/add-internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "add-internal-note" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Purchase order internal note added successfully\",\n \"data\": {\n \"id\": 1,\n \"internal_notes\": [\n {\n \"id\": 1,\n \"purchase_order_id\": 1,\n \"note\": \"Follow up with vendor on ETA.\",\n \"created_at\": \"2026-04-13T10:00:00.000000Z\",\n \"updated_at\": \"2026-04-13T10:00:00.000000Z\"\n }\n ]\n }\n}" + } + ] + }, + { + "name": "POST /api/purchase-orders/{id}/edit-internal-note", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"internal_note_id\": 1,\n \"note\": \"Updated internal note text.\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/edit-internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "edit-internal-note" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"internal_note_id\": 1,\n \"note\": \"Updated internal note text.\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/edit-internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "edit-internal-note" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Purchase order internal note updated successfully\",\n \"data\": {\n \"id\": 1,\n \"internal_notes\": [\n {\n \"id\": 1,\n \"purchase_order_id\": 1,\n \"note\": \"Updated internal note text.\",\n \"created_at\": \"2026-04-13T10:00:00.000000Z\",\n \"updated_at\": \"2026-04-13T10:20:00.000000Z\"\n }\n ]\n }\n}" + } + ] + }, + { + "name": "DELETE /api/purchase-orders/{id}/delete-internal-note", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"internal_note_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/delete-internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "delete-internal-note" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"internal_note_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/purchase-orders/{{id}}/delete-internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "purchase-orders", + "{{id}}", + "delete-internal-note" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Purchase order internal note deleted successfully\",\n \"data\": {\n \"id\": 1,\n \"internal_notes\": []\n }\n}" } ] } @@ -23973,12 +26317,12 @@ } ], "cookie": [], - "body": "{\n \"data\": [\n {\n \"id\": 1,\n \"title\": \"Parts Bill\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"bill_number\": \"BILL-001\",\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"notes\": \"string\",\n \"status\": \"draft\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n ],\n \"meta\": {\n \"current_page\": 1,\n \"last_page\": 5,\n \"per_page\": 15,\n \"total\": 75\n },\n \"links\": {\n \"first\": \"https://api.example.com/resource?page=1\",\n \"last\": \"https://api.example.com/resource?page=5\",\n \"prev\": \"string\",\n \"next\": \"https://api.example.com/resource?page=2\"\n }\n}" + "body": "{\n \"data\": [\n {\n \"id\": 1,\n \"title\": \"Workshop Bill\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"purchase_order_id\": 1,\n \"bill_number\": \"BILL-001\",\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"notes\": \"Vendor invoice for parts, service and expense lines\",\n \"status\": \"open\",\n \"services\": [\n {\n \"id\": 1,\n \"bill_id\": 1,\n \"service_id\": 1,\n \"quantity\": \"2.00\",\n \"rate\": \"150.00\",\n \"chart_of_account\": \"COA-401\",\n \"description\": \"Labor service line\"\n }\n ],\n \"parts\": [\n {\n \"id\": 1,\n \"bill_id\": 1,\n \"part_id\": 1,\n \"quantity\": 3,\n \"rate\": \"40.00\",\n \"chart_of_account\": 1201,\n \"description\": \"Brake pad set\"\n }\n ],\n \"expenses\": [\n {\n \"id\": 1,\n \"bill_id\": 1,\n \"expense_id\": 1,\n \"quantity\": \"1.00\",\n \"rate\": \"75.00\",\n \"chart_of_account\": \"COA-402\",\n \"description\": \"Consumables expense line\"\n }\n ],\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n ],\n \"meta\": {\n \"current_page\": 1,\n \"last_page\": 5,\n \"per_page\": 15,\n \"total\": 75\n },\n \"links\": {\n \"first\": \"https://api.example.com/resource?page=1\",\n \"last\": \"https://api.example.com/resource?page=5\",\n \"prev\": \"string\",\n \"next\": \"https://api.example.com/resource?page=2\"\n }\n}" } ] }, { - "name": "Store a newly created bill with labels and parts.", + "name": "Store a newly created bill with labels, services and expenses.", "request": { "auth": { "type": "bearer", @@ -24003,7 +26347,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Parts Bill\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"status\": \"draft\"\n}", + "raw": "{\n \"title\": \"Workshop Bill\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"purchase_order_id\": 1,\n \"bill_number\": \"BILL-001\",\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"notes\": \"Vendor invoice for parts, service and expense lines\",\n \"label_ids\": [1],\n \"part_items\": [\n {\n \"part_id\": 1,\n \"quantity\": 3,\n \"rate\": 40,\n \"chart_of_account\": 1201,\n \"description\": \"Brake pad set\"\n }\n ],\n \"service_items\": [\n {\n \"service_id\": 1,\n \"quantity\": 2,\n \"rate\": 150,\n \"chart_of_account\": \"COA-401\",\n \"description\": \"Labor service line\"\n }\n ],\n \"expense_items\": [\n {\n \"expense_id\": 1,\n \"quantity\": 1,\n \"rate\": 75,\n \"chart_of_account\": \"COA-402\",\n \"description\": \"Consumables expense line\"\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -24023,7 +26367,7 @@ }, "response": [ { - "name": "200 OK", + "name": "201 Created", "originalRequest": { "auth": { "type": "bearer", @@ -24048,7 +26392,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Parts Bill\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"status\": \"draft\"\n}", + "raw": "{\n \"title\": \"Workshop Bill\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"purchase_order_id\": 1,\n \"bill_number\": \"BILL-001\",\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"notes\": \"Vendor invoice for parts, service and expense lines\",\n \"label_ids\": [1],\n \"part_items\": [\n {\n \"part_id\": 1,\n \"quantity\": 3,\n \"rate\": 40,\n \"chart_of_account\": 1201,\n \"description\": \"Brake pad set\"\n }\n ],\n \"service_items\": [\n {\n \"service_id\": 1,\n \"quantity\": 2,\n \"rate\": 150,\n \"chart_of_account\": \"COA-401\",\n \"description\": \"Labor service line\"\n }\n ],\n \"expense_items\": [\n {\n \"expense_id\": 1,\n \"quantity\": 1,\n \"rate\": 75,\n \"chart_of_account\": \"COA-402\",\n \"description\": \"Consumables expense line\"\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -24066,8 +26410,8 @@ ] } }, - "status": "OK", - "code": 200, + "status": "Created", + "code": 201, "_postman_previewlanguage": "json", "header": [ { @@ -24076,7 +26420,7 @@ } ], "cookie": [], - "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"title\": \"Parts Bill\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"bill_number\": \"BILL-001\",\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"notes\": \"string\",\n \"status\": \"draft\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}" + "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"title\": \"Workshop Bill\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"purchase_order_id\": 1,\n \"bill_number\": \"BILL-001\",\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"notes\": \"Vendor invoice for parts, service and expense lines\",\n \"status\": \"open\",\n \"services\": [\n {\n \"id\": 1,\n \"bill_id\": 1,\n \"service_id\": 1,\n \"quantity\": \"2.00\",\n \"rate\": \"150.00\",\n \"chart_of_account\": \"COA-401\",\n \"description\": \"Labor service line\"\n }\n ],\n \"parts\": [\n {\n \"id\": 1,\n \"bill_id\": 1,\n \"part_id\": 1,\n \"quantity\": 3,\n \"rate\": \"40.00\",\n \"chart_of_account\": 1201,\n \"description\": \"Brake pad set\"\n }\n ],\n \"expenses\": [\n {\n \"id\": 1,\n \"bill_id\": 1,\n \"expense_id\": 1,\n \"quantity\": \"1.00\",\n \"rate\": \"75.00\",\n \"chart_of_account\": \"COA-402\",\n \"description\": \"Consumables expense line\"\n }\n ],\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}" } ] }, @@ -24106,7 +26450,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Parts Bill\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"status\": \"draft\"\n}", + "raw": "{\n \"title\": \"Workshop Bill - Updated\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"purchase_order_id\": 1,\n \"bill_number\": \"BILL-001\",\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-20\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"notes\": \"Updated vendor invoice lines\",\n \"label_ids\": [1],\n \"part_items\": [\n {\n \"part_id\": 1,\n \"quantity\": 1,\n \"rate\": 45,\n \"chart_of_account\": 1201,\n \"description\": \"Updated part line\"\n }\n ],\n \"service_items\": [\n {\n \"service_id\": 1,\n \"quantity\": 1,\n \"rate\": 200,\n \"chart_of_account\": \"COA-401\",\n \"description\": \"Updated labor service line\"\n }\n ],\n \"expense_items\": [\n {\n \"expense_id\": 1,\n \"quantity\": 2,\n \"rate\": 50,\n \"chart_of_account\": \"COA-402\",\n \"description\": \"Updated consumables expense line\"\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -24152,7 +26496,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"title\": \"Parts Bill\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"status\": \"draft\"\n}", + "raw": "{\n \"title\": \"Workshop Bill - Updated\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"purchase_order_id\": 1,\n \"bill_number\": \"BILL-001\",\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-20\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"notes\": \"Updated vendor invoice lines\",\n \"label_ids\": [1],\n \"part_items\": [\n {\n \"part_id\": 1,\n \"quantity\": 1,\n \"rate\": 45,\n \"chart_of_account\": 1201,\n \"description\": \"Updated part line\"\n }\n ],\n \"service_items\": [\n {\n \"service_id\": 1,\n \"quantity\": 1,\n \"rate\": 200,\n \"chart_of_account\": \"COA-401\",\n \"description\": \"Updated labor service line\"\n }\n ],\n \"expense_items\": [\n {\n \"expense_id\": 1,\n \"quantity\": 2,\n \"rate\": 50,\n \"chart_of_account\": \"COA-402\",\n \"description\": \"Updated consumables expense line\"\n }\n ]\n}", "options": { "raw": { "language": "json" @@ -24181,7 +26525,7 @@ } ], "cookie": [], - "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"title\": \"Parts Bill\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"bill_number\": \"BILL-001\",\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-14\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"notes\": \"string\",\n \"status\": \"draft\",\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T10:00:00.000000Z\"\n }\n}" + "body": "{\n \"message\": \"Operation completed successfully.\",\n \"data\": {\n \"id\": 1,\n \"title\": \"Workshop Bill - Updated\",\n \"job_card_id\": 1,\n \"vendor_id\": 1,\n \"vendor_address_id\": 1,\n \"purchase_order_id\": 1,\n \"bill_number\": \"BILL-001\",\n \"bill_date\": \"2026-03-31\",\n \"bill_due_date\": \"2026-04-20\",\n \"payment_terms_id\": 1,\n \"department_id\": 1,\n \"notes\": \"Updated vendor invoice lines\",\n \"status\": \"open\",\n \"services\": [\n {\n \"id\": 2,\n \"bill_id\": 1,\n \"service_id\": 1,\n \"quantity\": \"1.00\",\n \"rate\": \"200.00\",\n \"chart_of_account\": \"COA-401\",\n \"description\": \"Updated labor service line\"\n }\n ],\n \"parts\": [\n {\n \"id\": 2,\n \"bill_id\": 1,\n \"part_id\": 1,\n \"quantity\": 1,\n \"rate\": \"45.00\",\n \"chart_of_account\": 1201,\n \"description\": \"Updated part line\"\n }\n ],\n \"expenses\": [\n {\n \"id\": 2,\n \"bill_id\": 1,\n \"expense_id\": 1,\n \"quantity\": \"2.00\",\n \"rate\": \"50.00\",\n \"chart_of_account\": \"COA-402\",\n \"description\": \"Updated consumables expense line\"\n }\n ],\n \"created_at\": \"2026-03-31T10:00:00.000000Z\",\n \"updated_at\": \"2026-03-31T11:00:00.000000Z\"\n }\n}" } ] }, @@ -24271,6 +26615,631 @@ "body": "{\n \"message\": \"Deleted successfully.\"\n}" } ] + }, + { + "name": "POST /api/bills/{id}/add-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "attachments[]", + "type": "file", + "description": "Repeat attachments[] for multiple files (max total 20, 5MB each).", + "value": "" + } + ] + }, + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/add-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "add-attachment" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "attachments[]", + "type": "file", + "src": [ + "" + ] + } + ] + }, + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/add-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "add-attachment" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Bill attachments added successfully\",\n \"data\": {\n \"id\": 1,\n \"attachments\": [\n {\n \"id\": 1,\n \"bill_id\": 1,\n \"file_path\": \"http://localhost/storage/bill_attachments/sample.pdf\",\n \"created_at\": \"2026-04-13T10:00:00.000000Z\",\n \"updated_at\": \"2026-04-13T10:00:00.000000Z\"\n }\n ]\n }\n}" + } + ] + }, + { + "name": "DELETE /api/bills/{id}/delete-attachment", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"attachment_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/delete-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "delete-attachment" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"attachment_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/delete-attachment", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "delete-attachment" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Bill attachment deleted successfully\",\n \"data\": {\n \"id\": 1,\n \"attachments\": []\n }\n}" + } + ] + }, + { + "name": "GET /api/bills/{id}/internal-note", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/internal-note?per_page=15", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "internal-note" + ], + "query": [ + { + "key": "per_page", + "value": "15" + } + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/json" + } + ], + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/internal-note?per_page=15", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "internal-note" + ], + "query": [ + { + "key": "per_page", + "value": "15" + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"data\": [\n {\n \"id\": 1,\n \"bill_id\": 1,\n \"note\": \"Requested updated invoice copy.\",\n \"created_at\": \"2026-04-13T10:00:00.000000Z\",\n \"updated_at\": \"2026-04-13T10:00:00.000000Z\"\n }\n ],\n \"meta\": {\n \"current_page\": 1,\n \"last_page\": 1,\n \"per_page\": 15,\n \"total\": 1,\n \"from\": 1,\n \"to\": 1\n }\n}" + } + ] + }, + { + "name": "POST /api/bills/{id}/internal-note", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"note\": \"Requested updated invoice copy.\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "internal-note" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"note\": \"Requested updated invoice copy.\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "internal-note" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Bill internal note added successfully\",\n \"data\": {\n \"id\": 1,\n \"internal_notes\": [\n {\n \"id\": 1,\n \"bill_id\": 1,\n \"note\": \"Requested updated invoice copy.\",\n \"created_at\": \"2026-04-13T10:00:00.000000Z\",\n \"updated_at\": \"2026-04-13T10:00:00.000000Z\"\n }\n ]\n }\n}" + } + ] + }, + { + "name": "PUT /api/bills/{id}/internal-note", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"internal_note_id\": 1,\n \"note\": \"Updated internal note text.\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "internal-note" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "PUT", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"internal_note_id\": 1,\n \"note\": \"Updated internal note text.\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "internal-note" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Bill internal note updated successfully\",\n \"data\": {\n \"id\": 1,\n \"internal_notes\": [\n {\n \"id\": 1,\n \"bill_id\": 1,\n \"note\": \"Updated internal note text.\",\n \"created_at\": \"2026-04-13T10:00:00.000000Z\",\n \"updated_at\": \"2026-04-13T10:20:00.000000Z\"\n }\n ]\n }\n}" + } + ] + }, + { + "name": "DELETE /api/bills/{id}/internal-note", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"internal_note_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "internal-note" + ] + } + }, + "response": [ + { + "name": "200 OK", + "originalRequest": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "DELETE", + "header": [ + { + "key": "Accept", + "value": "application/json" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"internal_note_id\": 1\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "{{base_url}}/api/bills/{{id}}/internal-note", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "bills", + "{{id}}", + "internal-note" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "cookie": [], + "body": "{\n \"message\": \"Bill internal note deleted successfully\",\n \"data\": {\n \"id\": 1,\n \"internal_notes\": []\n }\n}" + } + ] } ] }, @@ -30600,7 +33569,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"subject\": \"Invoice for Service\",\n \"customer_id\": 1,\n \"vehicle_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 \"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}", "options": { "raw": { "language": "json" @@ -30645,7 +33614,7 @@ ], "body": { "mode": "raw", - "raw": "{\n \"subject\": \"Invoice for Service\",\n \"customer_id\": 1,\n \"vehicle_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 \"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}", "options": { "raw": { "language": "json" @@ -30673,7 +33642,7 @@ } ], "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 \"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\": \"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}" } ] }, @@ -39549,6 +42518,102 @@ ] } ] + }, + { + "name": "Document print / download", + "item": [ + { + "name": "GET /api/document-print", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "GET", + "header": [ + { + "key": "Accept", + "value": "application/pdf" + } + ], + "url": { + "raw": "{{base_url}}/api/document-print?type=invoice&id=1&mode=download", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "document-print" + ], + "query": [ + { + "key": "type", + "value": "invoice", + "description": "inspection | estimate | job_card | invoice | payment_received | expense | purchase_order | bill | payment_made" + }, + { + "key": "id", + "value": "1" + }, + { + "key": "mode", + "value": "download", + "description": "print = open in browser; download = attachment" + } + ] + } + }, + "response": [] + }, + { + "name": "POST /api/document-print", + "request": { + "auth": { + "type": "bearer", + "bearer": [ + { + "key": "token", + "value": "{{auth_token}}", + "type": "string" + } + ] + }, + "method": "POST", + "header": [ + { + "key": "Accept", + "value": "application/pdf" + }, + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"type\": \"job_card\",\n \"id\": 1,\n \"mode\": \"print\"\n}" + }, + "url": { + "raw": "{{base_url}}/api/document-print", + "host": [ + "{{base_url}}" + ], + "path": [ + "api", + "document-print" + ] + } + }, + "response": [] + } + ], + "description": "Unified PDF endpoint. Query/body: type = inspection | estimate | job_card | invoice | payment_received | expense | purchase_order | bill | payment_made; id = record id; mode = print (inline) | download (attachment)." } ], "auth": { diff --git a/packages/api/src/clients/purchase-orders.ts b/packages/api/src/clients/purchase-orders.ts index dab0ecb..82015b9 100644 --- a/packages/api/src/clients/purchase-orders.ts +++ b/packages/api/src/clients/purchase-orders.ts @@ -14,4 +14,9 @@ export class PurchaseOrdersClient extends CrudClient< constructor(baseUrl?: string, defaultOptions?: ApiClientOptions) { super(baseUrl, defaultOptions, PURCHASE_ORDER_ROUTES.INDEX, PURCHASE_ORDER_ROUTES.BY_ID) } + + async getById(id: string) { + const res= await this.list({query: { id }}) + return { ...res, data: (res as any)?.data[0] ?? res } + } } diff --git a/packages/api/types/index.ts b/packages/api/types/index.ts index d710f9e..ae36c4e 100644 --- a/packages/api/types/index.ts +++ b/packages/api/types/index.ts @@ -12830,6 +12830,1395 @@ export interface paths { patch?: never; trace?: never; }; + "/api/estimates/{id}/link-appointment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/estimates/{id}/link-appointment */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "appointment_id": 1 + * } + */ + "application/json": { + appointment_id?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimates/{id}/unlink-appointment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/estimates/{id}/unlink-appointment */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "appointment_id": 1 + * } + */ + "application/json": { + appointment_id?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimates/{id}/store-authorisation": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/estimates/{id}/store-authorisation */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "services_status": "accepted", + * "parts_status": "accepted", + * "inspections_status": "accepted", + * "expense_items_status": "accepted", + * "authorisation_method": "in_person", + * "employee_id": 1 + * } + */ + "application/json": { + services_status?: string; + parts_status?: string; + inspections_status?: string; + expense_items_status?: string; + authorisation_method?: string; + employee_id?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Estimate authorisation stored successfully", + * "data": { + * "estimate": { + * "id": 1, + * "is_authorisation": true, + * "authorisation_date": "2026-04-13", + * "authorisation_time": "14:30:00", + * "authorisation_method": "in_person", + * "total_accepted": 8 + * }, + * "updated_counts": { + * "services": 3, + * "parts": 2, + * "inspections": 1, + * "expense_items": 2 + * }, + * "histories_count": 9 + * } + * } + */ + "application/json": { + message?: string; + data?: { + estimate?: { + id?: number; + is_authorisation?: boolean; + authorisation_date?: string; + authorisation_time?: string; + authorisation_method?: string; + total_accepted?: number; + }; + updated_counts?: { + services?: number; + parts?: number; + inspections?: number; + expense_items?: number; + }; + histories_count?: number; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimates/{id}/update-invoice": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/estimates/{id}/update-invoice */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "invoice_id": 1 + * } + */ + "application/json": { + invoice_id?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Invoice synced from estimate successfully.", + * "data": { + * "invoice": { + * "id": 1, + * "estimate_id": 1, + * "invoice_parts": [ + * { + * "part_id": 1, + * "quantity": "2.00", + * "rate": "45.50" + * } + * ], + * "invoice_inpsection_categories": [ + * { + * "inspection_category_id": 1, + * "rate_type": "flat_rate", + * "rate": "80.00" + * } + * ], + * "invoice_services": [ + * { + * "service_id": 1, + * "quantity": "1.00", + * "rate": "120.00" + * } + * ], + * "invoice_expenses": [ + * { + * "expense_id": 1, + * "quantity": "1.00", + * "rate": "25.00" + * } + * ] + * }, + * "synced_counts": { + * "services": 1, + * "parts": 1, + * "inspections": 1, + * "expense_items": 1 + * } + * } + * } + */ + "application/json": { + message?: string; + data?: { + invoice?: { + id?: number; + estimate_id?: number; + invoice_parts?: { + part_id?: number; + quantity?: string; + rate?: string; + }[]; + invoice_inpsection_categories?: { + inspection_category_id?: number; + rate_type?: string; + rate?: string; + }[]; + invoice_services?: { + service_id?: number; + quantity?: string; + rate?: string; + }[]; + invoice_expenses?: { + expense_id?: number; + quantity?: string; + rate?: string; + }[]; + }; + synced_counts?: { + services?: number; + parts?: number; + inspections?: number; + expense_items?: number; + }; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimates/{id}/add-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/estimates/{id}/add-attachment */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "multipart/form-data": { + /** Format: binary */ + "attachments[]"?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimates/{id}/delete-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** DELETE /api/estimates/{id}/delete-attachment */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimates/{id}/get-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/estimates/{id}/get-attachment */ + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/documents": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/estimate/{id}/documents */ + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + put?: never; + /** POST /api/estimate/{id}/documents */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "document_type_id": 1, + * "customer_id": 1, + * "vehicle_id": 1 + * } + */ + "application/json": { + document_type_id?: number; + customer_id?: number; + vehicle_id?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/documents/{document_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** PUT /api/estimate/{id}/documents/{document_id} */ + put: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + document_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "document_type_id": 1, + * "customer_id": 1, + * "vehicle_id": 1 + * } + */ + "application/json": { + document_type_id?: number; + customer_id?: number; + vehicle_id?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + post?: never; + /** DELETE /api/estimate/{id}/documents/{document_id} */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + document_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/services": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/estimate/{id}/services */ + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + put?: never; + /** POST /api/estimate/{id}/services */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "service_id": 1, + * "rate_type": "flat_rate", + * "labor_rate_id": 1, + * "quantity": 1, + * "rate": "120.00", + * "working_hours": "1.00", + * "labor_hours": "1.00", + * "tax": "5", + * "chart_of_account": "4000", + * "department_id": 1, + * "description": "Labor line" + * } + */ + "application/json": { + service_id?: number; + rate_type?: string; + labor_rate_id?: number; + quantity?: number; + rate?: string; + working_hours?: string; + labor_hours?: string; + tax?: string; + chart_of_account?: string; + department_id?: number; + description?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/services/{service_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** PUT /api/estimate/{id}/services/{service_id} */ + put: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + service_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "rate": "130.00", + * "quantity": 2 + * } + */ + "application/json": { + rate?: string; + quantity?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + post?: never; + /** DELETE /api/estimate/{id}/services/{service_id} */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + service_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/services/{service_id}/add-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/estimate/{id}/services/{service_id}/add-attachment */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + service_id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "multipart/form-data": { + /** Format: binary */ + "attachments[]"?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/services/{service_id}/delete-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** DELETE /api/estimate/{id}/services/{service_id}/delete-attachment */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + service_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/services/{service_id}/get-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/estimate/{id}/services/{service_id}/get-attachment */ + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + service_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/parts": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/estimate/{id}/parts */ + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + put?: never; + /** POST /api/estimate/{id}/parts */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "part_id": 1, + * "quantity": 2, + * "rate": "45.50", + * "tax": "5", + * "chart_of_account": "5000", + * "department_id": 1, + * "description": "Oil filter" + * } + */ + "application/json": { + part_id?: number; + quantity?: number; + rate?: string; + tax?: string; + chart_of_account?: string; + department_id?: number; + description?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/parts/{part_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** PUT /api/estimate/{id}/parts/{part_id} */ + put: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + part_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "quantity": 3 + * } + */ + "application/json": { + quantity?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + post?: never; + /** DELETE /api/estimate/{id}/parts/{part_id} */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + part_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/parts/{part_id}/add-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/estimate/{id}/parts/{part_id}/add-attachment */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + part_id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "multipart/form-data": { + /** Format: binary */ + "attachments[]"?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/parts/{part_id}/delete-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** DELETE /api/estimate/{id}/parts/{part_id}/delete-attachment */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + part_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/parts/{part_id}/get-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/estimate/{id}/parts/{part_id}/get-attachment */ + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + part_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/expense-items": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/estimate/{id}/expense-items */ + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + put?: never; + /** POST /api/estimate/{id}/expense-items */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "expense_item_id": 1, + * "quantity": 1, + * "rate": "25.00", + * "tax": "5", + * "chart_of_account": "6000", + * "department_id": 1, + * "description": "Shop supplies" + * } + */ + "application/json": { + expense_item_id?: number; + quantity?: number; + rate?: string; + tax?: string; + chart_of_account?: string; + department_id?: number; + description?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/expense-items/{expense_item_id}": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + /** PUT /api/estimate/{id}/expense-items/{expense_item_id} */ + put: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + expense_item_id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "quantity": 2 + * } + */ + "application/json": { + quantity?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + post?: never; + /** DELETE /api/estimate/{id}/expense-items/{expense_item_id} */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + expense_item_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/expense-items/{expense_item_id}/add-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/estimate/{id}/expense-items/{expense_item_id}/add-attachment */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + expense_item_id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "multipart/form-data": { + /** Format: binary */ + "attachments[]"?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/expense-items/{expense_item_id}/delete-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** DELETE /api/estimate/{id}/expense-items/{expense_item_id}/delete-attachment */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + expense_item_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/estimate/{id}/expense-items/{expense_item_id}/get-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/estimate/{id}/expense-items/{expense_item_id}/get-attachment */ + get: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + expense_item_id: string; + }; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/estimates": { parameters: { query?: never; @@ -15215,6 +16604,7 @@ export interface paths { * "title": "Job Card 001", * "customer_id": 1, * "vehicle_id": 1, + * "estimate_id": 1, * "order_number": "ORD-001", * "estimate_number": "EST-001", * "order_date": "2026-03-31", @@ -15264,6 +16654,7 @@ export interface paths { title?: string; customer_id?: number; vehicle_id?: number; + estimate_id?: number; order_number?: string; estimate_number?: string; order_date?: string; @@ -15319,6 +16710,7 @@ export interface paths { * "title": "Job Card 001", * "customer_id": 1, * "vehicle_id": 1, + * "estimate_id": 1, * "sales_person_id": 3, * "order_number": "ORD-001", * "estimate_number": "EST-001", @@ -15396,6 +16788,7 @@ export interface paths { title?: string; customer_id?: number; vehicle_id?: number; + estimate_id?: number; sales_person_id?: number; order_number?: string; estimate_number?: string; @@ -19210,6 +20603,72 @@ export interface paths { patch?: never; trace?: never; }; + "/api/job-cards/{id}/change-technician-id": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/job-cards/{id}/change-technician-id */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "technician_id": 1 + * } + */ + "application/json": { + technician_id?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Technician updated successfully", + * "data": { + * "id": 1, + * "technician_id": 1, + * "title": "Job Card 001" + * } + * } + */ + "application/json": { + message?: string; + data?: { + id?: number; + technician_id?: number; + title?: string; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/payment-mode": { parameters: { query?: never; @@ -19800,6 +21259,7 @@ export interface paths { * "purchase_price": 15, * "track_inventory": false, * "opening_stock": 1, + * "available_stock": 1, * "as_on_date": "2026-03-31", * "min_stock": 1, * "max_stock": 1, @@ -19848,6 +21308,7 @@ export interface paths { purchase_price?: number; track_inventory?: boolean; opening_stock?: number; + available_stock?: number; as_on_date?: string; min_stock?: number; max_stock?: number; @@ -19890,37 +21351,69 @@ export interface paths { content: { /** * @example { + * "shop_type_id": 1, + * "category_id": 1, * "title": "Engine Oil Filter", * "sku": "SKU-001", - * "category_id": 1, + * "part_number": "PRT-001", * "unit_type_id": 1, + * "manufactured_by": "Toyota", + * "description": "Premium quality oil filter", + * "location": "Shelf A1", + * "pricing_matrix": "retail", * "department_id": 1, * "sales_information": true, * "selling_price": 25, + * "sales_chart_of_account": 101, * "purchase_information": true, + * "purchase_chart_of_account": 201, + * "purchase_preferred_vendor_id": 1, * "purchase_price": 15, - * "track_inventory": false, - * "is_active": true + * "track_inventory": true, + * "opening_stock": 10, + * "available_stock": 10, + * "as_on_date": "2026-03-31", + * "min_stock": 2, + * "max_stock": 100, + * "inventory_chart_of_account": 301, + * "tracking_type": 1, + * "inventory_purchase_price": 15 * } */ "application/json": { + shop_type_id?: number; + category_id?: number; title?: string; sku?: string; - category_id?: number; + part_number?: string; unit_type_id?: number; + manufactured_by?: string; + description?: string; + location?: string; + pricing_matrix?: string; department_id?: number; sales_information?: boolean; selling_price?: number; + sales_chart_of_account?: number; purchase_information?: boolean; + purchase_chart_of_account?: number; + purchase_preferred_vendor_id?: number; purchase_price?: number; track_inventory?: boolean; - is_active?: boolean; + opening_stock?: number; + available_stock?: number; + as_on_date?: string; + min_stock?: number; + max_stock?: number; + inventory_chart_of_account?: number; + tracking_type?: number; + inventory_purchase_price?: number; }; }; }; responses: { - /** @description OK */ - 200: { + /** @description Created */ + 201: { headers: { [name: string]: unknown; }; @@ -19950,6 +21443,7 @@ export interface paths { * "purchase_price": 15, * "track_inventory": false, * "opening_stock": 1, + * "available_stock": 1, * "as_on_date": "2026-03-31", * "min_stock": 1, * "max_stock": 1, @@ -19986,6 +21480,7 @@ export interface paths { purchase_price?: number; track_inventory?: boolean; opening_stock?: number; + available_stock?: number; as_on_date?: string; min_stock?: number; max_stock?: number; @@ -20059,6 +21554,7 @@ export interface paths { * "purchase_price": 15, * "track_inventory": false, * "opening_stock": 1, + * "available_stock": 1, * "as_on_date": "2026-03-31", * "min_stock": 1, * "max_stock": 1, @@ -20095,6 +21591,7 @@ export interface paths { purchase_price?: number; track_inventory?: boolean; opening_stock?: number; + available_stock?: number; as_on_date?: string; min_stock?: number; max_stock?: number; @@ -20135,8 +21632,9 @@ export interface paths { * "selling_price": 25, * "purchase_information": true, * "purchase_price": 15, - * "track_inventory": false, - * "is_active": true + * "track_inventory": true, + * "opening_stock": 10, + * "available_stock": 8 * } */ "application/json": { @@ -20150,7 +21648,8 @@ export interface paths { purchase_information?: boolean; purchase_price?: number; track_inventory?: boolean; - is_active?: boolean; + opening_stock?: number; + available_stock?: number; }; }; }; @@ -20186,6 +21685,7 @@ export interface paths { * "purchase_price": 15, * "track_inventory": false, * "opening_stock": 1, + * "available_stock": 1, * "as_on_date": "2026-03-31", * "min_stock": 1, * "max_stock": 1, @@ -20222,6 +21722,7 @@ export interface paths { purchase_price?: number; track_inventory?: boolean; opening_stock?: number; + available_stock?: number; as_on_date?: string; min_stock?: number; max_stock?: number; @@ -20551,20 +22052,32 @@ export interface paths { * "notes": "string", * "terms_and_conditions": "string", * "created_at": "2026-03-31T10:00:00.000000Z", - * "updated_at": "2026-03-31T10:00:00.000000Z" + * "updated_at": "2026-03-31T10:00:00.000000Z", + * "parts": [ + * { + * "id": 1, + * "purchase_order_id": 1, + * "part_id": 1, + * "quantity": 2, + * "rate": "45.50", + * "description": "Oil filter", + * "created_at": "2026-03-31T10:00:00.000000Z", + * "updated_at": "2026-03-31T10:00:00.000000Z", + * "part": { + * "id": 1, + * "title": "Oil Filter" + * } + * } + * ] * } * ], * "meta": { * "current_page": 1, * "last_page": 5, * "per_page": 15, - * "total": 75 - * }, - * "links": { - * "first": "https://api.example.com/resource?page=1", - * "last": "https://api.example.com/resource?page=5", - * "prev": "string", - * "next": "https://api.example.com/resource?page=2" + * "total": 75, + * "from": 1, + * "to": 15 * } * } */ @@ -20584,18 +22097,30 @@ export interface paths { created_at?: string; /** Format: date-time */ updated_at?: string; + parts?: { + id?: number; + purchase_order_id?: number; + part_id?: number; + quantity?: number; + rate?: string; + description?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + part?: { + id?: number; + title?: string; + }; + }[]; }[]; meta?: { current_page?: number; last_page?: number; per_page?: number; total?: number; - }; - links?: { - first?: string; - last?: string; - prev?: string; - next?: string; + from?: number; + to?: number; }; }; }; @@ -20618,33 +22143,51 @@ export interface paths { * "job_card_id": 1, * "vendor_id": 1, * "title": "Purchase Order for Parts", + * "order_number": "PO-001", * "order_date": "2026-03-31", * "delivery_date": "2026-04-07", * "department_id": 1, - * "notes": "string" + * "notes": "string", + * "terms_and_conditions": "Net 30", + * "items": [ + * { + * "part_id": 1, + * "quantity": 2, + * "rate": 45.5, + * "description": "Oil filter" + * } + * ] * } */ "application/json": { job_card_id?: number; vendor_id?: number; title?: string; + order_number?: string; order_date?: string; delivery_date?: string; department_id?: number; notes?: string; + terms_and_conditions?: string; + items?: { + part_id?: number; + quantity?: number; + rate?: number; + description?: string; + }[]; }; }; }; responses: { - /** @description OK */ - 200: { + /** @description Created */ + 201: { headers: { [name: string]: unknown; }; content: { /** * @example { - * "message": "Operation completed successfully.", + * "message": "Purchase order created successfully", * "data": { * "id": 1, * "job_card_id": 1, @@ -20655,9 +22198,25 @@ export interface paths { * "delivery_date": "2026-04-07", * "department_id": 1, * "notes": "string", - * "terms_and_conditions": "string", + * "terms_and_conditions": "Net 30", * "created_at": "2026-03-31T10:00:00.000000Z", - * "updated_at": "2026-03-31T10:00:00.000000Z" + * "updated_at": "2026-03-31T10:00:00.000000Z", + * "parts": [ + * { + * "id": 1, + * "purchase_order_id": 1, + * "part_id": 1, + * "quantity": 2, + * "rate": "45.50", + * "description": "Oil filter", + * "created_at": "2026-03-31T10:00:00.000000Z", + * "updated_at": "2026-03-31T10:00:00.000000Z", + * "part": { + * "id": 1, + * "title": "Oil Filter" + * } + * } + * ] * } * } */ @@ -20678,6 +22237,22 @@ export interface paths { created_at?: string; /** Format: date-time */ updated_at?: string; + parts?: { + id?: number; + purchase_order_id?: number; + part_id?: number; + quantity?: number; + rate?: string; + description?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + part?: { + id?: number; + title?: string; + }; + }[]; }; }; }; @@ -20712,23 +22287,35 @@ export interface paths { content: { /** * @example { - * "job_card_id": 1, - * "vendor_id": 1, - * "title": "Purchase Order for Parts", - * "order_date": "2026-03-31", - * "delivery_date": "2026-04-07", - * "department_id": 1, - * "notes": "string" + * "title": "Purchase Order for Parts (updated)", + * "order_number": "PO-001", + * "delivery_date": "2026-04-08", + * "items": [ + * { + * "part_id": 1, + * "quantity": 3, + * "rate": 45.5, + * "description": "Oil filter - updated qty" + * }, + * { + * "part_id": 2, + * "quantity": 1, + * "rate": 120, + * "description": "Air filter" + * } + * ] * } */ "application/json": { - job_card_id?: number; - vendor_id?: number; title?: string; - order_date?: string; + order_number?: string; delivery_date?: string; - department_id?: number; - notes?: string; + items?: { + part_id?: number; + quantity?: number; + rate?: number; + description?: string; + }[]; }; }; }; @@ -20741,20 +22328,50 @@ export interface paths { content: { /** * @example { - * "message": "Operation completed successfully.", + * "message": "Purchase order updated successfully", * "data": { * "id": 1, * "job_card_id": 1, * "vendor_id": 1, - * "title": "Purchase Order for Parts", + * "title": "Purchase Order for Parts (updated)", * "order_number": "PO-001", * "order_date": "2026-03-31", - * "delivery_date": "2026-04-07", + * "delivery_date": "2026-04-08", * "department_id": 1, * "notes": "string", - * "terms_and_conditions": "string", + * "terms_and_conditions": "Net 30", * "created_at": "2026-03-31T10:00:00.000000Z", - * "updated_at": "2026-03-31T10:00:00.000000Z" + * "updated_at": "2026-03-31T10:20:00.000000Z", + * "parts": [ + * { + * "id": 3, + * "purchase_order_id": 1, + * "part_id": 1, + * "quantity": 3, + * "rate": "45.50", + * "description": "Oil filter - updated qty", + * "created_at": "2026-03-31T10:20:00.000000Z", + * "updated_at": "2026-03-31T10:20:00.000000Z", + * "part": { + * "id": 1, + * "title": "Oil Filter" + * } + * }, + * { + * "id": 4, + * "purchase_order_id": 1, + * "part_id": 2, + * "quantity": 1, + * "rate": "120.00", + * "description": "Air filter", + * "created_at": "2026-03-31T10:20:00.000000Z", + * "updated_at": "2026-03-31T10:20:00.000000Z", + * "part": { + * "id": 2, + * "title": "Air Filter" + * } + * } + * ] * } * } */ @@ -20775,6 +22392,22 @@ export interface paths { created_at?: string; /** Format: date-time */ updated_at?: string; + parts?: { + id?: number; + purchase_order_id?: number; + part_id?: number; + quantity?: number; + rate?: string; + description?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + part?: { + id?: number; + title?: string; + }; + }[]; }; }; }; @@ -20802,7 +22435,7 @@ export interface paths { content: { /** * @example { - * "message": "Deleted successfully." + * "message": "Purchase order deleted successfully" * } */ "application/json": { @@ -20817,6 +22450,433 @@ export interface paths { patch?: never; trace?: never; }; + "/api/purchase-orders/{id}/add-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/purchase-orders/{id}/add-attachment */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "multipart/form-data": { + /** Format: binary */ + "attachments[]"?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Purchase order attachments added successfully", + * "data": { + * "id": 1, + * "attachments": [ + * { + * "id": 1, + * "purchase_order_id": 1, + * "file_path": "purchase_order_attachments/sample.pdf", + * "created_at": "2026-04-13T10:00:00.000000Z", + * "updated_at": "2026-04-13T10:00:00.000000Z" + * } + * ] + * } + * } + */ + "application/json": { + message?: string; + data?: { + id?: number; + attachments?: { + id?: number; + purchase_order_id?: number; + file_path?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }[]; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/purchase-orders/{id}/delete-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** DELETE /api/purchase-orders/{id}/delete-attachment */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "attachment_id": 1 + * } + */ + "application/json": { + attachment_id?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Purchase order attachment deleted successfully", + * "data": { + * "id": 1, + * "attachments": [] + * } + * } + */ + "application/json": { + message?: string; + data?: { + id?: number; + attachments?: unknown[]; + }; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/purchase-orders/{id}/get-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/purchase-orders/{id}/get-attachment */ + 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, + * "purchase_order_id": 1, + * "file_path": "purchase_order_attachments/sample.pdf", + * "url": "http://localhost/storage/purchase_order_attachments/sample.pdf", + * "created_at": "2026-04-13T10:00:00.000000Z", + * "updated_at": "2026-04-13T10:00:00.000000Z" + * } + * } + */ + "application/json": { + data?: { + id?: number; + purchase_order_id?: number; + file_path?: string; + url?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }; + }; + }; + }; + }; + }; + put?: never; + post?: never; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/purchase-orders/{id}/add-internal-note": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/purchase-orders/{id}/add-internal-note */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "note": "Follow up with vendor on ETA." + * } + */ + "application/json": { + note?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Purchase order internal note added successfully", + * "data": { + * "id": 1, + * "internal_notes": [ + * { + * "id": 1, + * "purchase_order_id": 1, + * "note": "Follow up with vendor on ETA.", + * "created_at": "2026-04-13T10:00:00.000000Z", + * "updated_at": "2026-04-13T10:00:00.000000Z" + * } + * ] + * } + * } + */ + "application/json": { + message?: string; + data?: { + id?: number; + internal_notes?: { + id?: number; + purchase_order_id?: number; + note?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }[]; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/purchase-orders/{id}/edit-internal-note": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/purchase-orders/{id}/edit-internal-note */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "internal_note_id": 1, + * "note": "Updated internal note text." + * } + */ + "application/json": { + internal_note_id?: number; + note?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Purchase order internal note updated successfully", + * "data": { + * "id": 1, + * "internal_notes": [ + * { + * "id": 1, + * "purchase_order_id": 1, + * "note": "Updated internal note text.", + * "created_at": "2026-04-13T10:00:00.000000Z", + * "updated_at": "2026-04-13T10:20:00.000000Z" + * } + * ] + * } + * } + */ + "application/json": { + message?: string; + data?: { + id?: number; + internal_notes?: { + id?: number; + purchase_order_id?: number; + note?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }[]; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/purchase-orders/{id}/delete-internal-note": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** DELETE /api/purchase-orders/{id}/delete-internal-note */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "internal_note_id": 1 + * } + */ + "application/json": { + internal_note_id?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Purchase order internal note deleted successfully", + * "data": { + * "id": 1, + * "internal_notes": [] + * } + * } + */ + "application/json": { + message?: string; + data?: { + id?: number; + internal_notes?: unknown[]; + }; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/services": { parameters: { query?: never; @@ -21848,17 +23908,51 @@ export interface paths { * "data": [ * { * "id": 1, - * "title": "Parts Bill", + * "title": "Workshop Bill", * "job_card_id": 1, * "vendor_id": 1, * "vendor_address_id": 1, + * "purchase_order_id": 1, * "bill_number": "BILL-001", * "bill_date": "2026-03-31", * "bill_due_date": "2026-04-14", * "payment_terms_id": 1, * "department_id": 1, - * "notes": "string", - * "status": "draft", + * "notes": "Vendor invoice for parts, service and expense lines", + * "status": "open", + * "services": [ + * { + * "id": 1, + * "bill_id": 1, + * "service_id": 1, + * "quantity": "2.00", + * "rate": "150.00", + * "chart_of_account": "COA-401", + * "description": "Labor service line" + * } + * ], + * "parts": [ + * { + * "id": 1, + * "bill_id": 1, + * "part_id": 1, + * "quantity": 3, + * "rate": "40.00", + * "chart_of_account": 1201, + * "description": "Brake pad set" + * } + * ], + * "expenses": [ + * { + * "id": 1, + * "bill_id": 1, + * "expense_id": 1, + * "quantity": "1.00", + * "rate": "75.00", + * "chart_of_account": "COA-402", + * "description": "Consumables expense line" + * } + * ], * "created_at": "2026-03-31T10:00:00.000000Z", * "updated_at": "2026-03-31T10:00:00.000000Z" * } @@ -21884,6 +23978,7 @@ export interface paths { job_card_id?: number; vendor_id?: number; vendor_address_id?: number; + purchase_order_id?: number; bill_number?: string; bill_date?: string; bill_due_date?: string; @@ -21891,6 +23986,33 @@ export interface paths { department_id?: number; notes?: string; status?: string; + services?: { + id?: number; + bill_id?: number; + service_id?: number; + quantity?: string; + rate?: string; + chart_of_account?: string; + description?: string; + }[]; + parts?: { + id?: number; + bill_id?: number; + part_id?: number; + quantity?: number; + rate?: string; + chart_of_account?: number; + description?: string; + }[]; + expenses?: { + id?: number; + bill_id?: number; + expense_id?: number; + quantity?: string; + rate?: string; + chart_of_account?: string; + description?: string; + }[]; /** Format: date-time */ created_at?: string; /** Format: date-time */ @@ -21914,7 +24036,7 @@ export interface paths { }; }; put?: never; - /** Store a newly created bill with labels and parts. */ + /** Store a newly created bill with labels, services and expenses. */ post: { parameters: { query?: never; @@ -21926,15 +24048,47 @@ export interface paths { content: { /** * @example { - * "title": "Parts Bill", + * "title": "Workshop Bill", * "job_card_id": 1, * "vendor_id": 1, * "vendor_address_id": 1, + * "purchase_order_id": 1, + * "bill_number": "BILL-001", * "bill_date": "2026-03-31", * "bill_due_date": "2026-04-14", * "payment_terms_id": 1, * "department_id": 1, - * "status": "draft" + * "notes": "Vendor invoice for parts, service and expense lines", + * "label_ids": [ + * 1 + * ], + * "part_items": [ + * { + * "part_id": 1, + * "quantity": 3, + * "rate": 40, + * "chart_of_account": 1201, + * "description": "Brake pad set" + * } + * ], + * "service_items": [ + * { + * "service_id": 1, + * "quantity": 2, + * "rate": 150, + * "chart_of_account": "COA-401", + * "description": "Labor service line" + * } + * ], + * "expense_items": [ + * { + * "expense_id": 1, + * "quantity": 1, + * "rate": 75, + * "chart_of_account": "COA-402", + * "description": "Consumables expense line" + * } + * ] * } */ "application/json": { @@ -21942,17 +24096,41 @@ export interface paths { job_card_id?: number; vendor_id?: number; vendor_address_id?: number; + purchase_order_id?: number; + bill_number?: string; bill_date?: string; bill_due_date?: string; payment_terms_id?: number; department_id?: number; - status?: string; + notes?: string; + label_ids?: number[]; + part_items?: { + part_id?: number; + quantity?: number; + rate?: number; + chart_of_account?: number; + description?: string; + }[]; + service_items?: { + service_id?: number; + quantity?: number; + rate?: number; + chart_of_account?: string; + description?: string; + }[]; + expense_items?: { + expense_id?: number; + quantity?: number; + rate?: number; + chart_of_account?: string; + description?: string; + }[]; }; }; }; responses: { - /** @description OK */ - 200: { + /** @description Created */ + 201: { headers: { [name: string]: unknown; }; @@ -21962,17 +24140,51 @@ export interface paths { * "message": "Operation completed successfully.", * "data": { * "id": 1, - * "title": "Parts Bill", + * "title": "Workshop Bill", * "job_card_id": 1, * "vendor_id": 1, * "vendor_address_id": 1, + * "purchase_order_id": 1, * "bill_number": "BILL-001", * "bill_date": "2026-03-31", * "bill_due_date": "2026-04-14", * "payment_terms_id": 1, * "department_id": 1, - * "notes": "string", - * "status": "draft", + * "notes": "Vendor invoice for parts, service and expense lines", + * "status": "open", + * "services": [ + * { + * "id": 1, + * "bill_id": 1, + * "service_id": 1, + * "quantity": "2.00", + * "rate": "150.00", + * "chart_of_account": "COA-401", + * "description": "Labor service line" + * } + * ], + * "parts": [ + * { + * "id": 1, + * "bill_id": 1, + * "part_id": 1, + * "quantity": 3, + * "rate": "40.00", + * "chart_of_account": 1201, + * "description": "Brake pad set" + * } + * ], + * "expenses": [ + * { + * "id": 1, + * "bill_id": 1, + * "expense_id": 1, + * "quantity": "1.00", + * "rate": "75.00", + * "chart_of_account": "COA-402", + * "description": "Consumables expense line" + * } + * ], * "created_at": "2026-03-31T10:00:00.000000Z", * "updated_at": "2026-03-31T10:00:00.000000Z" * } @@ -21986,6 +24198,7 @@ export interface paths { job_card_id?: number; vendor_id?: number; vendor_address_id?: number; + purchase_order_id?: number; bill_number?: string; bill_date?: string; bill_due_date?: string; @@ -21993,6 +24206,33 @@ export interface paths { department_id?: number; notes?: string; status?: string; + services?: { + id?: number; + bill_id?: number; + service_id?: number; + quantity?: string; + rate?: string; + chart_of_account?: string; + description?: string; + }[]; + parts?: { + id?: number; + bill_id?: number; + part_id?: number; + quantity?: number; + rate?: string; + chart_of_account?: number; + description?: string; + }[]; + expenses?: { + id?: number; + bill_id?: number; + expense_id?: number; + quantity?: string; + rate?: string; + chart_of_account?: string; + description?: string; + }[]; /** Format: date-time */ created_at?: string; /** Format: date-time */ @@ -22031,15 +24271,47 @@ export interface paths { content: { /** * @example { - * "title": "Parts Bill", + * "title": "Workshop Bill - Updated", * "job_card_id": 1, * "vendor_id": 1, * "vendor_address_id": 1, + * "purchase_order_id": 1, + * "bill_number": "BILL-001", * "bill_date": "2026-03-31", - * "bill_due_date": "2026-04-14", + * "bill_due_date": "2026-04-20", * "payment_terms_id": 1, * "department_id": 1, - * "status": "draft" + * "notes": "Updated vendor invoice lines", + * "label_ids": [ + * 1 + * ], + * "part_items": [ + * { + * "part_id": 1, + * "quantity": 1, + * "rate": 45, + * "chart_of_account": 1201, + * "description": "Updated part line" + * } + * ], + * "service_items": [ + * { + * "service_id": 1, + * "quantity": 1, + * "rate": 200, + * "chart_of_account": "COA-401", + * "description": "Updated labor service line" + * } + * ], + * "expense_items": [ + * { + * "expense_id": 1, + * "quantity": 2, + * "rate": 50, + * "chart_of_account": "COA-402", + * "description": "Updated consumables expense line" + * } + * ] * } */ "application/json": { @@ -22047,11 +24319,35 @@ export interface paths { job_card_id?: number; vendor_id?: number; vendor_address_id?: number; + purchase_order_id?: number; + bill_number?: string; bill_date?: string; bill_due_date?: string; payment_terms_id?: number; department_id?: number; - status?: string; + notes?: string; + label_ids?: number[]; + part_items?: { + part_id?: number; + quantity?: number; + rate?: number; + chart_of_account?: number; + description?: string; + }[]; + service_items?: { + service_id?: number; + quantity?: number; + rate?: number; + chart_of_account?: string; + description?: string; + }[]; + expense_items?: { + expense_id?: number; + quantity?: number; + rate?: number; + chart_of_account?: string; + description?: string; + }[]; }; }; }; @@ -22067,19 +24363,53 @@ export interface paths { * "message": "Operation completed successfully.", * "data": { * "id": 1, - * "title": "Parts Bill", + * "title": "Workshop Bill - Updated", * "job_card_id": 1, * "vendor_id": 1, * "vendor_address_id": 1, + * "purchase_order_id": 1, * "bill_number": "BILL-001", * "bill_date": "2026-03-31", - * "bill_due_date": "2026-04-14", + * "bill_due_date": "2026-04-20", * "payment_terms_id": 1, * "department_id": 1, - * "notes": "string", - * "status": "draft", + * "notes": "Updated vendor invoice lines", + * "status": "open", + * "services": [ + * { + * "id": 2, + * "bill_id": 1, + * "service_id": 1, + * "quantity": "1.00", + * "rate": "200.00", + * "chart_of_account": "COA-401", + * "description": "Updated labor service line" + * } + * ], + * "parts": [ + * { + * "id": 2, + * "bill_id": 1, + * "part_id": 1, + * "quantity": 1, + * "rate": "45.00", + * "chart_of_account": 1201, + * "description": "Updated part line" + * } + * ], + * "expenses": [ + * { + * "id": 2, + * "bill_id": 1, + * "expense_id": 1, + * "quantity": "2.00", + * "rate": "50.00", + * "chart_of_account": "COA-402", + * "description": "Updated consumables expense line" + * } + * ], * "created_at": "2026-03-31T10:00:00.000000Z", - * "updated_at": "2026-03-31T10:00:00.000000Z" + * "updated_at": "2026-03-31T11:00:00.000000Z" * } * } */ @@ -22091,6 +24421,7 @@ export interface paths { job_card_id?: number; vendor_id?: number; vendor_address_id?: number; + purchase_order_id?: number; bill_number?: string; bill_date?: string; bill_due_date?: string; @@ -22098,6 +24429,33 @@ export interface paths { department_id?: number; notes?: string; status?: string; + services?: { + id?: number; + bill_id?: number; + service_id?: number; + quantity?: string; + rate?: string; + chart_of_account?: string; + description?: string; + }[]; + parts?: { + id?: number; + bill_id?: number; + part_id?: number; + quantity?: number; + rate?: string; + chart_of_account?: number; + description?: string; + }[]; + expenses?: { + id?: number; + bill_id?: number; + expense_id?: number; + quantity?: string; + rate?: string; + chart_of_account?: string; + description?: string; + }[]; /** Format: date-time */ created_at?: string; /** Format: date-time */ @@ -22144,6 +24502,401 @@ export interface paths { patch?: never; trace?: never; }; + "/api/bills/{id}/add-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** POST /api/bills/{id}/add-attachment */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody?: { + content: { + "multipart/form-data": { + /** Format: binary */ + "attachments[]"?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Bill attachments added successfully", + * "data": { + * "id": 1, + * "attachments": [ + * { + * "id": 1, + * "bill_id": 1, + * "file_path": "http://localhost/storage/bill_attachments/sample.pdf", + * "created_at": "2026-04-13T10:00:00.000000Z", + * "updated_at": "2026-04-13T10:00:00.000000Z" + * } + * ] + * } + * } + */ + "application/json": { + message?: string; + data?: { + id?: number; + attachments?: { + id?: number; + bill_id?: number; + file_path?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }[]; + }; + }; + }; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/bills/{id}/delete-attachment": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post?: never; + /** DELETE /api/bills/{id}/delete-attachment */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "attachment_id": 1 + * } + */ + "application/json": { + attachment_id?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Bill attachment deleted successfully", + * "data": { + * "id": 1, + * "attachments": [] + * } + * } + */ + "application/json": { + message?: string; + data?: { + id?: number; + attachments?: unknown[]; + }; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/api/bills/{id}/internal-note": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/bills/{id}/internal-note */ + 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, + * "bill_id": 1, + * "note": "Requested updated invoice copy.", + * "created_at": "2026-04-13T10:00:00.000000Z", + * "updated_at": "2026-04-13T10:00:00.000000Z" + * } + * ], + * "meta": { + * "current_page": 1, + * "last_page": 1, + * "per_page": 15, + * "total": 1, + * "from": 1, + * "to": 1 + * } + * } + */ + "application/json": { + data?: { + id?: number; + bill_id?: number; + note?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }[]; + meta?: { + current_page?: number; + last_page?: number; + per_page?: number; + total?: number; + from?: number; + to?: number; + }; + }; + }; + }; + }; + }; + /** PUT /api/bills/{id}/internal-note */ + put: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "internal_note_id": 1, + * "note": "Updated internal note text." + * } + */ + "application/json": { + internal_note_id?: number; + note?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Bill internal note updated successfully", + * "data": { + * "id": 1, + * "internal_notes": [ + * { + * "id": 1, + * "bill_id": 1, + * "note": "Updated internal note text.", + * "created_at": "2026-04-13T10:00:00.000000Z", + * "updated_at": "2026-04-13T10:20:00.000000Z" + * } + * ] + * } + * } + */ + "application/json": { + message?: string; + data?: { + id?: number; + internal_notes?: { + id?: number; + bill_id?: number; + note?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }[]; + }; + }; + }; + }; + }; + }; + /** POST /api/bills/{id}/internal-note */ + post: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "note": "Requested updated invoice copy." + * } + */ + "application/json": { + note?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Bill internal note added successfully", + * "data": { + * "id": 1, + * "internal_notes": [ + * { + * "id": 1, + * "bill_id": 1, + * "note": "Requested updated invoice copy.", + * "created_at": "2026-04-13T10:00:00.000000Z", + * "updated_at": "2026-04-13T10:00:00.000000Z" + * } + * ] + * } + * } + */ + "application/json": { + message?: string; + data?: { + id?: number; + internal_notes?: { + id?: number; + bill_id?: number; + note?: string; + /** Format: date-time */ + created_at?: string; + /** Format: date-time */ + updated_at?: string; + }[]; + }; + }; + }; + }; + }; + }; + /** DELETE /api/bills/{id}/internal-note */ + delete: { + parameters: { + query?: never; + header?: never; + path: { + id: string; + }; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "internal_note_id": 1 + * } + */ + "application/json": { + internal_note_id?: number; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + /** + * @example { + * "message": "Bill internal note deleted successfully", + * "data": { + * "id": 1, + * "internal_notes": [] + * } + * } + */ + "application/json": { + message?: string; + data?: { + id?: number; + internal_notes?: unknown[]; + }; + }; + }; + }; + }; + }; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; "/api/expenses": { parameters: { query?: never; @@ -26918,6 +29671,7 @@ export interface paths { * "subject": "Invoice for Service", * "customer_id": 1, * "vehicle_id": 1, + * "estimate_id": 1, * "invoice_date": "2026-03-31", * "due_date": "2026-04-14", * "payment_terms_id": 1, @@ -26933,6 +29687,7 @@ export interface paths { subject?: string; customer_id?: number; vehicle_id?: number; + estimate_id?: number; invoice_date?: string; due_date?: string; payment_terms_id?: number; @@ -26960,6 +29715,7 @@ export interface paths { * "subject": "Invoice for Job Card 001", * "customer_id": 1, * "vehicle_id": 1, + * "estimate_id": 1, * "kms_in": 50000, * "has_insurance": false, * "insurer_id": 1, @@ -26993,6 +29749,7 @@ export interface paths { subject?: string; customer_id?: number; vehicle_id?: number; + estimate_id?: number; kms_in?: number; has_insurance?: boolean; insurer_id?: number; @@ -34180,6 +36937,73 @@ export interface paths { patch?: never; trace?: never; }; + "/api/document-print": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + /** GET /api/document-print */ + get: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + put?: never; + /** POST /api/document-print */ + post: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + /** + * @example { + * "type": "job_card", + * "id": 1, + * "mode": "print" + * } + */ + "application/json": { + type?: string; + id?: number; + mode?: string; + }; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content?: never; + }; + }; + }; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; } export type webhooks = Record; export interface components {