/** * Integration test for PO-level TCS & Discount (issue #133). * Verifies totalAmount folds in the charges (subtotal + GST + TCS − Discount), * the absolute amounts are persisted, edits update them, and a manager line edit * preserves them. */ import { vi, describe, it, expect, beforeAll, afterEach } from "vitest"; vi.mock("@/auth", () => ({ auth: vi.fn() })); vi.mock("next/cache", () => ({ revalidatePath: vi.fn() })); vi.mock("@/lib/notifier", () => ({ notify: vi.fn() })); import { auth } from "@/auth"; import { db } from "@/lib/db"; import type { Role } from "@prisma/client"; import { createPo } from "@/app/(portal)/po/new/actions"; import { updatePo } from "@/app/(portal)/po/[id]/edit/actions"; import { managerEditLineItems } from "@/app/(portal)/approvals/[id]/manager-line-edit-actions"; import { makeSession, getSeedUser, getSeedVessel, getSeedAccount, makePoForm, deletePosByTitle } from "./helpers"; const PREFIX = "INTTEST_TCSDISC_"; let techId: string; let managerId: string; let vesselId: string; let accountId: string; beforeAll(async () => { const [tech, manager, vessel, account] = await Promise.all([ getSeedUser("tech@pelagia.local"), getSeedUser("manager@pelagia.local"), getSeedVessel("MV Pelagia Star"), getSeedAccount("700201"), ]); techId = tech.id; managerId = manager.id; vesselId = vessel.id; accountId = account.id; }); afterEach(async () => { await deletePosByTitle(PREFIX); vi.clearAllMocks(); }); function as(userId: string, role: Role) { vi.mocked(auth as unknown as () => Promise).mockResolvedValue(makeSession(userId, role)); } // One line item: 10 × ₹100 @ 18% GST ⇒ taxable 1000, GST 180, incl-GST 1180. function form(title: string, intent: "draft" | "submit", tcs: number, discount: number) { const f = makePoForm({ title, vesselId, accountId, intent, lineItems: [{ description: "Item", quantity: 10, unit: "pc", unitPrice: 100, gstRate: 0.18 }], }); f.set("tcsAmount", String(tcs)); f.set("discountAmount", String(discount)); return f; } describe("PO TCS & Discount", () => { it("folds TCS and Discount into totalAmount and stores the absolute amounts", async () => { as(techId, "TECHNICAL"); const result = await createPo(form(`${PREFIX}Create`, "draft", 118, 100)); expect(result).not.toHaveProperty("error"); const po = await db.purchaseOrder.findUniqueOrThrow({ where: { id: (result as { id: string }).id } }); expect(Number(po.tcsAmount)).toBeCloseTo(118, 2); expect(Number(po.discountAmount)).toBeCloseTo(100, 2); expect(Number(po.totalAmount)).toBeCloseTo(1180 + 118 - 100, 2); // 1198 }); it("defaults to zero charges ⇒ totalAmount is just incl-GST", async () => { as(techId, "TECHNICAL"); const result = await createPo(form(`${PREFIX}Zero`, "draft", 0, 0)); const po = await db.purchaseOrder.findUniqueOrThrow({ where: { id: (result as { id: string }).id } }); expect(Number(po.totalAmount)).toBeCloseTo(1180, 2); expect(Number(po.tcsAmount)).toBe(0); expect(Number(po.discountAmount)).toBe(0); }); it("edit updates the charges and recomputes the total", async () => { as(techId, "TECHNICAL"); const created = await createPo(form(`${PREFIX}Edit`, "draft", 0, 0)); const poId = (created as { id: string }).id; as(techId, "TECHNICAL"); const edited = await updatePo(poId, form(`${PREFIX}Edit`, "save", 50, 30)); expect(edited).not.toHaveProperty("error"); const po = await db.purchaseOrder.findUniqueOrThrow({ where: { id: poId } }); expect(Number(po.tcsAmount)).toBeCloseTo(50, 2); expect(Number(po.discountAmount)).toBeCloseTo(30, 2); expect(Number(po.totalAmount)).toBeCloseTo(1180 + 50 - 30, 2); // 1200 }); it("a manager line edit preserves the PO's TCS & Discount", async () => { as(techId, "TECHNICAL"); const created = await createPo(form(`${PREFIX}MgrEdit`, "submit", 118, 100)); // ⇒ MGR_REVIEW const poId = (created as { id: string }).id; as(managerId, "MANAGER"); const res = await managerEditLineItems({ poId, // Double the quantity: incl-GST becomes 20 × 100 × 1.18 = 2360. lineItems: [{ name: "Item", quantity: 20, unit: "pc", unitPrice: 100 }], }); expect(res).not.toHaveProperty("error"); const po = await db.purchaseOrder.findUniqueOrThrow({ where: { id: poId } }); expect(Number(po.tcsAmount)).toBeCloseTo(118, 2); expect(Number(po.discountAmount)).toBeCloseTo(100, 2); expect(Number(po.totalAmount)).toBeCloseTo(2360 + 118 - 100, 2); // 2378 }); });