import { describe, expect, it } from "vitest"; import { computeLineAmount, computeTotals } from "./invoice"; import { EMPTY_ITEM, type InvoiceItem } from "./types"; // These cases mirror the unit tests in src-tauri/src/gst.rs, so the on-screen preview // and the stored totals stay identical. const fixed = (amount: number): InvoiceItem => ({ ...EMPTY_ITEM, mode: "fixed", amount }); const rate = (r: number, quantity: number): InvoiceItem => ({ ...EMPTY_ITEM, mode: "rate", rate: r, quantity, unit: "hour", }); describe("computeTotals", () => { it("splits 18% on 7310.00 into 657.90 CGST and SGST", () => { const t = computeTotals([fixed(7310)], 0, "cgst_sgst", 18); expect(t.subtotal).toBe(7310); expect(t.cgst).toBe(657.9); expect(t.sgst).toBe(657.9); expect(t.igst).toBe(0); expect(t.total).toBe(8625.8); }); it("rounds odd paise half up for IGST and per head for CGST/SGST", () => { const igst = computeTotals([fixed(33.33)], 0, "igst", 5); expect(igst.igst).toBe(1.67); expect(igst.total).toBe(35); const split = computeTotals([fixed(33.33)], 0, "cgst_sgst", 5); expect([split.cgst, split.sgst]).toEqual([0.83, 0.83]); expect(split.total).toBe(34.99); }); it("handles fractional quantities", () => { const t = computeTotals([rate(1200, 1.5)], 0, "none", 0); expect(t.subtotal).toBe(1800); expect(t.total).toBe(1800); expect(computeLineAmount(rate(99.99, 2.5))).toBe(249.98); }); it("clamps the discount to the subtotal", () => { const items = [fixed(100), fixed(50.5)]; const over = computeTotals(items, 999.99, "igst", 18); expect(over.discount).toBe(150.5); expect(over.taxable).toBe(0); expect(over.total).toBe(0); expect(computeTotals(items, -5, "none", 0).discount).toBe(0); expect(computeTotals(items, 50.5, "none", 0).taxable).toBe(100); }); it("ignores the rate when no tax applies", () => { const t = computeTotals([fixed(1000)], 0, "none", 18); expect(t.taxTotal).toBe(0); expect(t.total).toBe(1000); }); });