Files
ApiCore/ApiCore-Backend/src/label-templates/label-proof-watermark.ts
T
Nick SzaunigandClaude Opus 5.5 edc193f128
Backend Image / build-and-push (push) Successful in 5m13s
Frontend Image / build-and-push (push) Successful in 8m54s
feat: T6 wave 3 – treatment books from lot, label print lock, restricted zone, authority notices
- Treatments book their dose from a medicine lot (delta booking on edit,
  bulk all-or-nothing, idempotent); Bestandsbuch columns from the lot,
  FEHLT exactly where treatmentLogGaps reports a gap; Bestandsbuch export
  needs veterinary-medicines:read.
- Label printing: 409 LABEL_PARTICULARS_MISSING on gaps, proof print with
  watermark, PRINT audit before the PDF, check endpoints; digit rules only
  from the seeded ComplianceSettings row (fail closed).
- Restricted zone (Berlin day): create, update, move, merge and CSV import
  blocked inside an active restriction; override with reason and
  compliance:manage, audited, never stored on the colony; split, harvest
  and dissolve stay free; restriction details only with compliance:read.
- Authority notices: settings, herd snapshot, registration/herd change/TSK
  notices with one open draft per kind under the settings lock.
- Label check refinements (geometric reading order, number|unit pairs).
- ARCHITECTURE.md §12.6–12.8, Annahmen 71–77; spec §5.6 Berlin day.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
2026-10-15 14:18:41 +02:00

50 lines
1.6 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.
export const PROOF_WATERMARK_TEXT = 'MUSTER – nicht verkaufsfähig';
const WATERMARK_COLOR = '#808080';
const WATERMARK_OPACITY = 0.5;
const WATERMARK_FONT = 'Helvetica-Bold';
// The text runs along the label's diagonal; this share of it is covered.
const DIAGONAL_COVERAGE = 0.8;
// Never taller than this share of the label's height.
const MAX_HEIGHT_SHARE = 0.5;
const MEASURE_SIZE_PT = 10;
/**
* Draws the proof stamp "MUSTER – nicht verkaufsfähig" over one label: grey,
* half opaque, rotated along the label's diagonal and centred. Called once
* per label, after its content. The graphics state is restored, so nothing
* leaks into the next label.
*/
export function drawProofWatermark(
doc: PDFKit.PDFDocument,
originX: number,
originY: number,
widthPt: number,
heightPt: number,
): void {
const centerX = originX + widthPt / 2;
const centerY = originY + heightPt / 2;
const angleDeg = (Math.atan2(heightPt, widthPt) * 180) / Math.PI;
doc.save();
doc.font(WATERMARK_FONT).fontSize(MEASURE_SIZE_PT);
const widthAtMeasureSize = doc.widthOfString(PROOF_WATERMARK_TEXT);
const targetWidth = Math.hypot(widthPt, heightPt) * DIAGONAL_COVERAGE;
const fontSize = Math.min(
(MEASURE_SIZE_PT * targetWidth) / widthAtMeasureSize,
heightPt * MAX_HEIGHT_SHARE,
);
const textWidth = (widthAtMeasureSize * fontSize) / MEASURE_SIZE_PT;
doc.fillColor(WATERMARK_COLOR).fillOpacity(WATERMARK_OPACITY);
doc.rotate(-angleDeg, { origin: [centerX, centerY] });
doc.fontSize(fontSize);
doc.text(
PROOF_WATERMARK_TEXT,
centerX - textWidth / 2,
centerY - fontSize / 2,
{ lineBreak: false },
);
doc.restore();
}