feat: T6 wave 3 – treatment books from lot, label print lock, restricted zone, authority notices
Backend Image / build-and-push (push) Successful in 5m13s
Frontend Image / build-and-push (push) Successful in 8m54s

- 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]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-15 14:18:41 +02:00
1 parent c18860c75c
commit edc193f128
73 files changed
+9470 -171

No files matched your search

@@ -0,0 +1,49 @@
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();
}