3.4 KiB
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)
- Main column (9/12):
2. Discount Implementation
- Discount Type:
- Field:
discount(enum: 'no', 'line_item_level', 'transaction_level') - Select field in main column
- Field:
- Transaction-level Discount:
- Field:
discount_amount(number) - Only shown when
discount === "transaction_level"
- Field:
- Line-level Discount:
- Each line item (parts, services, expenses) has
discount_amountfield - Only shown when
discount === "line_item_level"
- Each line item (parts, services, expenses) has
- Payload Mapping:
- Only include
discount_amountat transaction level ifdiscount === "transaction_level" - Only include per-line
discount_amountifdiscount === "line_item_level"
- Only include
3. Tax Type Implementation
- Tax Field:
- Field:
tax(relationFieldSchema:{ value: string, label: string } | null) - Uses
RhfAsyncSelectFieldin 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+)?)%\)/)
- Field:
4. Summary Implementation
- Summary Card:
- Always rendered in the sidebar below the Details card
- Uses
InvoiceFormSummary(form-aware adapter) InvoiceFormSummaryflattens all line items, reads discount/tax fields, and passes them touseDocumentTotalshookuseDocumentTotals(pure hook) computes subtotal, discounts, tax, and totalDocumentTotalsSummary(pure display component) renders the summary
Reference: Invoice Form
- See
apps/dashboard/modules/invoices/invoice-form.tsxfor 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>