Adds two PO-level charges shown below GST, per issue #133 ask 2. - Stored as ABSOLUTE rupee amounts on PurchaseOrder.tcsAmount / discountAmount (Decimal?, default 0; null/0 on historical & imported POs). Migration added. - Discount is applied post-GST. totalAmount folds the charges in (net payable = subtotal + GST + TCS − Discount), so payments / reports / advance all use the true amount due. lib/po-money.ts is the single source of truth. - Forms (create + edit) render a shared TcsDiscountFields with a % control bidirectionally linked to the rupee value (percentage is convenience only, taken against the GST-inclusive total; only the absolute amount is persisted). - createPo / updatePo store & compute; both manager-edit actions PRESERVE the PO's TCS/Discount when recomputing the total; import leaves them at 0. - PO detail shows TCS / Discount / Net payable below GST; PDF + XLSX export show the same breakdown and a corrected grand total. Tests: lib/po-money unit tests; po-tcs-discount integration test (create / edit / manager-line-edit preservation). Docs: CLAUDE.md GST section + wiki Purchase Orders (TCS/Discount + a full "what import sets vs. not" field-mapping table). Full unit (360) + integration (305) suites green; tsc clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
56 lines
1.7 KiB
TypeScript
56 lines
1.7 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
||
import {
|
||
computePoMoney,
|
||
poNetPayable,
|
||
amountToPercent,
|
||
percentToAmount,
|
||
} from "@/lib/po-money";
|
||
|
||
const items = [
|
||
{ quantity: 10, unitPrice: 100, gstRate: 0.18 }, // taxable 1000, gst 180
|
||
{ quantity: 2, unitPrice: 50, gstRate: 0.05 }, // taxable 100, gst 5
|
||
];
|
||
|
||
describe("computePoMoney", () => {
|
||
it("breaks down taxable, GST and incl-GST", () => {
|
||
const m = computePoMoney(items);
|
||
expect(m.taxable).toBe(1100);
|
||
expect(m.gst).toBeCloseTo(185, 5);
|
||
expect(m.inclGst).toBeCloseTo(1285, 5);
|
||
expect(m.netPayable).toBeCloseTo(1285, 5); // no charges ⇒ equals inclGst
|
||
});
|
||
|
||
it("adds TCS and subtracts Discount post-GST", () => {
|
||
const m = computePoMoney(items, 50, 85);
|
||
expect(m.tcs).toBe(50);
|
||
expect(m.discount).toBe(85);
|
||
expect(m.netPayable).toBeCloseTo(1285 + 50 - 85, 5);
|
||
});
|
||
|
||
it("defaults a missing gstRate to 18%", () => {
|
||
expect(computePoMoney([{ quantity: 1, unitPrice: 100 }]).gst).toBeCloseTo(18, 5);
|
||
});
|
||
|
||
it("treats nullish charges as zero", () => {
|
||
const m = computePoMoney(items, undefined, undefined);
|
||
expect(m.netPayable).toBeCloseTo(1285, 5);
|
||
});
|
||
});
|
||
|
||
describe("poNetPayable", () => {
|
||
it("is incl-GST + TCS − Discount", () => {
|
||
expect(poNetPayable(items, 100, 200)).toBeCloseTo(1285 + 100 - 200, 5);
|
||
});
|
||
});
|
||
|
||
describe("percent ↔ amount conversion", () => {
|
||
it("round-trips against a base", () => {
|
||
expect(amountToPercent(128.5, 1285)).toBeCloseTo(10, 5);
|
||
expect(percentToAmount(10, 1285)).toBeCloseTo(128.5, 5);
|
||
});
|
||
|
||
it("is zero-safe when the base is zero", () => {
|
||
expect(amountToPercent(50, 0)).toBe(0);
|
||
expect(percentToAmount(10, 0)).toBe(0);
|
||
});
|
||
});
|