diff --git a/backend/src/utils/pdf.ts b/backend/src/utils/pdf.ts index f82e66f1..72104895 100644 --- a/backend/src/utils/pdf.ts +++ b/backend/src/utils/pdf.ts @@ -488,6 +488,8 @@ export const createInvoicePDF = async (server:FastifyInstance, returnMode, invoi const getTitleKey = (row) => `${row.pos} - ${row.text}` const getTransferSumText = (row) => { + if (deliveryNoteLikeDocumentTypes.includes(invoiceData.type)) return null + const transferSum = invoiceData.total.titleSumsTransfer[getTitleKey(row)] return transferSum ? `Übertrag: ${transferSum}` : null diff --git a/backend/tests/pdfTransferSums.test.ts b/backend/tests/pdfTransferSums.test.ts new file mode 100644 index 00000000..10720ee3 --- /dev/null +++ b/backend/tests/pdfTransferSums.test.ts @@ -0,0 +1,88 @@ +import assert from "node:assert/strict" +import { Readable } from "node:stream" +import test from "node:test" +import { PDFDocument, PDFPage } from "pdf-lib" +import { createInvoicePDF } from "../src/utils/pdf" +import { initS3, s3 } from "../src/utils/s3" + +const createDocumentData = (type: string) => { + const item = (pos: string) => ({ + mode: "normal", + pos, + text: `Artikel ${pos}`, + quantity: "1", + unit: "Stk", + taxPercent: 19, + price: "25,00 €", + rowAmount: "25,00 €", + }) + + return { + type, + adressLine: "Musterfirma, Musterweg 1, 12345 Musterstadt", + recipient: ["Musterkunde"], + info: [], + title: type === "packingSlips" ? "Packschein" : type === "deliveryNotes" ? "Lieferschein" : "Rechnung", + description: "Abschnitte mit Seitenumbruch", + startText: "Ihre Artikel:", + endText: "Vielen Dank.", + rows: [ + { mode: "title", pos: "1", text: "Abschnitt A" }, + item("1.1"), + { mode: "title", pos: "2", text: "Abschnitt B" }, + item("2.1"), + { mode: "pagebreak" }, + { mode: "title", pos: "3", text: "Abschnitt C" }, + item("3.1"), + ], + total: { + titleSums: { + "1 - Abschnitt A": "25,00 €", + "2 - Abschnitt B": "25,00 €", + "3 - Abschnitt C": "25,00 €", + }, + titleSumsTransfer: { + "2 - Abschnitt B": "25,00 €", + "3 - Abschnitt C": "50,00 €", + }, + }, + totalArray: [{ label: "Nettobetrag", content: "75,00 €" }], + } +} + +for (const type of ["packingSlips", "deliveryNotes", "invoices"]) { + test(`${type}: renders transfers only for priced documents, including before page breaks`, async t => { + const background = await PDFDocument.create() + background.addPage().drawText("Musterfirma", { x: 60, y: 800, size: 12 }) + const backgroundBytes = await background.save() + + await initS3() + t.mock.method(s3, "send", async () => ({ Body: Readable.from([backgroundBytes]) })) + const drawText = t.mock.method(PDFPage.prototype, "drawText") + + const result = await createInvoicePDF(null, "base64", createDocumentData(type), "test-letterhead.pdf") + assert.equal(result?.mimeType, "application/pdf") + const pdf = await PDFDocument.load(Buffer.from(result!.base64, "base64")) + assert.equal(pdf.getPageCount(), 2) + + const texts = drawText.mock.calls.map(call => call.arguments[0]) + for (const text of ["Abschnitt A", "Abschnitt B", "Abschnitt C", "Artikel 1.1", "Artikel 2.1", "Artikel 3.1"]) { + assert.ok(texts.includes(text), `Missing document content: ${text}`) + } + assert.equal(texts.filter(text => text === "1 Stk").length, 3) + assert.equal(texts.filter(text => text === "Check").length, type === "packingSlips" ? 2 : 0) + + const transfers = texts.filter(text => text.startsWith("Übertrag:")) + if (type === "invoices") { + assert.deepEqual(transfers, ["Übertrag: 25,00 €", "Übertrag: 50,00 €"]) + assert.ok(texts.includes("75,00 €")) + assert.equal(texts.filter(text => text === "Einheitspreis").length, 2) + } else { + assert.deepEqual(transfers, []) + assert.ok(texts.every(text => !text.includes("€")), "Delivery documents must not display monetary amounts") + for (const label of ["Steuer", "Einheitspreis", "Gesamt", "Nettobetrag"]) { + assert.ok(!texts.includes(label), `Unexpected price label: ${label}`) + } + } + }) +}