From f8857f71e16ef87671231430a286b1557bd55a60 Mon Sep 17 00:00:00 2001 From: Bastian Wagner Date: Mon, 3 Aug 2026 21:20:57 +0200 Subject: [PATCH] feat: add buildPdf for cashbox export Co-Authored-By: Claude Sonnet 5 --- .../cashbox-export.utils.spec.ts | 25 +++++++++++++- .../cashbox-export/cashbox-export.utils.ts | 34 +++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.spec.ts b/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.spec.ts index bd7b207..a04288a 100644 --- a/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.spec.ts +++ b/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.spec.ts @@ -1,4 +1,4 @@ -import { buildCsv, buildRows } from './cashbox-export.utils'; +import { buildCsv, buildPdf, buildRows } from './cashbox-export.utils'; describe('buildRows', () => { const team = (overrides: Partial<{ transactions: any[]; players: any[] }> = {}) => ({ @@ -167,3 +167,26 @@ describe('buildCsv', () => { ); }); }); + +describe('buildPdf', () => { + it('produces a non-empty valid PDF buffer', async () => { + const buffer = await buildPdf( + { name: 'Team A' } as any, + [ + { date: '2026-08-05T00:00:00.000Z', type: 'payment', who: 'Alex Muster', note: 'Bar bezahlt', amount: 10, runningTotal: 10 }, + ], + '2026-08-01', + '2026-08-31', + ); + + expect(Buffer.isBuffer(buffer)).toBe(true); + expect(buffer.length).toBeGreaterThan(100); + expect(buffer.subarray(0, 5).toString('utf-8')).toBe('%PDF-'); + }); + + it('still produces a valid PDF when there are no rows', async () => { + const buffer = await buildPdf({ name: 'Team A' } as any, [], '2026-08-01', '2026-08-31'); + + expect(buffer.subarray(0, 5).toString('utf-8')).toBe('%PDF-'); + }); +}); diff --git a/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.ts b/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.ts index 96ad9df..874b299 100644 --- a/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.ts +++ b/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.ts @@ -1,4 +1,6 @@ import { Team } from 'src/teams/entities/team.entity'; +// eslint-disable-next-line @typescript-eslint/no-var-requires +const PDFDocument = require('pdfkit'); export interface CashboxExportRow { date: string; @@ -100,3 +102,35 @@ export function buildCsv(rows: CashboxExportRow[]): string { } return lines.join('\r\n'); } + +export function buildPdf( + team: Pick, + rows: CashboxExportRow[], + from: string, + to: string, +): Promise { + return new Promise((resolve, reject) => { + const doc = new PDFDocument({ margin: 40 }); + const chunks: Buffer[] = []; + doc.on('data', (chunk) => chunks.push(chunk)); + doc.on('end', () => resolve(Buffer.concat(chunks))); + doc.on('error', reject); + + doc.fontSize(16).text(`Kassenbuch ${team.name}`); + doc.fontSize(10).text(`Zeitraum: ${from} bis ${to}`); + doc.moveDown(); + + if (rows.length === 0) { + doc.text('Keine Buchungen im gewählten Zeitraum.'); + } else { + for (const row of rows) { + doc.text( + `${row.date.slice(0, 10)} ${TYPE_LABELS[row.type] ?? row.type} ${row.who} ${row.note} ` + + `${formatGermanAmount(row.amount)} € Saldo: ${formatGermanAmount(row.runningTotal)} €`, + ); + } + } + + doc.end(); + }); +}