Add toasts, Settings tabs, Clients page and item presets (Phase E1)

- Global Carbon toast stack replaces scattered inline notices; errors persist until closed.
- Settings is split into eight tabs with one Save and a remembered last tab; Numbering embeds the series settings.
- New Clients page with a DataTable, shared add/edit modal, structured addresses, GST category (informational), per-client notes and payment terms. The composed address string is kept for back-compat. Deleting a client warns about invoice count; issued invoices keep their own snapshot.
- Migration M7 adds the client columns and item_presets; existing clients with a GSTIN are backfilled to registered_regular.
- New invoice form: client ComboBox with type-ahead and inline create, client defaults applied on selection, and an Add from preset control.
- Rust tests for M7 (fresh and v6 upgrade), client validation and CRUD, presets CRUD; vitest for the toast store and new helpers.

PDF output and fingerprint goldens are unchanged. The new UI has not been run in a webview yet.
This commit is contained in:
2026-10-04 17:29:46 +05:30
parent 9e17ba3479
commit 3510cc4260
30 changed files with 2119 additions and 204 deletions
+64
View File
@@ -58,16 +58,80 @@ export interface InvoiceSeries {
nextInvoiceNumber: string;
}
export type GstCategory = "registered_regular" | "unregistered" | "composition" | "sez" | "overseas";
/** Informational only; the tax heads still derive from supplier state and place of supply. */
export const GST_CATEGORY_LABELS: Record<GstCategory, string> = {
registered_regular: "Registered (regular)",
unregistered: "Unregistered",
composition: "Composition",
sez: "SEZ",
overseas: "Overseas",
};
export interface Client {
id: number | null;
name: string;
/** The composed multi-line address; what invoices store and print. */
address: string;
gstin: string;
/** Doubles as the address state and the default place of supply. */
stateCode: string;
poNumber: string;
createdAt: string;
addressLine1: string;
addressLine2: string;
city: string;
pincode: string;
gstCategory: GstCategory;
/** Notes / payment terms text copied onto a new invoice when the client is picked. */
defaultNotes: string;
/** Null means the settings default. */
paymentTermsDays: number | null;
/** Read-only: how many invoices reference this client. */
invoiceCount?: number;
}
export const blankClient = (): Client => ({
id: null,
name: "",
address: "",
gstin: "",
stateCode: "",
poNumber: "",
createdAt: "",
addressLine1: "",
addressLine2: "",
city: "",
pincode: "",
gstCategory: "unregistered",
defaultNotes: "",
paymentTermsDays: null,
});
/** A saved line item. `ratePaise` is the amount for "fixed" and the per-unit rate for "rate". */
export interface ItemPreset {
id: number | null;
description: string;
hsnSac: string;
mode: LineMode;
unit: LineUnit;
ratePaise: number;
sortOrder: number;
createdAt: string;
}
export const blankPreset = (): ItemPreset => ({
id: null,
description: "",
hsnSac: "",
mode: "fixed",
unit: "unit",
ratePaise: 0,
sortOrder: 0,
createdAt: "",
});
export interface InvoiceItem {
id?: number | null;
description: string;