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 { 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(); }); });