Alle ausstehenden Aenderungen (Bilder, Texte) fuer den Server-Umzug uebernommen
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
/* =====================================================================
|
||||
lib/supporter-cycle.js — Reine Rechenlogik für den 30-Monats-
|
||||
Prämienzyklus des Dogfather Supporter-Abos (die Teddy-Kollektion
|
||||
entsteht in Kooperation mit VanVan, das Abo selbst gehört nur Dogfather).
|
||||
|
||||
Verbindliche Regeln (siehe Dogfather_VanVan_Supporter_Abo.odt,
|
||||
Abschnitt 11-13, wortwörtlich umgesetzt, NICHT verändert):
|
||||
Monat 6 → kleiner Hasen-Teddy
|
||||
Monat 12 → mittelgroßer Hasen-Teddy
|
||||
Monat 18 → großer Hasen-Teddy
|
||||
Monat 24 → Tasse + Autogrammkarte
|
||||
Monat 30 → Hoodie (Zyklus damit abgeschlossen)
|
||||
ab 31 → neuer Zyklus, neue Kollektion, Fortschritt bei 0
|
||||
|
||||
Reine Funktionen ohne D1-/Netzwerkzugriff — dadurch leicht von Hand
|
||||
nachrechenbar und ohne Testrunner überprüfbar (siehe Beispiel im
|
||||
Auftrag: 37 bezahlte Monate → Zyklus 2, Fortschritt 7 von 30).
|
||||
===================================================================== */
|
||||
|
||||
export const CYCLE_LENGTH = 30;
|
||||
|
||||
// Reihenfolge ist bewusst fest — bestimmt auch premium_type-Zuordnung.
|
||||
export const MILESTONES = [
|
||||
{ month: 6, type: "teddy_klein", labelDe: "Kleiner Hasen-Teddy" },
|
||||
{ month: 12, type: "teddy_mittel", labelDe: "Mittelgroßer Hasen-Teddy" },
|
||||
{ month: 18, type: "teddy_gross", labelDe: "Großer Hasen-Teddy" },
|
||||
{ month: 24, type: "tasse_karte", labelDe: "Tasse & Autogrammkarte" },
|
||||
{ month: 30, type: "hoodie", labelDe: "Exklusiver Hoodie" },
|
||||
];
|
||||
|
||||
/**
|
||||
* Aus der GESAMTEN Anzahl erfolgreich angerechneter Abo-Monate (bleibt für
|
||||
* immer erhalten, auch über Kündigung/Reaktivierung hinweg) errechnet sich
|
||||
* rein rechnerisch: wie viele 30er-Zyklen sind vollständig abgeschlossen,
|
||||
* in welchem Zyklus befindet man sich gerade (1-basiert) und wie weit ist
|
||||
* der aktuelle Zyklus fortgeschritten (1..30).
|
||||
*/
|
||||
export function computeProgress(totalPaidMonths) {
|
||||
const total = Math.max(0, Math.floor(totalPaidMonths || 0));
|
||||
if (total === 0) {
|
||||
return { totalPaidMonths: 0, completedCycles: 0, cycleNumber: 1, currentCycleMonth: 0 };
|
||||
}
|
||||
const completedCycles = Math.floor((total - 1) / CYCLE_LENGTH);
|
||||
const currentCycleMonth = total - completedCycles * CYCLE_LENGTH; // 1..30
|
||||
return {
|
||||
totalPaidMonths: total,
|
||||
completedCycles,
|
||||
cycleNumber: completedCycles + 1, // 1-basiert = welche Kollektion gerade läuft
|
||||
currentCycleMonth,
|
||||
};
|
||||
}
|
||||
|
||||
/** Welche Meilensteine sind im AKTUELLEN, laufenden Zyklus schon erreicht? */
|
||||
export function reachedMilestonesInCurrentCycle(currentCycleMonth) {
|
||||
return MILESTONES.filter((m) => currentCycleMonth >= m.month);
|
||||
}
|
||||
|
||||
/** Nächster noch offener Meilenstein im laufenden Zyklus (oder null, wenn Zyklus fertig). */
|
||||
export function nextMilestone(currentCycleMonth) {
|
||||
return MILESTONES.find((m) => currentCycleMonth < m.month) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Liefert ALLE (Zyklus, Meilenstein)-Kombinationen, die bei der gegebenen
|
||||
* Gesamtmonatszahl freigeschaltet sein MÜSSEN — über alle bereits
|
||||
* abgeschlossenen Zyklen hinweg plus den laufenden Zyklus. Wird beim
|
||||
* Verarbeiten einer erfolgreichen Zahlung benutzt, um herauszufinden,
|
||||
* welche supporter_premiums-Zeilen neu angelegt werden müssen.
|
||||
*/
|
||||
export function allUnlockedMilestones(totalPaidMonths) {
|
||||
const { completedCycles, currentCycleMonth } = computeProgress(totalPaidMonths);
|
||||
const unlocked = [];
|
||||
// Vollständig abgeschlossene Zyklen: ALLE 5 Meilensteine gelten als erreicht.
|
||||
for (let c = 1; c <= completedCycles; c++) {
|
||||
for (const m of MILESTONES) unlocked.push({ cycleNumber: c, ...m });
|
||||
}
|
||||
// Laufender Zyklus: nur die bereits erreichten.
|
||||
const cycleNumber = completedCycles + 1;
|
||||
for (const m of reachedMilestonesInCurrentCycle(currentCycleMonth)) {
|
||||
unlocked.push({ cycleNumber, ...m });
|
||||
}
|
||||
return unlocked;
|
||||
}
|
||||
|
||||
/** Für die Fortschritts-Zeitleiste im Supporter-Bereich (Abschnitt 10, Fenster 2). */
|
||||
export function timelineForCurrentCycle(totalPaidMonths) {
|
||||
const { currentCycleMonth, cycleNumber } = computeProgress(totalPaidMonths);
|
||||
return {
|
||||
cycleNumber,
|
||||
currentCycleMonth,
|
||||
milestones: MILESTONES.map((m) => ({
|
||||
...m,
|
||||
reached: currentCycleMonth >= m.month,
|
||||
isNext: nextMilestone(currentCycleMonth)?.month === m.month,
|
||||
monthsRemaining: Math.max(0, m.month - currentCycleMonth),
|
||||
})),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user