2026-04-23 14:38:41 +03:00

3.4 KiB

Invoice Pattern Skill

This skill defines the standard for implementing invoice-like forms (Invoice, Estimate, Job Card, Purchase Order, Bill, etc.) in the carage-erp dashboard. All such forms must follow this pattern for layout, discount/tax handling, and summary calculation. The current Invoice form is the canonical reference.


1. Layout

  • Two-column grid:
    • Main column (9/12):
      • Subject, invoice number/title
      • Status select, Discount Type select
      • Conditional Transaction Discount field
      • Line item selectors: Parts, Services, Expense Items (with optional line-level discount)
      • Notes, Terms & Conditions
      • Submit button
    • Sidebar column (3/12):
      • Invoice date, Due date
      • Customer, Vehicle selectors
      • Tax select (see below)
      • Department, Payment Terms, Invoice Sequence, Insurance fields
      • Summary card (see below)

2. Discount Implementation

  • Discount Type:
    • Field: discount (enum: 'no', 'line_item_level', 'transaction_level')
    • Select field in main column
  • Transaction-level Discount:
    • Field: discount_amount (number)
    • Only shown when discount === "transaction_level"
  • Line-level Discount:
    • Each line item (parts, services, expenses) has discount_amount field
    • Only shown when discount === "line_item_level"
  • Payload Mapping:
    • Only include discount_amount at transaction level if discount === "transaction_level"
    • Only include per-line discount_amount if discount === "line_item_level"

3. Tax Type Implementation

  • Tax Field:
    • Field: tax (relationFieldSchema: { value: string, label: string } | null)
    • Uses RhfAsyncSelectField in sidebar
    • mapOption: { value: String(item.id), label: ${item.title} (${item.rate}%) }
    • The selected tax's rate is parsed from the label string in the summary (regex: /\((\d+(?:\.\d+)?)%\)/)

4. Summary Implementation

  • Summary Card:
    • Always rendered in the sidebar below the Details card
    • Uses InvoiceFormSummary (form-aware adapter)
    • InvoiceFormSummary flattens all line items, reads discount/tax fields, and passes them to useDocumentTotals hook
    • useDocumentTotals (pure hook) computes subtotal, discounts, tax, and total
    • DocumentTotalsSummary (pure display component) renders the summary

Reference: Invoice Form

  • See apps/dashboard/modules/invoices/invoice-form.tsx for the canonical implementation.
  • Schema: apps/dashboard/modules/invoices/invoice.schema.ts
  • Summary logic: apps/dashboard/modules/invoices/invoice-form-summary.tsx, shared/hooks/use-document-totals.ts, shared/components/document-totals-summary.tsx

Required for All Invoice-like Forms

  • Follow the above layout and field conventions
  • Use the same discount/tax logic and summary calculation
  • Use the same field and payload mapping patterns
  • Use the same summary component structure

Example: Tax Field (in sidebar)

<RhfAsyncSelectField
  name="tax"
  label="Tax"
  placeholder="Select tax rate"
  queryKey={[TAX_ROUTES.INDEX]}
  listFn={() => api.taxes.list()}
  mapOption={(item: any) => ({
    value: String(item.id),
    label: item.title ? `${item.title} (${item.rate}%)` : `#${item.id}`,
  })}
  {...STORE_OBJECT}
/>

Example: Discount Type Select (in main column)

<RhfSelectField name="discount" label="Discount Type" options={DISCOUNT_OPTIONS} />

Example: Summary Card

<div className="mt-4">
  <InvoiceFormSummary />
</div>