Files
ApiCore/ApiCore-Backend/src/compliance/herd-status.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

72 lines
2.0 KiB
TypeScript

import { AuthorityNotice, ComplianceSettings } from '../../generated/prisma';
import { DbClient } from '../prisma/db-client';
import {
buildHerdSnapshot,
herdDeviation,
HerdDeviation,
HerdSnapshot,
loadHerdApiaryRows,
} from './herd-snapshot';
export const COMPLIANCE_SETTINGS_ID = 'singleton';
export interface HerdStatus {
settings: ComplianceSettings;
baseline: AuthorityNotice | null;
current: HerdSnapshot;
/** null without a baseline: there is nothing to deviate from. */
deviation: HerdDeviation | null;
}
/** The settings row, created with its defaults when it does not exist yet. */
export async function complianceSettingsIn(
db: DbClient,
): Promise<ComplianceSettings> {
const existing = await db.complianceSettings.findUnique({
where: { id: COMPLIANCE_SETTINGS_ID },
});
if (existing) {
return existing;
}
return db.complianceSettings.upsert({
where: { id: COMPLIANCE_SETTINGS_ID },
update: {},
create: { id: COMPLIANCE_SETTINGS_ID },
});
}
/**
* The baseline is the newest SENT registration or herd change (by sentOn);
* TSK reports go to another recipient and do not count.
*/
export function findBaselineIn(db: DbClient): Promise<AuthorityNotice | null> {
return db.authorityNotice.findFirst({
where: { status: 'SENT', kind: { in: ['REGISTRATION', 'HERD_CHANGE'] } },
orderBy: [{ sentOn: 'desc' }, { createdAt: 'desc' }, { id: 'desc' }],
});
}
export async function herdStatusIn(
db: DbClient,
now: Date,
): Promise<HerdStatus> {
const settings = await complianceSettingsIn(db);
const baseline = await findBaselineIn(db);
const current = buildHerdSnapshot(await loadHerdApiaryRows(db), now);
return {
settings,
baseline,
current,
deviation: baseline
? herdDeviation(
{
snapshot: baseline.snapshot as unknown as HerdSnapshot,
totalColonies: baseline.totalColonies,
},
current,
settings,
)
: null,
};
}