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 af2fbe6..028c08b 100644 --- a/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.spec.ts +++ b/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.spec.ts @@ -1,5 +1,11 @@ import { buildCsv, buildPdf, buildReceivableRows, buildRows } from './cashbox-export.utils'; +function pdfPageCount(buffer: Buffer): number { + const match = buffer.toString('latin1').match(/\/Type\s*\/Pages[\s\S]{0,80}?\/Count\s+(\d+)/); + if (!match) throw new Error('Could not find page count in PDF buffer'); + return Number(match[1]); +} + describe('buildRows', () => { const team = (overrides: Partial<{ transactions: any[]; players: any[] }> = {}) => ({ id: 5, @@ -10,12 +16,15 @@ describe('buildRows', () => { ...overrides, }); - it('includes team-wallet credit and expense rows as "Teamkasse"', () => { + it('includes team-wallet credit rows as-is and negates expense rows so they reduce the budget', () => { + // DB stores TeamWalletTransaction.amount as a positive number even for + // expenses (see team-wallet-transaction.entity.ts setBalance()); buildRows + // must negate expenses itself so they subtract from the running total. const rows = buildRows( team({ transactions: [ { date: '2026-08-05T00:00:00.000Z', amount: 100, note: 'Sponsoring', type: { name: 'credit' } }, - { date: '2026-08-10T00:00:00.000Z', amount: -20, note: 'Bälle', type: { name: 'expense' } }, + { date: '2026-08-10T00:00:00.000Z', amount: 20, note: 'Bälle', type: { name: 'expense' } }, ], }) as any, '2026-08-01', @@ -297,6 +306,19 @@ describe('buildPdf', () => { 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-'); + expect(pdfPageCount(buffer)).toBe(1); + }); + + it('does not append blank trailing pages when content fits on a single page', async () => { + const buffer = await buildPdf( + { name: 'Team A' } as any, + [cashRow], + [receivableRow], + '2026-08-01', + '2026-08-31', + ); + + expect(pdfPageCount(buffer)).toBe(1); }); it('paginates correctly and stays a valid PDF for many rows', async () => { diff --git a/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.ts b/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.ts index 0b8d872..8f1a060 100644 --- a/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.ts +++ b/myteamwallet_backend/src/cashbox-export/cashbox-export.utils.ts @@ -26,12 +26,13 @@ export function buildRows(team: Team, from: string, to: string): CashboxExportRo for (const transaction of team.transactions ?? []) { if (!transaction.type) continue; + const amount = Number(transaction.amount); raw.push({ date: transaction.date, type: transaction.type.name, who: 'Teamkasse', note: transaction.note, - amount: Number(transaction.amount), + amount: transaction.type.name === 'expense' ? -amount : amount, }); } @@ -318,6 +319,7 @@ function addFooters(doc: PDFKit.PDFDocument, teamName: string): void { .fillColor(COLORS.footerText) .text(`${teamName} – Kassenbuch-Report, erstellt am ${generatedAt}`, PAGE_MARGIN, footerY, { width: doc.page.width - PAGE_MARGIN * 2 - 60, + height: 20, lineBreak: false, }); doc @@ -325,6 +327,7 @@ function addFooters(doc: PDFKit.PDFDocument, teamName: string): void { .fillColor(COLORS.footerText) .text(`Seite ${i - range.start + 1} von ${range.count}`, doc.page.width - PAGE_MARGIN - 60, footerY, { width: 60, + height: 20, align: 'right', lineBreak: false, });