From 5c2878875ba32c89de35f6d91628b8d1322a0b68 Mon Sep 17 00:00:00 2001 From: Nick Szaunig Date: Tue, 6 Oct 2026 23:07:39 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Buchung=20sofort=20m=C3=B6glich,=20soba?= =?UTF-8?q?ld=20alle=20Freigaben=20erteilt=20sind?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Vor dem 01.11.2026 gilt dann der Early-Bird-Preis. Fehlt eine Freigabe, bleibt die Vorschau. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs/ARCHITEKTUR.md | 2 +- src/lib/sales-gate.ts | 5 ++++- tests/sales-gate.test.ts | 15 +++++++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/docs/ARCHITEKTUR.md b/docs/ARCHITEKTUR.md index 46d94d5..324aaa8 100644 --- a/docs/ARCHITEKTUR.md +++ b/docs/ARCHITEKTUR.md @@ -85,7 +85,7 @@ Der gehostete Checkout bildet den Ablauf vollständig ab: Betrag, Ablaufzeit, da `src/lib/sales-gate.ts` wird von jedem Checkout-Endpunkt aufgerufen. -- **Livemodus (`sk_live_`):** Verkauf nur mit laufender echter Preisphase, eingeschaltetem Live-Verkauf und allen elf Bestätigungen. +- **Livemodus (`sk_live_`):** Verkauf nur mit eingeschaltetem Live-Verkauf und allen elf Bestätigungen. Sind alle erteilt, startet der Verkauf sofort, auch vor dem 01.11.2026; bis dahin gilt der Early-Bird-Preis (seit 06.10.2026). - **Testmodus:** Checkout ist ohne Freigaben möglich, die Preisphase lässt sich simulieren. - **Schutz vor Fehlkonfiguration:** Ein Live-Schlüssel außerhalb von `APP_ENV=production` bricht mit einem Fehler ab. diff --git a/src/lib/sales-gate.ts b/src/lib/sales-gate.ts index 7d7c9ee..5f12ef2 100644 --- a/src/lib/sales-gate.ts +++ b/src/lib/sales-gate.ts @@ -107,8 +107,11 @@ export function releaseBlockers(camp: CampGateFields): ReleaseBlocker[] { export function evaluateSales(now: Date, camp: CampGateFields, mode: StripeMode): SalesState { const phase = pricePhaseAt(now); const simulate = mode === "test" && camp.testPhaseOverride !== null && camp.testPhaseOverride !== "PREVIEW"; - const effectivePhase = simulate ? camp.testPhaseOverride! : phase; const blockers = releaseBlockers(camp); + // Sind alle Freigaben bestaetigt, startet der Verkauf sofort, auch vor dem + // 01.11.2026: Bis dahin gilt der Early-Bird-Preis. + const releasedEarly = phase === "PREVIEW" && blockers.length === 0; + const effectivePhase = simulate ? camp.testPhaseOverride! : releasedEarly ? "EARLY_BIRD" : phase; const campStarted = camp.campStartDate !== null && now.getTime() >= berlinStartOfDay(camp.campStartDate).getTime(); let closedReason: SalesState["closedReason"] = null; diff --git a/tests/sales-gate.test.ts b/tests/sales-gate.test.ts index 4c85bc8..6cdedc0 100644 --- a/tests/sales-gate.test.ts +++ b/tests/sales-gate.test.ts @@ -73,8 +73,19 @@ describe("Verkaufsfreigabe", () => { expect(evaluateSales(earlyBird, released, "live").checkoutOpen).toBe(true); }); - test("Livemodus vor dem 01.11.2026: Vorschau ohne Kaufmoeglichkeit, Override wirkungslos", () => { - const state = evaluateSales(new Date("2026-10-01T12:00:00Z"), { ...released, testPhaseOverride: "EARLY_BIRD" }, "live"); + test("Livemodus vor dem 01.11.2026 mit allen Freigaben: offen zum Early-Bird-Preis", () => { + const state = evaluateSales(new Date("2026-10-01T12:00:00Z"), released, "live"); + expect(state.checkoutOpen).toBe(true); + expect(state.effectivePhase).toBe("EARLY_BIRD"); + expect(state.phaseSimulated).toBe(false); + }); + + test("Livemodus vor dem 01.11.2026 mit fehlender Freigabe: Vorschau, Override wirkungslos", () => { + const state = evaluateSales( + new Date("2026-10-01T12:00:00Z"), + { ...released, travelLawChecked: false, testPhaseOverride: "REGULAR" }, + "live", + ); expect(state.checkoutOpen).toBe(false); expect(state.closedReason).toBe("PREVIEW"); });