issueAndArchive keeps an issued invoice even when rendering or archiving fails (Issued - not archived, with a Render & archive action). The export menu offers a searchable PDF or a 300 DPI image-only PDF whose metadata is the invoice number alone; flatten failures offer Retry or searchable export and never silently lower the resolution. History re-exports the original as issued by default and warns when a re-render's layout fingerprint differs. The render client gains a FIFO assemble lane that previews cannot supersede. A Diagnostics panel runs fixtures in the real worker and compares against goldens generated from Node.
136 lines
4.4 KiB
TypeScript
136 lines
4.4 KiB
TypeScript
import { computeTotals } from "../../lib/invoice";
|
|
import { amountInWords } from "../../lib/numberToWords";
|
|
import type { BankAccount, Invoice, InvoiceItem, Settings, TaxType } from "../../lib/types";
|
|
import { buildRenderModel, type RenderModel } from "../model/build";
|
|
import { DEFAULT_PREFS, type RenderPrefsV1 } from "../model/prefs";
|
|
|
|
/**
|
|
* Pure data for the Diagnostics self-test and the goldens test (no Node or DOM imports, so the
|
|
* webview and Node build the very same models). Change a fixture and regenerate the goldens.
|
|
*/
|
|
|
|
export interface SelfTestFixture {
|
|
id: string;
|
|
label: string;
|
|
model: RenderModel;
|
|
prefs: RenderPrefsV1;
|
|
invoiceNumber: string;
|
|
/** The grand total as the template prints it, e.g. "₹8,260.00". */
|
|
totalText: string;
|
|
}
|
|
|
|
const VENDOR: Settings = {
|
|
vendorName: "Selftest Studio",
|
|
vendorAddress: "1 Test Lane, Test Town, Bangalore - 560005",
|
|
vendorEmail: "selftest@example.com",
|
|
vendorPhone: "9000000000",
|
|
vendorPan: "AAAAA0000A",
|
|
vendorGstin: "",
|
|
vendorStateCode: "29",
|
|
logoPath: null,
|
|
signaturePath: null,
|
|
defaultBankId: 1,
|
|
defaultTaxRate: 18,
|
|
defaultTaxType: "cgst_sgst",
|
|
paymentTermsDays: 30,
|
|
currency: "INR",
|
|
onboarded: true,
|
|
theme: "g10",
|
|
gstRegistration: "unregistered",
|
|
defaultHsnSac: "",
|
|
signatoryName: "",
|
|
signatoryDesignation: "",
|
|
renderPrefs: "",
|
|
};
|
|
|
|
const BANK: BankAccount = {
|
|
id: 1,
|
|
label: "Primary",
|
|
bankName: "State Bank of India",
|
|
accountName: "Selftest Studio",
|
|
accountNo: "00000000000",
|
|
branch: "000000",
|
|
ifsc: "SBIN0000000",
|
|
isDefault: true,
|
|
};
|
|
|
|
const items = (n: number, hsn: boolean): InvoiceItem[] =>
|
|
Array.from({ length: n }, (_, i) => ({
|
|
id: i + 1,
|
|
description: `Voice-over session ${i + 1} - narration for module ${i + 1}`,
|
|
mode: i % 2 === 0 ? "rate" : "fixed",
|
|
rate: 1200,
|
|
unit: "hour",
|
|
quantity: 1 + (i % 3),
|
|
amount: 2500 + i * 10,
|
|
sortOrder: i,
|
|
...(hsn ? { hsnSac: "998397" } : {}),
|
|
}));
|
|
|
|
function build(o: { id: string; label: string; number: string; rows: number; registered: boolean; prefs: Partial<RenderPrefsV1> }): SelfTestFixture {
|
|
const taxType: TaxType = o.registered ? "cgst_sgst" : "none";
|
|
const taxRate = o.registered ? 18 : 0;
|
|
const rows = items(o.rows, o.registered);
|
|
const totals = computeTotals(rows, 0, taxType, taxRate);
|
|
const vendor: Settings = o.registered
|
|
? { ...VENDOR, vendorGstin: "29AAAAA0000A1Z5", gstRegistration: "regular", signatoryName: "Selftest Studio", signatoryDesignation: "Proprietor" }
|
|
: VENDOR;
|
|
const invoice: Invoice = {
|
|
id: 1,
|
|
number: o.number,
|
|
seriesId: 1,
|
|
invoiceDate: "2026-09-15",
|
|
dueDate: "2026-10-15",
|
|
clientId: null,
|
|
clientName: "Acme Learning Pvt Ltd",
|
|
clientAddress: "42 MG Road\nBangalore 560001",
|
|
clientGstin: o.registered ? "29ABCDE1234F1Z5" : "",
|
|
poNumber: "",
|
|
placeOfSupplyStateCode: o.registered ? "29" : "",
|
|
subtotal: totals.subtotal,
|
|
discount: totals.discount,
|
|
taxType,
|
|
taxRate,
|
|
cgstAmount: totals.cgst,
|
|
sgstAmount: totals.sgst,
|
|
igstAmount: totals.igst,
|
|
total: totals.total,
|
|
amountInWords: amountInWords(totals.total),
|
|
bankAccountId: 1,
|
|
bankSnapshot: "",
|
|
signaturePath: null,
|
|
notes: "",
|
|
status: "issued",
|
|
createdAt: "2026-09-15T00:00:00Z",
|
|
updatedAt: "2026-09-15T00:00:00Z",
|
|
docType: o.registered ? "tax_invoice" : "invoice",
|
|
reverseCharge: false,
|
|
vendorSnapshot: "",
|
|
snapshotOrigin: "",
|
|
cancelledAt: null,
|
|
cancelReason: "",
|
|
archivedPdfSha256: null,
|
|
renderPrefs: "",
|
|
items: rows,
|
|
};
|
|
const model = buildRenderModel({ invoice, vendor, bank: BANK, logo: null, signatureDataUri: null, appVersion: "0.0.0-selftest" });
|
|
return {
|
|
id: o.id,
|
|
label: o.label,
|
|
model,
|
|
prefs: { ...DEFAULT_PREFS, ...o.prefs },
|
|
invoiceNumber: o.number,
|
|
totalText: model.totals.totalText,
|
|
};
|
|
}
|
|
|
|
export const SELF_TEST_FIXTURE_IDS = ["classic-a4-tax-3", "classic-letter-unreg-60"] as const;
|
|
|
|
/** Classic A4 tax invoice with 3 rows, and Classic Letter plain invoice with 60 rows (a multi-page table). */
|
|
export function selfTestFixtures(): SelfTestFixture[] {
|
|
return [
|
|
build({ id: "classic-a4-tax-3", label: "Classic, A4, tax invoice, 3 rows", number: "ST/2026-001", rows: 3, registered: true, prefs: { pageSize: "A4" } }),
|
|
build({ id: "classic-letter-unreg-60", label: "Classic, Letter, unregistered, 60 rows", number: "ST/2026-002", rows: 60, registered: false, prefs: { pageSize: "LETTER" } }),
|
|
];
|
|
}
|