Compare commits
2 Commits
cd9d7b165f
...
24c509c0d5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
24c509c0d5 | ||
|
|
4185efb83a |
@@ -1,5 +1,14 @@
|
||||
import { buildCsv, buildPdf, buildReceivableRows, buildRows } from './cashbox-export.utils';
|
||||
|
||||
// Reads the page count directly out of the raw PDF bytes instead of pulling in
|
||||
// a parser dependency. Coupled to pdfkit's current /Pages dict serialization -
|
||||
// a pdfkit upgrade that reorders/reflows it could require adjusting this regex.
|
||||
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 +19,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 +309,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 () => {
|
||||
@@ -322,5 +347,8 @@ describe('buildPdf', () => {
|
||||
|
||||
expect(buffer.subarray(0, 5).toString('utf-8')).toBe('%PDF-');
|
||||
expect(buffer.length).toBeGreaterThan(2000);
|
||||
// Guards against the footer loop reintroducing blank trailing pages: with
|
||||
// the bug, this dataset produced 12 pages (3x the real content pages).
|
||||
expect(pdfPageCount(buffer)).toBe(4);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -312,12 +313,19 @@ function addFooters(doc: PDFKit.PDFDocument, teamName: string): void {
|
||||
for (let i = range.start; i < range.start + range.count; i++) {
|
||||
doc.switchToPage(i);
|
||||
const footerY = doc.page.height - 25;
|
||||
// footerY sits inside the reserved bottom margin (below pdfkit's page
|
||||
// maxY()). Without an explicit `height`, pdfkit's LineWrapper measures
|
||||
// overflow against the full-page maxY() and calls addPage() here on every
|
||||
// iteration - silently appending blank trailing pages. Bounding the text
|
||||
// to its own small box (well over the 8pt single-line height needed)
|
||||
// keeps the overflow check local and stops that auto-pagination.
|
||||
doc
|
||||
.fontSize(8)
|
||||
.font('Helvetica')
|
||||
.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 +333,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,
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user