feat: add buildPdf for cashbox export
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { buildCsv, buildRows } from './cashbox-export.utils';
|
import { buildCsv, buildPdf, buildRows } from './cashbox-export.utils';
|
||||||
|
|
||||||
describe('buildRows', () => {
|
describe('buildRows', () => {
|
||||||
const team = (overrides: Partial<{ transactions: any[]; players: any[] }> = {}) => ({
|
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-');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import { Team } from 'src/teams/entities/team.entity';
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
|
const PDFDocument = require('pdfkit');
|
||||||
|
|
||||||
export interface CashboxExportRow {
|
export interface CashboxExportRow {
|
||||||
date: string;
|
date: string;
|
||||||
@@ -100,3 +102,35 @@ export function buildCsv(rows: CashboxExportRow[]): string {
|
|||||||
}
|
}
|
||||||
return lines.join('\r\n');
|
return lines.join('\r\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function buildPdf(
|
||||||
|
team: Pick<Team, 'name'>,
|
||||||
|
rows: CashboxExportRow[],
|
||||||
|
from: string,
|
||||||
|
to: string,
|
||||||
|
): Promise<Buffer> {
|
||||||
|
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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user