Files
FireAdministration/tests/unit/wartung-planung.test.ts
T
Florian WildeandClaude Opus 5 e503aafc18 Phase 2: Wartungs- und Pruefmanagement
Wartungsvorlagen mit Checklisten, Prueftermine aus einem naechtlichen Lauf,
Prueffristen-Dashboard, Pruefwizard mit Foto und Unterschrift, unveraenderliche
Protokolle als PDF, Maengel-Workflow, Rueckrufe und Lebensdauerueberwachung.

Elf neue Tabellen, alle mit organisationId und Row Level Security. Die
Unveraenderbarkeit abgeschlossener Protokolle setzt ein Datenbanktrigger
durch, nicht der Anwendungscode: eine Korrektur erzeugt eine neue Pruefung
mit Verweis auf die alte, beide bleiben lesbar.

79 neue Tests (47 Unit, 10 gegen eine echte Datenbank, 22 E2E).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-03 12:07:11 +02:00

141 lines
4.3 KiB
TypeScript

import { describe, expect, it } from "vitest";
import {
fruehesteFaelligkeit,
zuPlanendeTermine,
type PlanungsGeraet,
} from "@/lib/wartung/planung";
/**
* Terminplanung des nächtlichen Laufs.
*
* Der Lauf entscheidet, was im Fristen-Dashboard steht. Ein Fehler hier fällt
* niemandem auf — bis eine Prüfung fehlt, die hätte stattfinden müssen.
*/
const HEUTE = new Date("2026-06-15T02:15:00.000Z");
function tag(jahr: number, monat: number, tagImMonat: number): Date {
return new Date(Date.UTC(jahr, monat - 1, tagImMonat));
}
const VORLAGE = { id: "v1", intervallMonate: 12 };
function geraet(teil: Partial<PlanungsGeraet> = {}): PlanungsGeraet {
return {
id: "g1",
inventarnummer: "INV-0001",
status: "EINSATZBEREIT",
hatOffenenTermin: false,
...teil,
};
}
describe("zuPlanendeTermine", () => {
it("rechnet ab der letzten Prüfung nach dieser Vorlage", () => {
const geplant = zuPlanendeTermine(
VORLAGE,
[geraet({ letztePruefungZurVorlage: tag(2025, 8, 1) })],
{ horizontTage: 180, heute: HEUTE },
);
expect(geplant).toHaveLength(1);
expect(geplant[0]!.faelligAm.toISOString()).toBe("2026-08-01T00:00:00.000Z");
expect(geplant[0]!.ohneBasisdatum).toBe(false);
});
it("nimmt ersatzweise das Prüfdatum am Gerät, dann die Indienststellung", () => {
const ausGeraet = zuPlanendeTermine(
VORLAGE,
[geraet({ letztePruefung: tag(2025, 7, 1), indienststellung: tag(2020, 1, 1) })],
{ horizontTage: 180, heute: HEUTE },
);
expect(ausGeraet[0]!.faelligAm.toISOString()).toBe("2026-07-01T00:00:00.000Z");
const ausIndienststellung = zuPlanendeTermine(
{ id: "v1", intervallMonate: 6 },
[geraet({ indienststellung: tag(2026, 2, 1) })],
{ horizontTage: 180, heute: HEUTE },
);
expect(ausIndienststellung[0]!.faelligAm.toISOString()).toBe("2026-08-01T00:00:00.000Z");
});
it("setzt Geräte ohne jedes Datum sofort fällig", () => {
const geplant = zuPlanendeTermine(VORLAGE, [geraet()], {
horizontTage: 180,
heute: HEUTE,
});
expect(geplant).toHaveLength(1);
expect(geplant[0]!.ohneBasisdatum).toBe(true);
expect(geplant[0]!.faelligAm.toISOString()).toBe("2026-06-15T00:00:00.000Z");
});
it("plant nichts für ausgesonderte Geräte", () => {
const geplant = zuPlanendeTermine(
VORLAGE,
[geraet({ status: "AUSGESONDERT", letztePruefungZurVorlage: tag(2025, 8, 1) })],
{ horizontTage: 180, heute: HEUTE },
);
expect(geplant).toHaveLength(0);
});
it("plant gesperrte Geräte weiter — die sollen ja wieder in Dienst", () => {
const geplant = zuPlanendeTermine(
VORLAGE,
[geraet({ status: "GESPERRT", letztePruefungZurVorlage: tag(2025, 8, 1) })],
{ horizontTage: 180, heute: HEUTE },
);
expect(geplant).toHaveLength(1);
});
it("legt keinen zweiten Termin an, solange einer offen ist", () => {
const geplant = zuPlanendeTermine(
VORLAGE,
[geraet({ hatOffenenTermin: true, letztePruefungZurVorlage: tag(2025, 8, 1) })],
{ horizontTage: 180, heute: HEUTE },
);
expect(geplant).toHaveLength(0);
});
it("plant nur innerhalb des Horizonts", () => {
const geplant = zuPlanendeTermine(
VORLAGE,
[geraet({ letztePruefungZurVorlage: tag(2026, 5, 1) })],
// Fällig am 01.05.2027 — mit 30 Tagen Horizont noch nicht zu planen.
{ horizontTage: 30, heute: HEUTE },
);
expect(geplant).toHaveLength(0);
});
it("plant Überfälliges immer, egal wie lange es her ist", () => {
const geplant = zuPlanendeTermine(
VORLAGE,
[geraet({ letztePruefungZurVorlage: tag(2019, 1, 1) })],
{ horizontTage: 30, heute: HEUTE },
);
expect(geplant).toHaveLength(1);
expect(geplant[0]!.faelligAm.toISOString()).toBe("2020-01-01T00:00:00.000Z");
});
});
describe("fruehesteFaelligkeit", () => {
it("nimmt den dringendsten von mehreren Terminen", () => {
const frueheste = fruehesteFaelligkeit([
{ faelligAm: tag(2026, 9, 1) },
{ faelligAm: tag(2026, 7, 1) },
{ faelligAm: tag(2027, 1, 1) },
]);
expect(frueheste?.toISOString()).toBe("2026-07-01T00:00:00.000Z");
});
it("gibt ohne Termine nichts zurück", () => {
expect(fruehesteFaelligkeit([])).toBeNull();
});
});