feat: platform backbone and field capture complete (checkpoint)

Platform backbone (plan 2026-10-13-plattform-rueckgrat), all tasks reviewed:
- webhooks with delivery table, retries and stable delivery id
- calendar handlers; a finished task removes its Outlook event
- tasks, automations, inspections, varroa, treatments, feedings,
  harvests, colonies, wintering, stock and bottling record domain events
  in the same transaction; legacy post-commit calls removed
- row locks through one helper (lockRowForUpdate, lockRowsForShare,
  lockRowsForUpdate)
- job framework with JobRun, cli exit codes, cleanup, task templates,
  daily evaluation, hourly app-backup and the rekey job
- system API and page "Verwaltung > System" with retry
- @nestjs/schedule removed, guard tests against in-process schedulers

Field capture T3 (plan 2026-10-13-t3-erfassung-am-stand), all tasks reviewed:
- inspection photo offline without metadata
- bulk capture for treatments, feedings and varroa (backend + frontend)
- offline stand mode with service worker, offline pages and packs
- offline session, cold start and logout on shared devices

Docs: ARCHITECTURE.md assumptions and sections updated, TASKS.md.
Gates: backend lint, typecheck, 1770 unit, 420 e2e, build, no schema
drift; frontend lint, typecheck, 913 tests, build. Not rolled out.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-14 11:36:38 +02:00
1 parent c01a38835a
commit cdd30cf3ee
288 files changed
+39165 -2853

No files matched your search

@@ -0,0 +1,151 @@
// Test data for offline packs - shared by the specs, never imported by app code.
import type { StoredPack } from "@/lib/offline/db";
import type { ColonyOverviewRow, OfflinePackV1 } from "./field-types";
export const MINUTE_MS = 60 * 1000;
export const HOUR_MS = 60 * MINUTE_MS;
export const DAY_MS = 24 * HOUR_MS;
type PackColony = OfflinePackV1["colonies"][number];
type PackInspection = NonNullable<OfflinePackV1["inspections"]>[number];
type PackTask = NonNullable<OfflinePackV1["tasks"]>[number];
type PackQueen = NonNullable<OfflinePackV1["queens"]>[number];
export function packColony(id: string, code: string, overrides: Partial<PackColony> = {}): PackColony {
return {
id,
code,
apiaryId: "apiary-1",
status: "ACTIVE",
colonyType: "PRODUCTION_COLONY",
hiveType: "Zander",
strength: 8,
broodStatus: "GOOD",
feedStatus: "GOOD",
healthStatus: "GOOD",
currentQueenId: null,
notes: null,
...overrides,
};
}
export function overviewRow(colony: PackColony, overrides: Partial<ColonyOverviewRow> = {}): ColonyOverviewRow {
return {
id: colony.id,
code: colony.code,
status: colony.status,
strength: colony.strength,
broodStatus: colony.broodStatus,
feedStatus: colony.feedStatus,
healthStatus: colony.healthStatus,
lastInspectionDate: new Date().toISOString(),
openWarnings: 0,
openCritical: 0,
...overrides,
};
}
export function packInspection(
id: string,
colonyId: string,
date: string,
overrides: Partial<PackInspection> = {},
): PackInspection {
return {
id,
colonyId,
date,
weather: null,
temperature: null,
colonyStrength: null,
broodAmount: null,
broodFrames: null,
broodPattern: null,
openBroodPresent: null,
cappedBroodPresent: null,
eggsPresent: null,
queenSeen: null,
queenCellsPresent: null,
swarmMood: null,
gentleness: null,
combBehavior: null,
feedStatus: null,
honeySuperStatus: null,
healthIssues: null,
varroaIssues: null,
measures: null,
notes: null,
createdAt: date,
...overrides,
};
}
export function packTask(id: string, title: string, overrides: Partial<PackTask> = {}): PackTask {
return {
id,
title,
description: null,
dueDate: null,
priority: "NORMAL",
status: "OPEN",
relatedType: "COLONY",
relatedId: null,
...overrides,
};
}
export function packQueen(id: string, code: string, overrides: Partial<PackQueen> = {}): PackQueen {
return {
id,
code,
hatchYear: 2025,
breed: "Carnica",
markingColor: "Rot",
markingNumber: "12",
status: "ACTIVE",
...overrides,
};
}
interface PackOptions {
apiaryId?: string;
apiaryName?: string;
colonies?: PackColony[];
queens?: OfflinePackV1["queens"];
inspections?: OfflinePackV1["inspections"];
tasks?: OfflinePackV1["tasks"];
inspectionsVisible?: boolean;
}
/** A pack as the server sends it: colonies, their overview rows and the sections. */
export function offlinePack({
apiaryId = "apiary-1",
apiaryName = "Obstwiese",
colonies = [packColony("colony-1", "A1", { apiaryId })],
queens = [],
inspections = [],
tasks = [],
inspectionsVisible = true,
}: PackOptions = {}): OfflinePackV1 {
return {
version: 1,
generatedAt: "2026-10-13T08:00:00.000Z",
apiary: { id: apiaryId, name: apiaryName, location: "Hinterm Hof", status: "ACTIVE" },
overview: {
apiaryId,
inspectionsVisible,
rows: colonies.map((colony) => overviewRow(colony)),
},
colonies,
queens,
inspections,
tasks,
};
}
export function storedPack(
pack: OfflinePackV1,
{ ownerUserId = "user-1", savedAt = new Date().toISOString() } = {},
): StoredPack {
return { apiaryId: pack.apiary.id, ownerUserId, savedAt, pack };
}