import { describe, expect, it } from "vitest"; import { STANDARDKATEGORIEN, aehnlichkeit, kategorieVorschlagen, woerter, } from "@/lib/kasse/vorschlag"; /** * Der Kategorievorschlag. * * Sein Nutzen liegt nicht in den gesparten Sekunden, sondern darin, dass * dieselbe Sache immer unter derselben Kategorie landet — erst das macht die * Jahresauswertung brauchbar. Zwei Kategorien für Diesel, weil einmal * „Kraftstoff" und einmal „Fahrzeuge" gewählt wurde, sind in der * Jahreshauptversammlung nicht zu erklären. * * Ebenso wichtig ist, wann er schweigt: ein falscher Vorschlag wird * übernommen, ohne gelesen zu werden. Deshalb die Schwelle bei der Hälfte * gemeinsamer Wörter — und deshalb prüft dieser Test beide Seiten. */ const KATEGORIEN = [ { id: "k-kraftstoff", name: "Kraftstoff", art: "AUSGABE" as const }, { id: "k-reparatur", name: "Reparatur", art: "AUSGABE" as const }, { id: "k-strom", name: "Wasser und Strom", art: "AUSGABE" as const }, { id: "k-zuschuss", name: "Zuschüsse", art: "EINNAHME" as const }, { id: "k-beitrag", name: "Mitgliedsbeiträge", art: "EINNAHME" as const }, ]; describe("Wörter", () => { it("wirft Füllwörter, kurze Wörter und reine Zahlen weg", () => { expect(woerter("Rechnung Nr. 4711 vom 12.03. für die Feuerwehr GmbH")).toEqual([]); }); it("behält, was unterscheidet", () => { expect(woerter("Tankstelle Musterhausen Diesel LF 20")).toEqual([ "tankstelle", "musterhausen", "diesel", ]); }); it("lässt Umlaute stehen", () => { expect(woerter("Prüfung Drehleiter")).toEqual(["prüfung", "drehleiter"]); }); }); describe("Ähnlichkeit", () => { it("misst am kürzeren der beiden Texte", () => { // "stadtwerke musterhausen" steckt vollständig in beiden. const wert = aehnlichkeit( "Stadtwerke Musterhausen Abschlag Strom", "Stadtwerke Musterhausen Jahresabrechnung Strom und Wasser", ); expect(wert).toBeGreaterThanOrEqual(0.5); }); it("ergibt null bei nichts Gemeinsamem", () => { expect(aehnlichkeit("Diesel Tankstelle", "Einsatzjacken Beschaffung")).toBe(0); }); it("ergibt null, wenn nach dem Aussieben nichts übrig ist", () => { expect(aehnlichkeit("Rechnung vom", "Rechnung Nr. 12")).toBe(0); }); }); describe("Vorschlag aus dem eigenen Bestand", () => { const vorbilder = [ { kategorieId: "k-kraftstoff", kategorieName: "Kraftstoff", verwendungszweck: "Tankstelle Musterhausen Diesel LF 20 Sammelrechnung", art: "AUSGABE" as const, }, { kategorieId: "k-reparatur", kategorieName: "Reparatur", verwendungszweck: "Werkstatt Müller Kupplung MTW", art: "AUSGABE" as const, }, ]; it("nimmt die Kategorie, unter der dasselbe zuletzt gebucht wurde", () => { const vorschlag = kategorieVorschlagen( "Tankstelle Musterhausen Diesel MTW", "AUSGABE", vorbilder, KATEGORIEN, ); expect(vorschlag?.kategorieId).toBe("k-kraftstoff"); expect(vorschlag?.grund).toMatch(/zuletzt so gebucht/); }); it("geht dem Bestand vor den Stichwörtern nach — die Wehr kennt sich selbst am besten", () => { const eigen = [ { kategorieId: "k-strom", kategorieName: "Wasser und Strom", // Diese Wehr bucht Diesel unter Wasser und Strom. Merkwürdig, aber // ihre Entscheidung — und sie soll dabei bleiben dürfen. verwendungszweck: "Tankstelle Musterhausen Diesel LF 20", art: "AUSGABE" as const, }, ]; const vorschlag = kategorieVorschlagen( "Tankstelle Musterhausen Diesel LF 20", "AUSGABE", eigen, KATEGORIEN, ); expect(vorschlag?.kategorieId).toBe("k-strom"); }); it("schweigt bei zufälliger Übereinstimmung, statt zu raten", () => { const vorschlag = kategorieVorschlagen( "Möbelhaus Sonnleitner Tischbeschaffung Schulungsraum", "AUSGABE", vorbilder, KATEGORIEN, ); expect(vorschlag).toBeNull(); }); it("mischt Einnahmen und Ausgaben nicht", () => { const vorschlag = kategorieVorschlagen( "Tankstelle Musterhausen Diesel LF 20 Sammelrechnung", "EINNAHME", vorbilder, KATEGORIEN, ); // Der Vorgänger ist eine Ausgabe und kommt deshalb nicht in Frage; ein // Stichwort für Einnahmen trifft hier ebenso wenig. expect(vorschlag?.kategorieId).not.toBe("k-kraftstoff"); }); }); describe("Vorschlag aus Stichwörtern", () => { it("greift, solange es keinen Bestand gibt", () => { const vorschlag = kategorieVorschlagen("Diesel für das LF 20", "AUSGABE", [], KATEGORIEN); expect(vorschlag?.kategorieId).toBe("k-kraftstoff"); expect(vorschlag?.grund).toMatch(/Verwendungszweck/); // Niedrigere Sicherheit als ein Treffer aus dem Bestand: ein Stichwort // weiß nichts über die Gepflogenheiten dieser Wehr. expect(vorschlag?.sicherheit).toBeLessThan(1); }); it("erkennt den Gemeindezuschuss als Einnahme", () => { const vorschlag = kategorieVorschlagen( "Zuschuss der Gemeinde für den laufenden Betrieb", "EINNAHME", [], KATEGORIEN, ); expect(vorschlag?.kategorieId).toBe("k-zuschuss"); }); it("schlägt nichts vor, wenn es die Kategorie in dieser Wehr nicht gibt", () => { const vorschlag = kategorieVorschlagen("Neue Einsatzjacken", "AUSGABE", [], KATEGORIEN); expect(vorschlag).toBeNull(); }); it("schweigt bei leerem Verwendungszweck", () => { expect(kategorieVorschlagen(" ", "AUSGABE", [], KATEGORIEN)).toBeNull(); }); }); describe("Standardkontenrahmen", () => { it("verweist nur auf Oberkategorien, die es auch gibt", () => { const namen = new Set(STANDARDKATEGORIEN.map((eintrag) => eintrag.name)); for (const eintrag of STANDARDKATEGORIEN) { if (eintrag.oberkategorie) { expect(namen.has(eintrag.oberkategorie)).toBe(true); } } }); it("gibt jeden Namen nur einmal — die Datenbank verlangt es so", () => { const namen = STANDARDKATEGORIEN.map((eintrag) => eintrag.name); expect(new Set(namen).size).toBe(namen.length); }); it("stellt jede Unterkategorie in eine Oberkategorie derselben Richtung", () => { const nachName = new Map(STANDARDKATEGORIEN.map((eintrag) => [eintrag.name, eintrag])); for (const eintrag of STANDARDKATEGORIEN) { if (eintrag.oberkategorie) { expect(nachName.get(eintrag.oberkategorie)?.art).toBe(eintrag.art); } } }); });