96 lines
3.4 KiB
Markdown
96 lines
3.4 KiB
Markdown
# 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)
|
|
```tsx
|
|
<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)
|
|
```tsx
|
|
<RhfSelectField name="discount" label="Discount Type" options={DISCOUNT_OPTIONS} />
|
|
```
|
|
|
|
## Example: Summary Card
|
|
```tsx
|
|
<div className="mt-4">
|
|
<InvoiceFormSummary />
|
|
</div>
|
|
```
|