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, }; }