Files
ApiCore/ApiCore-Frontend/src/lib/treatments/lot-text.ts
T
Nick SzaunigandClaude Opus 5.5 8188e4cab3
Backend Image / build-and-push (push) Successful in 6m52s
Frontend Image / build-and-push (push) Failing after 12m28s
Manifests / validate (push) Successful in 22s
feat: T6 wave 6 – treatment form with medicine and lot, notices and settings UI, restricted zone UI
- Treatment form and bulk entry: catalogue medicine or free text, lot with
  oldest preselected on create, lot fields from the lot, dose with at most
  3 decimals, catalogue prefill with explicit take-over of the withdrawal
  period on a medicine switch, ?editTreatment deep link.
- Authority notices: list with year filter, create with draft dedupe link,
  detail steps with PDF download and mark-sent, settings with MM-TT check,
  digit-rule editor and "Annahme – bitte prüfen" notes; COMPLIANCE label
  in notifications and Telegram categories.
- Restricted zone: page with active/planned/ended, create and end dialogs
  (endsOn inclusive), red band at the apiary, guards on move, merge, edit
  and new colony, override only with compliance:manage, harvest warning.
- ARCHITECTURE.md §12.11, Annahmen 91–94; TASKS.md T6 done with open items.

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

60 lines
2.2 KiB
TypeScript

import { formatQuantity, roundToThousandths } from "@/lib/medicines/format";
import type { LotView } from "@/lib/medicines/types";
const SEPARATOR = " · ";
/** "2027-03-31" as "03/2027", read as written. */
function monthYear(day: string): string {
const [year, month] = day.split("-");
return `${month}/${year}`;
}
/** "Ch.-B. 4711 · Rest 180 ml · verw. bis 03/2027" */
export function lotLabel(lot: LotView): string {
const parts = [
lot.batchNumber ? `Ch.-B. ${lot.batchNumber}` : "Ohne Ch.-B.",
`Rest ${formatQuantity(lot.remaining, lot.unit)}`,
];
if (lot.expiresOn) parts.push(`verw. bis ${monthYear(lot.expiresOn)}`);
return parts.join(SEPARATOR);
}
/** What the pack supplies instead of the typed fields, e.g. "aus Packung: Ch.-B. 4711 · Lieferant X". */
export function lotSourceLine(lot: LotView): string {
const parts = [
lot.batchNumber ? `Ch.-B. ${lot.batchNumber}` : null,
lot.supplier ? `Lieferant ${lot.supplier}` : null,
lot.purchaseProof ? `Beleg ${lot.purchaseProof}` : null,
lot.prescribingVet ? `Tierarzt ${lot.prescribingVet}` : null,
].filter((part): part is string => part !== null);
return parts.length > 0 ? `aus Packung: ${parts.join(SEPARATOR)}` : "aus Packung";
}
/**
* What speaks against taking from this pack: expired on the treatment day, or
* too little left for the dose of all colonies. Only warnings - the server
* books anyway and the readiness check reports a negative rest.
*/
export function lotWarnings(
lot: LotView,
dose: number | null,
colonyCount: number,
treatmentDay: string,
): string[] {
const warnings: string[] = [];
if (lot.expiresOn && treatmentDay && lot.expiresOn < treatmentDay) {
warnings.push(`Die Packung ist abgelaufen (verw. bis ${monthYear(lot.expiresOn)}).`);
}
if (dose !== null && Number.isFinite(dose) && dose > 0) {
const needed = roundToThousandths(dose * Math.max(colonyCount, 1));
if (needed > lot.remaining) {
warnings.push(
`Die Packung reicht nicht: Rest ${formatQuantity(lot.remaining, lot.unit)}, gebraucht ${formatQuantity(needed, lot.unit)}.`,
);
}
} else if (lot.remaining <= 0) {
warnings.push("Die Packung ist leer.");
}
return warnings;
}