import { describe, expect, test } from "vitest"; import { buildSchedule, splitIntoInstallments } from "@/lib/payment-plan"; import { calculatePrice } from "@/lib/pricing"; describe("Exakte Zahlungsplaene laut Vorgabe", () => { test.each([ ["Early Bird", "EARLY_BIRD", false, [[220_000], [110_000, 110_000], [80_000, 70_000, 70_000]]], ["Early Bird Stammmodel", "EARLY_BIRD", true, [[198_000], [99_000, 99_000], [66_000, 66_000, 66_000]]], ["Regulaer", "REGULAR", false, [[275_000], [137_500, 137_500], [95_000, 90_000, 90_000]]], ["Regulaer Stammmodel", "REGULAR", true, [[247_500], [123_750, 123_750], [82_500, 82_500, 82_500]]], ] as const)("%s", (_label, phase, code, expected) => { const total = calculatePrice(phase, code).totalCents; for (const [index, plan] of expected.entries()) { const split = splitIntoInstallments(total, index + 1); expect(split).toEqual(plan); expect(split.reduce((a, b) => a + b, 0)).toBe(total); } }); test("1.237,50 € je Rate bleibt centgenau", () => { expect(splitIntoInstallments(247_500, 2)).toEqual([123_750, 123_750]); }); test("mehr als drei Raten sind ausgeschlossen", () => { expect(() => splitIntoInstallments(220_000, 4)).toThrow(); }); }); describe("Faelligkeiten", () => { const base = { totalCents: 220_000, campStartDate: "2027-06-05", finalInstallmentLeadDays: 30, minInstallmentGapDays: 14, }; test("letzte Rate 30 Tage vor Anreise, zweite in der Mitte, Einzug 09:00 Berlin", () => { const result = buildSchedule({ ...base, planType: "THREE_INSTALLMENTS", now: new Date("2026-11-15T10:00:00Z") }); expect(result.ok).toBe(true); if (!result.ok) return; // 172 Tage bis zur Frist -> zweite Rate nach 86 Tagen. expect(result.installments.map((i) => i.dueDate)).toEqual([null, "2027-02-09", "2027-05-06"]); // 06.05.2027 09:00 Sommerzeit = 07:00 UTC expect(result.installments[2]!.dueAt!.toISOString()).toBe("2027-05-06T07:00:00.000Z"); expect(result.installments[0]!.amountCents).toBe(80_000); }); test("ohne Camp-Termin keine Ratenzahlung, Einmalzahlung bleibt moeglich", () => { const now = new Date("2026-11-15T10:00:00Z"); expect(buildSchedule({ ...base, campStartDate: null, planType: "TWO_INSTALLMENTS", now })).toEqual({ ok: false, reason: "CAMP_DATE_MISSING", }); expect(buildSchedule({ ...base, campStartDate: null, planType: "FULL", now }).ok).toBe(true); }); test("zu knapp vor der Frist wird die Variante serverseitig deaktiviert", () => { // Frist 06.05.2027; 20 Tage vorher reichen fuer zwei, nicht fuer drei Raten. const now = new Date("2027-04-16T10:00:00Z"); expect(buildSchedule({ ...base, planType: "TWO_INSTALLMENTS", now }).ok).toBe(true); expect(buildSchedule({ ...base, planType: "THREE_INSTALLMENTS", now })).toEqual({ ok: false, reason: "TOO_LATE" }); const later = new Date("2027-04-30T10:00:00Z"); expect(buildSchedule({ ...base, planType: "TWO_INSTALLMENTS", now: later })).toEqual({ ok: false, reason: "TOO_LATE" }); }); });