/** * View-Model der Kundenseite. * * Reine Funktion über typisierten Eingangsdaten, kein React, kein Store, kein * Zugriff auf `DATENSATZ`. Genau diese Grenze macht den Einbau billig: In der * Software ruft der Loader denselben Builder mit echten Daten auf, die * Screen-Komponente bleibt unverändert. */ import { inaktiveKunden, kundenEntwicklung, kundenNach, neueKunden, neueKundenVorperiode, topKunden, wiederkehrquote, zahlungsauffaellig, } from "../data/calculations"; import type { ChartPunkt, Datensatz, FilterState, Kunde, ZeitreihePunkt } from "../data/types"; import { bereit, leer, type VMState } from "./index"; export interface KundenDaten { readonly kundenGesamt: number; readonly kundenMitAuftrag: number; readonly neueKunden: number; readonly neueKundenVorher: number; readonly entwicklung: ZeitreihePunkt[]; readonly sparkline: number[]; readonly wiederkehrendProzent: number; readonly kundenwert: number; readonly inaktive: Kunde[]; readonly auffaellige: Kunde[]; readonly nachQuelle: ChartPunkt[]; readonly nachTyp: ChartPunkt[]; readonly topKunden: ChartPunkt[]; } export function baueKundenVM(daten: Datensatz, filter: FilterState): VMState { // Kein Kunde in der Auswahl heisst: Die Filter schneiden alles weg. // Das ist ein Leerzustand, kein Fehler. if (!daten.kunden.length) return leer(); const entwicklung = kundenEntwicklung(daten.kunden, filter.zeitraum); const mitAuftrag = daten.kunden.filter((k) => k.auftraegeGesamt > 0); return bereit({ kundenGesamt: daten.kunden.length, kundenMitAuftrag: mitAuftrag.length, neueKunden: neueKunden(daten.kunden, filter.zeitraum), neueKundenVorher: neueKundenVorperiode(daten.kunden, filter.zeitraum), entwicklung, sparkline: entwicklung.slice(-10).map((p) => p.value), wiederkehrendProzent: wiederkehrquote(daten.kunden), // Bezugsgrösse ist der Kunde mit Historie: Karteileichen würden den // Durchschnitt nach unten ziehen. kundenwert: mitAuftrag.length ? mitAuftrag.reduce((s, k) => s + k.umsatzGesamt, 0) / mitAuftrag.length : 0, inaktive: inaktiveKunden(daten.kunden), auffaellige: zahlungsauffaellig(daten.kunden), nachQuelle: kundenNach(daten.kunden, "quelle"), nachTyp: kundenNach(daten.kunden, "typ"), topKunden: topKunden(daten.kunden, 6), }); }