feat: T4 wave 5 - colony end cleanup, withdrawal chain, next steps, inspection takeover UI

- Colony end releases the queen in the same transaction; open
  suggestions become obsolete and a CLEANUP suggestion lists open
  tasks and the queen; apply-cleanup validates evidence and blocks
  reactivated colonies
- Withdrawal chain: plausibility warnings on harvest and bottling,
  conflict notifications also when a treatment is entered or corrected
  after the harvest (naming bottled lots), withdrawal spans in the
  calendar and M365 sync, Berlin-day end events
- Next 3 steps per colony and the "Heute" endpoint with ranking and an
  explicit merge table by intent
- Inspection-triggered rule suggestions follow the colony's latest
  inspection (resolve when the finding is gone, refresh when it stays)
- Frontend: colony card takeover in the inspection form, feed status as
  condition, occupied frames 0-40; legacy offline entries keep syncing
- Notifications and preferences are deleted with their user

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 16:28:07 +02:00
1 parent 02eb16924e
commit ef33e5549b
122 files changed
+10707 -504

No files matched your search

@@ -135,6 +135,34 @@ describe("syncPendingEntries", () => {
});
});
it("sends an inspection queued by the previous version with its legacy feed word unchanged", async () => {
// The server maps ausreichend/niedrig/kritisch itself; the queue must not rewrite or reject them.
const legacy = { ...inspection(1), feedStatus: "niedrig" } as never;
await db.queueInspection(legacy, "key-legacy-feed-0001", "user-1");
createInspection.mockResolvedValue({ id: "x" } as never);
const result = await sync.syncPendingEntries("user-1");
expect(result).toEqual({ synced: 1, failed: 0, skippedForeign: 0 });
expect(createInspection).toHaveBeenCalledWith(
expect.objectContaining({ feedStatus: "niedrig" }),
{ idempotencyKey: "key-legacy-feed-0001" },
);
});
it("sends the Volkskarte fields of a queued inspection along", async () => {
const queued = { ...inspection(1), feedStatus: "WEAK", applyToColony: ["feedStatus"] } as never;
await db.queueInspection(queued, "key-takeover-0001", "user-1");
createInspection.mockResolvedValue({ id: "x" } as never);
await sync.syncPendingEntries("user-1");
expect(createInspection).toHaveBeenCalledWith(
expect.objectContaining({ feedStatus: "WEAK", applyToColony: ["feedStatus"] }),
{ idempotencyKey: "key-takeover-0001" },
);
});
it("still sends entries queued before keys and owners existed", async () => {
await db.summarizeQueue("user-1"); // creates the database at the current version
const raw = await openDB("apicore-offline");