feat: Ein-/Auswinterung und Winterverluste je Volk
- WinterCheck per colony, winter and season (autumn/spring), every field editable; loss causes limited to the COLOSS monitoring categories. - Summary per winter and apiary: loss rate lost/wintered, marked provisional while colonies are not yet checked in spring - an unchecked colony never counts as survived. - A loss marks the colony deceased only on explicit request, in the same transaction and audited. - Permissions winter-checks:read/create/update granted by migration to beekeeper and staff roles; winter checks included in backups. - Page "Überwinterung": next action first, one button per row until the colony is wintered, German number format. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
1 parent
0d23963f07
commit
fac66882fa
26 files changed
+2020
-2
No files matched your search
@@ -0,0 +1,106 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { Colony } from "@/lib/colonies/types";
|
||||
import type { WinterCheckWithColony } from "./types";
|
||||
import { buildWinterRows, winterLabel, winterYearFor } from "./winter";
|
||||
|
||||
function colony(overrides: Partial<Colony>): Colony {
|
||||
return {
|
||||
id: "c1",
|
||||
code: "V-01",
|
||||
apiaryId: "a",
|
||||
hiveType: null,
|
||||
originText: null,
|
||||
colonyType: "PRODUCTION_COLONY",
|
||||
status: "ACTIVE",
|
||||
strength: null,
|
||||
broodStatus: null,
|
||||
feedStatus: null,
|
||||
healthStatus: null,
|
||||
notes: null,
|
||||
currentQueenId: null,
|
||||
createdAt: "",
|
||||
updatedAt: "",
|
||||
deactivatedAt: null,
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
const deadColony = {
|
||||
id: "c1",
|
||||
code: "V-01",
|
||||
status: "DECEASED",
|
||||
apiaryId: "a",
|
||||
apiary: { id: "a", name: "Obstwiese" },
|
||||
};
|
||||
|
||||
function check(overrides: Partial<WinterCheckWithColony>): WinterCheckWithColony {
|
||||
return {
|
||||
id: "w1",
|
||||
colonyId: "c1",
|
||||
userId: "u",
|
||||
winterYear: 2026,
|
||||
season: "AUTUMN",
|
||||
date: "2026-10-01T00:00:00.000Z",
|
||||
occupiedSeams: null,
|
||||
feedStockKg: null,
|
||||
queenPresent: null,
|
||||
outcome: null,
|
||||
lossCause: null,
|
||||
notes: null,
|
||||
createdAt: "",
|
||||
updatedAt: "",
|
||||
colony: { ...deadColony, status: "ACTIVE" },
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
describe("winterYearFor / winterLabel", () => {
|
||||
it("maps autumn and spring to the same winter", () => {
|
||||
expect(winterYearFor(new Date(2026, 9, 1))).toBe(2026);
|
||||
expect(winterYearFor(new Date(2027, 2, 20))).toBe(2026);
|
||||
expect(winterLabel(2026)).toBe("Winter 2026/27");
|
||||
});
|
||||
});
|
||||
|
||||
describe("buildWinterRows", () => {
|
||||
it("lists living colonies without records and hides old dead ones", () => {
|
||||
const groups = buildWinterRows(
|
||||
[colony({ id: "c1" }), colony({ id: "c2", code: "V-02", status: "DECEASED" })],
|
||||
[],
|
||||
new Map([["a", "Obstwiese"]]),
|
||||
);
|
||||
expect(groups).toHaveLength(1);
|
||||
expect(groups[0].rows.map((row) => row.colonyId)).toEqual(["c1"]);
|
||||
});
|
||||
|
||||
it("keeps a colony that died this winter, with its loss record", () => {
|
||||
const groups = buildWinterRows(
|
||||
[],
|
||||
[
|
||||
check({ colony: deadColony }),
|
||||
check({
|
||||
id: "w2",
|
||||
season: "SPRING",
|
||||
outcome: "LOST",
|
||||
lossCause: "DEAD_OR_EMPTY",
|
||||
colony: deadColony,
|
||||
}),
|
||||
],
|
||||
new Map(),
|
||||
);
|
||||
const [row] = groups[0].rows;
|
||||
expect(row).toMatchObject({ isLiving: false });
|
||||
expect(row.autumn?.id).toBe("w1");
|
||||
expect(row.spring?.outcome).toBe("LOST");
|
||||
expect(groups[0].apiaryName).toBe("Obstwiese");
|
||||
});
|
||||
|
||||
it("sorts colony codes naturally", () => {
|
||||
const groups = buildWinterRows(
|
||||
[colony({ id: "c10", code: "V-10" }), colony({ id: "c2", code: "V-2" })],
|
||||
[],
|
||||
new Map([["a", "Obstwiese"]]),
|
||||
);
|
||||
expect(groups[0].rows.map((row) => row.code)).toEqual(["V-2", "V-10"]);
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user