Files
ApiCore/ApiCore-Backend/src/export/pdf-table.ts
T
Nick SzaunigandClaude Opus 5.5 0d23963f07 feat(export): Bestandsbuch für Tierarzneimittel nach Art. 108 VO (EU) 2019/6
- Treatment records supplier, purchase proof and prescribing vet, and
  keeps the apiary the colony stood at on the treatment day.
- Holder name and address in site settings (never in public output;
  regression test in public.service.spec).
- treatment-log.pdf: one sheet per apiary, identical treatments grouped,
  missing mandatory fields printed as FEHLT, uncertain locations marked.
- Shared PDF table helpers extracted from the record card.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-09-28 11:10:07 +02:00

108 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Small pdfkit helpers shared by the printable records (Stockkarte,
* Bestandsbuch): dates, empty values and a table that repeats its header on
* every page.
*/
export const PDF_MARGIN = 36;
export interface PdfColumn {
header: string;
width: number;
}
export function formatDay(date: Date): string {
return date.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
timeZone: 'UTC',
});
}
export function orDash(value: string | number | null | undefined): string {
return value === null || value === undefined || value === ''
? '–'
: String(value);
}
/** Widens the last column so the table fills the page width. */
export function fillWidth(
doc: PDFKit.PDFDocument,
columns: PdfColumn[],
): PdfColumn[] {
const fixed = columns.slice(0, -1).reduce((sum, c) => sum + c.width, 0);
const last = columns[columns.length - 1];
return [
...columns.slice(0, -1),
{ ...last, width: doc.page.width - 2 * PDF_MARGIN - fixed },
];
}
export function drawSection(doc: PDFKit.PDFDocument, title: string): void {
if (doc.y > doc.page.height - PDF_MARGIN - 80) doc.addPage();
doc.moveDown(0.6);
doc
.font('Helvetica-Bold')
.fontSize(11)
.fillColor('#000000')
.text(title, PDF_MARGIN);
doc.moveDown(0.2);
}
/** Draws a simple table, repeating the header on every new page. */
export function drawTable(
doc: PDFKit.PDFDocument,
columns: PdfColumn[],
rows: string[][],
): void {
const left = PDF_MARGIN;
const bottomLimit = doc.page.height - PDF_MARGIN - 20;
const drawRow = (cells: string[], bold: boolean) => {
doc.font(bold ? 'Helvetica-Bold' : 'Helvetica').fontSize(8);
const heights = cells.map((cell, index) =>
doc.heightOfString(cell, { width: columns[index].width - 4 }),
);
const rowHeight = Math.max(...heights, 10) + 4;
if (doc.y + rowHeight > bottomLimit) {
doc.addPage();
if (!bold) {
drawRow(
columns.map((c) => c.header),
true,
);
doc.font('Helvetica').fontSize(8);
}
}
const top = doc.y;
let x = left;
cells.forEach((cell, index) => {
doc.text(cell, x + 2, top + 2, { width: columns[index].width - 4 });
x += columns[index].width;
});
doc
.moveTo(left, top + rowHeight)
.lineTo(x, top + rowHeight)
.lineWidth(0.3)
.strokeColor('#999999')
.stroke();
doc.y = top + rowHeight;
};
drawRow(
columns.map((c) => c.header),
true,
);
if (rows.length === 0) {
doc
.font('Helvetica-Oblique')
.fontSize(8)
.text('Keine Einträge.', left + 2, doc.y + 2);
doc.moveDown(0.5);
return;
}
for (const row of rows) drawRow(row, false);
doc.moveDown(0.5);
}