- 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]>
60 lines
2.2 KiB
TypeScript
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;
|
|
}
|