Files
Artista-Filmcamp/src/lib/pricing.ts
T
Nick Szaunig 59bb5a38a3
Docker-Images bauen und veroeffentlichen / build-and-push (push) Failing after 1m32s
first commit
2026-09-27 06:06:56 +02:00

82 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Verbindliche Preislogik (Briefing Abschnitt 5). Reine Funktionen ohne
* Datenbank — Grundlage fuer Anzeige, Checkout und Tests.
*
* Grundpreis 2.750,00 €
* Early Bird (-20 %) 2.200,00 € 01.11.2026 00:00 – 31.12.2026 24:00 Berlin
* Regulaer 2.750,00 € ab 01.01.2027 00:00 Berlin
* Stammmodel-Code weitere -10 % auf den jeweils gueltigen Preis
* (Early Bird 1.980,00 € = -28 % gesamt, Regulaer 2.475,00 €)
*/
import { assertCents } from "./money";
import { berlinToUtc } from "./time";
export type PricePhase = "PREVIEW" | "EARLY_BIRD" | "REGULAR";
export type SellablePhase = Exclude<PricePhase, "PREVIEW">;
export const BASE_PRICE_CENTS = 275_000;
export const EARLY_BIRD_DISCOUNT_PERCENT = 20;
export const STAMMMODEL_DISCOUNT_PERCENT = 10;
/** 01.11.2026 00:00 Europe/Berlin */
export const EARLY_BIRD_START = berlinToUtc(2026, 11, 1);
/** 01.01.2027 00:00 Europe/Berlin — Ende Early Bird, Beginn Regulaer. */
export const REGULAR_START = berlinToUtc(2027, 1, 1);
export function pricePhaseAt(now: Date): PricePhase {
const t = now.getTime();
if (t < EARLY_BIRD_START.getTime()) return "PREVIEW";
if (t < REGULAR_START.getTime()) return "EARLY_BIRD";
return "REGULAR";
}
export type PriceBreakdown = {
phase: SellablePhase;
basePriceCents: number;
phaseDiscountCents: number;
/** Preis nach Phasenrabatt, vor Gutschein. */
phasePriceCents: number;
voucherDiscountCents: number;
totalCents: number;
hasStammmodelCode: boolean;
};
export function calculatePrice(phase: SellablePhase, hasStammmodelCode: boolean): PriceBreakdown {
const basePriceCents = BASE_PRICE_CENTS;
const phaseDiscountCents = phase === "EARLY_BIRD" ? percentOf(basePriceCents, EARLY_BIRD_DISCOUNT_PERCENT) : 0;
const phasePriceCents = basePriceCents - phaseDiscountCents;
// Der Code wirkt NACH dem Phasenrabatt auf den dann gueltigen Preis
// (multiplikativ, nicht additiv: 28 % statt 30 % im Early Bird).
const voucherDiscountCents = hasStammmodelCode ? percentOf(phasePriceCents, STAMMMODEL_DISCOUNT_PERCENT) : 0;
const totalCents = phasePriceCents - voucherDiscountCents;
assertCents(totalCents);
return {
phase,
basePriceCents,
phaseDiscountCents,
phasePriceCents,
voucherDiscountCents,
totalCents,
hasStammmodelCode,
};
}
/**
* Prozentanteil in ganzen Cent. Die vorgegebenen Preise ergeben immer glatte
* Cent-Betraege; alles andere waere ein Konfigurationsfehler und bricht ab,
* statt still zu runden.
*/
function percentOf(cents: number, percent: number): number {
const raw = cents * percent;
if (raw % 100 !== 0) {
throw new Error(`Rabatt ergibt keinen glatten Cent-Betrag: ${cents} × ${percent} %`);
}
return raw / 100;
}
export const PHASE_LABEL: Record<PricePhase, string> = {
PREVIEW: "Vorschau",
EARLY_BIRD: "Early Bird",
REGULAR: "Regulär",
};