import { describe, it, expect, afterEach } from "vitest"; import prisma from "../config/database"; import { createOffer, getOfferTotals } from "../services/offers.service"; import { createInvoice, getInvoiceListTotals, } from "../services/invoices.service"; /** * Per-item Sleva (% discount) must reduce the line NET — and on invoices the * VAT is computed on the discounted net. Hits the real `app_test` DB through the * service layer (suite convention), mirroring offer-invoice-totals.test.ts. */ const MARKER = "SLEVA-TOTALS-2096"; const YEAR = 2096; const MONTH = 8; // invoice issue_date window [2096-08-01, 2096-09-01) const dateStr = `${YEAR}-0${MONTH}-15`; const offerIds: number[] = []; const invoiceIds: number[] = []; afterEach(async () => { for (const id of offerIds) await prisma.quotations.deleteMany({ where: { id } }); for (const id of invoiceIds) await prisma.invoices.deleteMany({ where: { id } }); offerIds.length = 0; invoiceIds.length = 0; await prisma.number_sequences.deleteMany({ where: { type: { in: ["offer", "invoice"] } }, }); }); const amountFor = ( totals: { currency: string; amount: number }[], currency: string, ): number | undefined => totals.find((t) => t.currency === currency)?.amount; describe("offer per-item discount", () => { it("reduces the offer NET total by the per-item percentage", async () => { // 2 × 1000 with 10% off => 1800 net. const res = await createOffer({ status: "draft", currency: "CZK", project_code: MARKER, items: [ { description: "X", quantity: 2, unit_price: 1000, discount: 10 }, ], }); if ("error" in res) throw new Error(JSON.stringify(res)); offerIds.push(res.id); const { totals } = await getOfferTotals({ search: MARKER }); expect(amountFor(totals, "CZK")).toBe(1800); }); }); describe("invoice per-item discount", () => { it("computes VAT on the discounted net (gross = discounted net + VAT)", async () => { // 2 × 1000 with 10% off => net 1800, VAT@21% = 378, gross 2178. const inv = await createInvoice({ status: "draft", currency: "CZK", vat_rate: 21, apply_vat: true, issue_date: dateStr, items: [ { description: "X", quantity: 2, unit_price: 1000, discount: 10 }, ], }); invoiceIds.push(inv.id); const { totals } = await getInvoiceListTotals({ month: MONTH, year: YEAR }); expect(amountFor(totals, "CZK")).toBe(2178); }); it("treats a 100% discount line as zero", async () => { const inv = await createInvoice({ status: "draft", currency: "CZK", vat_rate: 21, apply_vat: true, issue_date: dateStr, items: [ { description: "Free", quantity: 5, unit_price: 200, discount: 100 }, ], }); invoiceIds.push(inv.id); const { totals } = await getInvoiceListTotals({ month: MONTH, year: YEAR }); expect(amountFor(totals, "CZK")).toBeUndefined(); // zero totals are dropped }); });