Abo-System: VanVan kann jetzt wirklich alles im Admin einstellen

Bisher waren nur Preis, Rabatt-% und ein paar Verhaltensregeln im CMS
editierbar — Namen, Vorteilstexte und die Teddy-Vergabe-Intervalle steckten
fest im Code. Jetzt ist pro Abo-Stufe (Klein/Mittel/Groß) direkt im
Adminbereich einstellbar:

- Name & Vorteilstext der Stufe, in allen 4 Sprachen einzeln
- Ob die Stufe aktuell überhaupt neu buchbar ist (bestehende Abonnent:innen
  behalten ihre Vorteile trotzdem, im Kundenkonto wird die Stufe nur für neue
  Buchungen ausgegraut mit "Derzeit nicht buchbar")
- Nach wie vielen Monaten es den kleinen/großen Teddy gibt (ersetzt die vorher
  fest verankerten 6-/12-Monats-Abstände)
- Welches Produkt als "kleiner"/"großer" Teddy für die Prämien-Einlösung
  verwendet wird (Produktauswahl statt festem Slug im Code)

Alle Berechnungen (Meilensteine, Fortschrittsanzeige) laufen jetzt komplett
über diese Admin-Werte statt über Konstanten im Code. Konto-Plan-Karten in
allen 4 Sprachen zeigen die admin-gepflegten Texte live an.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
This commit is contained in:
qcigano
2026-08-03 17:03:28 +02:00
co-authored by Claude Sonnet 5
parent 208d995941
commit a222d6c96e
10 changed files with 204 additions and 57 deletions
+50 -12
View File
@@ -8,15 +8,38 @@ import raw from "../content/abo-einstellungen.json";
export type AboStufe = "klein" | "mittel" | "gross";
export interface MehrsprachigerText {
de: string;
en: string;
ch: string;
fr: string;
}
export interface AboStufenKonfig {
preis: number;
rabattProzent: number;
/** Ob diese Stufe aktuell neu buchbar ist. Bestehende Abonnent:innen dieser Stufe behalten
* ihre Vorteile unabhängig davon — nur NEUE Buchungen werden im Kundenkonto gesperrt. */
aktiv: boolean;
/** Nach wie vielen durchgehend bezahlten Monaten es jeweils einen kleinen/großen Teddy gibt.
* 0 oder nicht gesetzt = diese Stufe kennt diese Prämienart gar nicht. Admin-editierbar,
* ersetzt die früher fest im Code verankerten 6-/12-Monats-Intervalle. */
kleinIntervallMonate: number;
grossIntervallMonate: number;
/** Anzeigename/Vorteilstext der Stufe, pro Sprache — ersetzt die früher fest in i18n/ui.ts
* verankerten Texte, damit VanVan Name und Beschreibung jeder Stufe selbst pflegen kann. */
name: MehrsprachigerText;
vorteil: MehrsprachigerText;
}
export interface AboEinstellungen {
klein: AboStufenKonfig;
mittel: AboStufenKonfig;
gross: AboStufenKonfig;
/** Produkt-Slugs (siehe src/content/products/*.json), die als "kleiner"/"großer" Teddy für
* die Prämien-Einlösung verwendet werden — im Admin per Produkt-Auswahl änderbar. */
kleinTeddySlug: string;
grossTeddySlug: string;
kombinierbarMitAnderenRabatten: boolean;
versandKostenlosBeiPraemie: boolean;
fortsetzungBeiWiederanmeldung: boolean;
@@ -31,27 +54,42 @@ export function aboStufenKonfig(stufe: AboStufe): AboStufenKonfig {
export const ABO_STUFEN: AboStufe[] = ["klein", "mittel", "gross"];
/** Name/Vorteilstext der Stufe in der gewünschten Sprache, mit Fallback auf Deutsch, falls das
* Feld im Admin (noch) leer gelassen wurde. */
export function aboStufeName(stufe: AboStufe, lang: keyof MehrsprachigerText): string {
const cfg = aboStufenKonfig(stufe);
return cfg.name?.[lang] || cfg.name?.de || stufe;
}
export function aboStufeVorteil(stufe: AboStufe, lang: keyof MehrsprachigerText): string {
const cfg = aboStufenKonfig(stufe);
return cfg.vorteil?.[lang] || cfg.vorteil?.de || "";
}
/** Wie viele Teddy-Prämien welcher Art bei X durchgehend bezahlten Monaten in dieser Stufe
* INSGESAMT freigeschaltet sein müssten (kumulativ, nicht nur "neu diesen Monat"):
* - Klein-Abo: 1 kleiner Teddy pro volles Jahr.
* - Mittel-Abo: 1 großer Teddy pro volles Jahr.
* - Groß-Abo: 1 kleiner Teddy alle 6 Monate UND zusätzlich 1 großer Teddy pro volles Jahr
* (nach 6 Monaten: 1 klein · nach 12: 2 klein + 1 groß · nach 18: 3 klein · nach 24: 4 klein + 2 groß). */
* INSGESAMT freigeschaltet sein müssten (kumulativ, nicht nur "neu diesen Monat") — rechnet
* rein aus den admin-konfigurierten Intervallen (siehe AboStufenKonfig), keine fest im Code
* verankerten Zahlen mehr. Ein Intervall von 0 (oder nicht gesetzt) bedeutet "diese Stufe
* kennt diese Prämienart nicht". */
export function teddyMeilensteine(stufe: AboStufe, monate: number): { klein: number; gross: number } {
if (monate <= 0) return { klein: 0, gross: 0 };
if (stufe === "klein") return { klein: Math.floor(monate / 12), gross: 0 };
if (stufe === "mittel") return { klein: 0, gross: Math.floor(monate / 12) };
return { klein: Math.floor(monate / 6), gross: Math.floor(monate / 12) };
const cfg = aboStufenKonfig(stufe);
const kleinIntervall = cfg.kleinIntervallMonate || 0;
const grossIntervall = cfg.grossIntervallMonate || 0;
return {
klein: kleinIntervall > 0 ? Math.floor(monate / kleinIntervall) : 0,
gross: grossIntervall > 0 ? Math.floor(monate / grossIntervall) : 0,
};
}
/** Wie viele Monate fehlen noch bis zum jeweils nächsten Meilenstein (für die
* Fortschrittsanzeige im Kundenkonto), null wenn diese Stufe die Prämienart gar nicht kennt. */
export function naechsteMeilensteine(stufe: AboStufe, monate: number): { klein: number | null; gross: number | null } {
const kleinIntervall = stufe === "gross" ? 6 : stufe === "klein" ? 12 : null;
const grossIntervall = stufe === "klein" ? null : 12;
const cfg = aboStufenKonfig(stufe);
const kleinIntervall = cfg.kleinIntervallMonate || 0;
const grossIntervall = cfg.grossIntervallMonate || 0;
return {
klein: kleinIntervall ? kleinIntervall - (monate % kleinIntervall) : null,
gross: grossIntervall ? grossIntervall - (monate % grossIntervall) : null,
klein: kleinIntervall > 0 ? kleinIntervall - (monate % kleinIntervall) : null,
gross: grossIntervall > 0 ? grossIntervall - (monate % grossIntervall) : null,
};
}