Logos (PNG, JPEG, WebP) are decoded, trimmed to their ink box, downscaled to 1200 px and re-encoded as a content-addressed print PNG plus a white knockout variant when the logo has alpha; aspect, ink density, mean colour and kind (wordmark, mark, tall) are stored with the settings and frozen in the issue snapshot. Legacy logos are derived at startup. The Test Vendor logo PDF shrinks from 334 KB to 92 KB. Settings gains a Branding section with white and dark previews and a business-name toggle (migration M5).
102 lines
3.6 KiB
TypeScript
102 lines
3.6 KiB
TypeScript
import type { BankAccount, Invoice, InvoiceItem, Settings, TaxType } from "./types";
|
|
import { computeLineAmount, computeTotals } from "./invoice";
|
|
import { amountInWords } from "./numberToWords";
|
|
|
|
/** The form fields the preview reads. */
|
|
export interface PreviewForm {
|
|
invoiceDate: string;
|
|
dueDate: string;
|
|
clientId: number | null;
|
|
clientName: string;
|
|
clientAddress: string;
|
|
clientGstin: string;
|
|
poNumber: string;
|
|
placeOfSupply: string;
|
|
discount: number;
|
|
taxRate: number;
|
|
reverseCharge: boolean;
|
|
signaturePath: string | null;
|
|
notes: string;
|
|
items: InvoiceItem[];
|
|
}
|
|
|
|
/**
|
|
* The tax treatment Rust derives from the supplier state and the place of supply
|
|
* (src-tauri/src/gst.rs); the form shows and sends the same value.
|
|
*/
|
|
export function deriveTaxType(settings: Settings, placeOfSupply: string): TaxType {
|
|
if (settings.gstRegistration === "unregistered") return "none";
|
|
return !placeOfSupply || placeOfSupply === settings.vendorStateCode ? "cgst_sgst" : "igst";
|
|
}
|
|
|
|
/** Mirrors vendor_snapshot() in src-tauri/src/commands/invoice.rs. */
|
|
export function vendorSnapshotJson(settings: Settings): string {
|
|
return JSON.stringify({
|
|
vendorName: settings.vendorName,
|
|
vendorAddress: settings.vendorAddress,
|
|
vendorEmail: settings.vendorEmail,
|
|
vendorPhone: settings.vendorPhone,
|
|
vendorPan: settings.vendorPan,
|
|
vendorGstin: settings.gstRegistration === "unregistered" ? "" : settings.vendorGstin.trim(),
|
|
vendorStateCode: settings.vendorStateCode,
|
|
logoPath: settings.logoPath,
|
|
logoKnockoutPath: settings.logoKnockoutPath,
|
|
logoMeta: settings.logoMeta,
|
|
logoIncludesName: settings.logoIncludesName,
|
|
signaturePath: settings.signaturePath,
|
|
gstRegistration: settings.gstRegistration,
|
|
signatoryName: settings.signatoryName,
|
|
signatoryDesignation: settings.signatoryDesignation,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* A draft-status Invoice built from the live form, so the real renderer can draw it before anything
|
|
* is issued. Totals use the same paise rounding Rust stores. `number` is the next number (a peek),
|
|
* not yet consumed.
|
|
*/
|
|
export function formToPreviewInvoice(form: PreviewForm, number: string, settings: Settings, bank: BankAccount | null): Invoice {
|
|
const taxType = deriveTaxType(settings, form.placeOfSupply);
|
|
const registered = settings.gstRegistration !== "unregistered";
|
|
const totals = computeTotals(form.items, form.discount, taxType, form.taxRate);
|
|
const items = form.items.map((item, i) => ({ ...item, amount: computeLineAmount(item), sortOrder: i }));
|
|
return {
|
|
id: 0,
|
|
number,
|
|
seriesId: null,
|
|
invoiceDate: form.invoiceDate,
|
|
dueDate: form.dueDate,
|
|
clientId: form.clientId,
|
|
clientName: form.clientName,
|
|
clientAddress: form.clientAddress,
|
|
clientGstin: form.clientGstin,
|
|
poNumber: form.poNumber,
|
|
placeOfSupplyStateCode: form.placeOfSupply,
|
|
subtotal: totals.subtotal,
|
|
discount: totals.discount,
|
|
taxType,
|
|
taxRate: form.taxRate,
|
|
cgstAmount: totals.cgst,
|
|
sgstAmount: totals.sgst,
|
|
igstAmount: totals.igst,
|
|
total: totals.total,
|
|
amountInWords: amountInWords(totals.total),
|
|
bankAccountId: bank?.id ?? null,
|
|
bankSnapshot: bank ? JSON.stringify(bank) : "",
|
|
signaturePath: form.signaturePath,
|
|
notes: form.notes,
|
|
status: "draft",
|
|
createdAt: "",
|
|
updatedAt: "",
|
|
docType: registered ? "tax_invoice" : "invoice",
|
|
reverseCharge: registered && form.reverseCharge,
|
|
vendorSnapshot: vendorSnapshotJson(settings),
|
|
snapshotOrigin: "issued",
|
|
cancelledAt: null,
|
|
cancelReason: "",
|
|
archivedPdfSha256: null,
|
|
renderPrefs: "",
|
|
items,
|
|
};
|
|
}
|