115 lines
4.5 KiB
TypeScript
115 lines
4.5 KiB
TypeScript
/**
|
||
* 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<unknown>).mockResolvedValue(makeSession(userId, role));
|
||
}
|
||
|
||
// One line item: 10 × ₹100 @ 18% GST ⇒ taxable 1000, GST 180, incl-GST 1180.
|
||
function form(title: string, intent: string, tcs: number, discount: number) {
|
||
const f = makePoForm({
|
||
title, vesselId, accountId, intent: "draft",
|
||
lineItems: [{ description: "Item", quantity: 10, unit: "pc", unitPrice: 100, gstRate: 0.18 }],
|
||
});
|
||
f.set("intent", intent); // create: draft/submit · edit: save
|
||
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
|
||
});
|
||
});
|