- 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]>
72 lines
2.0 KiB
TypeScript
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,
|
|
};
|
|
}
|