/** * Deterministischer Dummy-Datensatz für einen SHK-/Handwerksbetrieb. * * Bewusst deterministisch (fester Seed): Die Zahlen bleiben über Reloads, * Screenshots und Abstimmungsrunden identisch. Für die echte Software wird * diese Datei durch Entity-Builder-Queries ersetzt — das Interface `Datensatz` * bleibt gleich. * * Betriebsprofil (fiktiv): * Meister Krause GmbH · Heizung · Sanitär · Klima · PV · Elektro * 24 Beschäftigte, ca. 3,4 Mio. € Jahresumsatz, Raum Rhein-Main */ import { AUFTRAG_STATUS_LABEL, GEWERKE, FOERDERFAEHIGE_KATEGORIEN, KATEGORIEN, type Auftrag, type AuftragStatus, type Datensatz, type Gewerk, type Kunde, type KundenQuelle, type KundenTyp, type Mitarbeiter, type MitarbeiterRolle, type Rechnung, type Zeiteintrag, } from "./types"; import { HEUTE, addTage, toISO } from "../utils/dateUtils"; /* ------------------------------------------------------ Zufall mit Seed */ /** mulberry32 — kleiner, schneller PRNG mit reproduzierbarer Sequenz. */ function prng(seed: number) { let a = seed; return function random(): number { a |= 0; a = (a + 0x6d2b79f5) | 0; let t = Math.imul(a ^ (a >>> 15), 1 | a); t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t; return ((t ^ (t >>> 14)) >>> 0) / 4294967296; }; } const rnd = prng(20260730); const zahl = (min: number, max: number) => Math.floor(rnd() * (max - min + 1)) + min; const kommazahl = (min: number, max: number) => rnd() * (max - min) + min; const aus = (liste: readonly T[]): T => liste[Math.floor(rnd() * liste.length)]; function gewichtet(optionen: { wert: T; gewicht: number }[]): T { const gesamt = optionen.reduce((s, o) => s + o.gewicht, 0); let r = rnd() * gesamt; for (const o of optionen) { r -= o.gewicht; if (r <= 0) return o.wert; } return optionen[optionen.length - 1].wert; } /** Zufälliges Datum zwischen zwei Zeitpunkten. */ function datumZwischen(von: Date, bis: Date): Date { return new Date(von.getTime() + rnd() * (bis.getTime() - von.getTime())); } /* ---------------------------------------------------------- Mitarbeiter */ const VORNAMEN = [ "Thomas", "Michael", "Andreas", "Stefan", "Christian", "Markus", "Peter", "Martin", "Daniel", "Kevin", "Sebastian", "Tobias", "Jens", "Sandra", "Maike", "Nicole", "Julia", "Patrick", "Dennis", "Marco", "Sven", "Lukas", "Fabian", "Mehmet", ]; const NACHNAMEN = [ "Krause", "Müller", "Schmidt", "Schneider", "Fischer", "Weber", "Meyer", "Wagner", "Becker", "Schulz", "Hoffmann", "Schäfer", "Koch", "Richter", "Klein", "Wolf", "Neumann", "Zimmermann", "Braun", "Hartmann", "Lange", "Werner", "Yilmaz", "Peters", ]; function kuerzelAus(name: string): string { const [v, n] = name.split(" "); return `${v[0]}${n[0]}`.toUpperCase(); } function baueMitarbeiter(): Mitarbeiter[] { const rollen: MitarbeiterRolle[] = [ "Meister", "Meister", "Meister", "Monteur", "Monteur", "Monteur", "Monteur", "Monteur", "Monteur", "Monteur", "Monteur", "Monteur", "Monteur", "Monteur", "Monteur", "Monteur", "Monteur", "Auszubildender", "Auszubildender", "Auszubildender", "Büro", "Büro", "Büro", "Büro", ]; const benutzt = new Set(); return rollen.map((rolle, i) => { let name = `${VORNAMEN[i % VORNAMEN.length]} ${NACHNAMEN[(i * 7) % NACHNAMEN.length]}`; while (benutzt.has(name)) name = `${aus(VORNAMEN)} ${aus(NACHNAMEN)}`; benutzt.add(name); return { id: `ma-${i + 1}`, name, kuerzel: kuerzelAus(name), rolle, gewerk: GEWERKE[i % GEWERKE.length], // Zwei Langzeitabwesende, damit "aktiv" vs. "gesamt" sichtbar wird. status: i === 12 || i === 19 ? "inaktiv" : "aktiv", wochenstunden: rolle === "Auszubildender" ? 35 : rolle === "Büro" ? 38 : 40, eintrittsdatum: toISO(addTage(HEUTE, -zahl(90, 4200))), } satisfies Mitarbeiter; }); } /* ---------------------------------------------------------------- Kunden */ const ORTE: { ort: string; plz: string; region: string }[] = [ { ort: "Wiesbaden", plz: "65189", region: "Hessen" }, { ort: "Mainz", plz: "55116", region: "Rheinland-Pfalz" }, { ort: "Frankfurt am Main", plz: "60313", region: "Hessen" }, { ort: "Rüsselsheim", plz: "65428", region: "Hessen" }, { ort: "Hofheim am Taunus", plz: "65719", region: "Hessen" }, { ort: "Bad Homburg", plz: "61348", region: "Hessen" }, { ort: "Darmstadt", plz: "64283", region: "Hessen" }, { ort: "Ingelheim", plz: "55218", region: "Rheinland-Pfalz" }, { ort: "Bingen", plz: "55411", region: "Rheinland-Pfalz" }, { ort: "Aschaffenburg", plz: "63739", region: "Bayern" }, ]; const GEWERBE_NAMEN = [ "Autohaus Reinhardt", "Bäckerei Lindner", "Hotel Rheinblick", "Praxis Dr. Alber", "Logistik Weber KG", "Metallbau Süd GmbH", "Kanzlei Hoffmann & Partner", "Sporthalle Taunusstein", "Kita Sonnenschein", "Getränke Hauser", "Elektro Nord GmbH", "Weingut Steinberg", "Fahrschule Brandt", "Pflegeheim St. Anna", ]; const VERWALTUNG_NAMEN = [ "Rhein-Main Immobilien GmbH", "Hausverwaltung Kessler", "WEG Parkstraße 12", "Wohnbau Wiesbaden eG", "Immobilien Service Taunus", ]; const QUELLEN: { wert: KundenQuelle; gewicht: number }[] = [ { wert: "Empfehlung", gewicht: 30 }, { wert: "Bestandskunde", gewicht: 24 }, { wert: "Google", gewicht: 18 }, { wert: "Website", gewicht: 14 }, { wert: "Partnerbetrieb", gewicht: 8 }, { wert: "Werbung", gewicht: 6 }, ]; function baueKunden(anzahl: number): Kunde[] { const kunden: Kunde[] = []; for (let i = 0; i < anzahl; i++) { const typ = gewichtet([ { wert: "Privat", gewicht: 68 }, { wert: "Gewerbe", gewicht: 22 }, { wert: "Hausverwaltung", gewicht: 10 }, ]); const ort = aus(ORTE); const name = typ === "Privat" ? `Familie ${NACHNAMEN[(i * 5) % NACHNAMEN.length]}` : typ === "Gewerbe" ? GEWERBE_NAMEN[i % GEWERBE_NAMEN.length] : VERWALTUNG_NAMEN[i % VERWALTUNG_NAMEN.length]; kunden.push({ id: `kd-${i + 1}`, name: typ === "Privat" ? `${name} · ${ort.ort}` : name, typ, quelle: gewichtet(QUELLEN), region: ort.region, plz: ort.plz, ort: ort.ort, angelegtAm: toISO(datumZwischen(addTage(HEUTE, -900), addTage(HEUTE, -2))), letzterAuftragAm: null, umsatzGesamt: 0, auftraegeGesamt: 0, // Die meisten zahlen nahe am Zahlungsziel; ein kleiner Teil schleppt. // Gleichverteilt wäre jeder zweite Kunde auffällig — das wäre kein Betrieb, // sondern ein Inkassofall. zahlungsmoralTage: gewichtet([ { wert: zahl(5, 14), gewicht: 42 }, { wert: zahl(15, 24), gewicht: 33 }, { wert: zahl(25, 34), gewicht: 15 }, { wert: zahl(35, 52), gewicht: 7 }, { wert: zahl(53, 78), gewicht: 3 }, ]), }); } return kunden; } /* -------------------------------------------------------------- Aufträge */ /** Realistische Auftragswerte je Leistungsart (netto, in €). */ const WERT_SPANNEN: Record = { "Wärmepumpe": [17_500, 34_000], "Gastherme": [5_800, 12_400], "Heizkörper": [1_200, 6_500], "Wartung Heizung": [150, 420], "Bad-Sanierung": [9_500, 27_000], "Rohrleitungen": [900, 7_800], "Armaturen": [250, 2_400], "Notdienst": [180, 950], "Splitgerät": [2_400, 8_900], "Lüftungsanlage": [6_500, 21_000], "Kanalreinigung": [420, 1_800], "Wartung Klima": [180, 480], "PV-Anlage": [11_500, 29_500], "Batteriespeicher": [6_800, 14_500], "Wallbox": [1_150, 3_400], "Wartung PV": [190, 460], "Elektroinstallation": [1_800, 12_000], "Zählerschrank": [1_400, 4_200], "Smart Home": [2_200, 9_800], "E-Check": [140, 390], }; /** Bezugsgröße der Förderquote — zentral in types.ts definiert. */ const FOERDERFAEHIG = FOERDERFAEHIGE_KATEGORIEN; /** * Ergebnis eines Vorgangs, abhängig von seinem Alter. * * Die Verteilung bildet einen realistischen Trichter ab: * von 100 Anfragen werden rund 55 zum Angebot und rund 24 zum Auftrag. * Verlorene Vorgänge merken sich, auf welcher Stufe sie abgesprungen sind — * sonst zeigt der Trichter oben zu wenig und unten zu viel. */ function ergebnisFuerAlter( tageAlt: number, /** Große Vorhaben werden angeboten und oft verglichen — kleine kaum. */ grossauftrag: boolean ): { status: AuftragStatus; verlorenAb: AuftragStatus | null; } { type Ergebnis = { status: AuftragStatus; verlorenAb: AuftragStatus | null }; const verloren = (stufe: AuftragStatus): Ergebnis => ({ status: "verloren", verlorenAb: stufe }); const offen = (stufe: AuftragStatus): Ergebnis => ({ status: stufe, verlorenAb: null }); /** * Verlustneigung: Wartungs- und Notdienstaufträge werden praktisch immer * ausgeführt, große Anlagen dagegen ausgeschrieben und verglichen. * Ein pauschaler Wert über alle Auftragsarten würde die Angebotsquote * unrealistisch schönrechnen. */ const verlustFaktor = grossauftrag ? 2.1 : 0.28; const v = (gewicht: number) => gewicht * verlustFaktor; // Frisch: steckt noch vorne im Trichter. if (tageAlt < 12) { return gewichtet([ { wert: offen("anfrage"), gewicht: 40 }, { wert: offen("aufmass"), gewicht: 24 }, { wert: offen("angebot"), gewicht: 20 }, { wert: offen("beauftragt"), gewicht: 9 }, { wert: verloren("anfrage"), gewicht: v(7) }, ]); } // In Bearbeitung: Entscheidungen fallen. if (tageAlt < 45) { return gewichtet([ { wert: offen("aufmass"), gewicht: 7 }, { wert: offen("angebot"), gewicht: 20 }, { wert: offen("beauftragt"), gewicht: 13 }, { wert: offen("in_arbeit"), gewicht: 17 }, { wert: offen("abgeschlossen"), gewicht: 8 }, { wert: verloren("anfrage"), gewicht: v(9) }, { wert: verloren("aufmass"), gewicht: v(7) }, { wert: verloren("angebot"), gewicht: v(34) }, ]); } // Älter als ein Quartal: fast alles ist entschieden. if (tageAlt < 120) { return gewichtet([ { wert: offen("in_arbeit"), gewicht: 9 }, { wert: offen("abgeschlossen"), gewicht: 50 }, { wert: verloren("anfrage"), gewicht: v(8) }, { wert: verloren("aufmass"), gewicht: v(7) }, { wert: verloren("angebot"), gewicht: v(46) }, ]); } // Über vier Monate alt: entweder fertig oder verloren. Ein Vorgang, der // seit zwei Jahren "in Arbeit" ist, wäre ein Datenfehler, kein Auftrag. return gewichtet([ { wert: offen("abgeschlossen"), gewicht: 60 }, { wert: verloren("anfrage"), gewicht: v(9) }, { wert: verloren("aufmass"), gewicht: v(8) }, { wert: verloren("angebot"), gewicht: v(54) }, ]); } /** * Leistungsart innerhalb eines Gewerks. * Wartung, Notdienst und E-Check sind im Alltag die häufigsten Aufträge — * gleichverteilt zu ziehen würde den Durchschnittswert massiv verzerren. */ function kategorieFuer(gewerk: Gewerk): string { const liste = KATEGORIEN[gewerk]; const kleinteilig = liste.filter((k) => /Wartung|Notdienst|E-Check|Armaturen/.test(k)); const gross = liste.filter((k) => !kleinteilig.includes(k)); if (!kleinteilig.length) return aus(liste); return rnd() < 0.42 ? aus(kleinteilig) : aus(gross); } /** * Verteilt Auftragsdaten über die letzten 24 Monate. * * Zwei Effekte sind absichtlich eingebaut, weil ein gleichverteilter * Datensatz in jedem Zeitreihen-Chart unnatürlich flach aussieht: * - moderates Wachstum von rund 12 % gegenüber dem Vorjahr * - Saisonalität: Heizung, Wärmepumpe und Lüftung ziehen von September * bis Februar an, der Hochsommer ist schwächer * Umgesetzt über Verwerfungsmethode gegen eine Dichtefunktion. */ const SAISON_INDEX = [ // Jan Feb Mär Apr Mai Jun Jul Aug Sep Okt Nov Dez 1.12, 1.06, 1.0, 0.94, 0.88, 0.84, 0.82, 0.9, 1.1, 1.22, 1.18, 0.98, ]; function ziehAuftragsalter(): number { const maxDichte = 1.12 * 1.22; for (let versuch = 0; versuch < 40; versuch++) { const tage = zahl(0, 719); const datum = addTage(HEUTE, -tage); const wachstum = 1 + 0.12 * (1 - tage / 720); const dichte = wachstum * SAISON_INDEX[datum.getMonth()]; if (rnd() <= dichte / maxDichte) return tage; } return zahl(0, 719); } function baueAuftraege(kunden: Kunde[], mitarbeiter: Mitarbeiter[], anzahl: number): Auftrag[] { const ausfuehrende = mitarbeiter.filter((m) => m.rolle === "Monteur" || m.rolle === "Meister"); const auftraege: Auftrag[] = []; for (let i = 0; i < anzahl; i++) { const tageAlt = ziehAuftragsalter(); const angelegt = addTage(HEUTE, -tageAlt); const gewerk = gewichtet([ { wert: "Heizung", gewicht: 34 }, { wert: "Sanitär", gewicht: 24 }, { wert: "Photovoltaik", gewicht: 20 }, { wert: "Klima & Lüftung", gewicht: 12 }, { wert: "Elektro", gewicht: 10 }, ]); const kategorie = kategorieFuer(gewerk); const [minWert, maxWert] = WERT_SPANNEN[kategorie] ?? [500, 5_000]; const wert = Math.round(kommazahl(minWert, maxWert) / 10) * 10; // Ab etwa 6.000 € wird im Handwerk regelmäßig schriftlich angeboten und // verglichen; darunter läuft der Auftrag meist direkt. const ueberAngebot = wert >= 6_000; const { status, verlorenAb } = ergebnisFuerAlter(tageAlt, ueberAngebot); const kunde = aus(kunden); const mitarbeiterId = aus(ausfuehrende).id; const laufzeitTage = wert > 12_000 ? zahl(21, 75) : zahl(3, 28); const startAm = status === "in_arbeit" || status === "abgeschlossen" ? toISO(addTage(angelegt, zahl(5, 30))) : null; const abgeschlossenAm = status === "abgeschlossen" ? toISO(addTage(angelegt, laufzeitTage + zahl(0, 20))) : null; const foerderfaehig = FOERDERFAEHIG.has(kategorie); // Ein Förderbescheid dauert Wochen, nicht Jahre: Nur bei jungen Vorgängen // ist "beantragt" plausibel, ältere sind längst entschieden. const foerderung: Auftrag["foerderung"] = !foerderfaehig ? "keine" : tageAlt <= 100 ? gewichtet([ { wert: "beantragt" as const, gewicht: 46 }, { wert: "bewilligt" as const, gewicht: 39 }, { wert: "keine" as const, gewicht: 15 }, ]) : gewichtet([ { wert: "bewilligt" as const, gewicht: 84 }, { wert: "keine" as const, gewicht: 16 }, ]); auftraege.push({ id: `au-${i + 1}`, nummer: `A-2026-${String(1000 + i).slice(-4)}`, kundeId: kunde.id, mitarbeiterId, gewerk, kategorie, titel: `${kategorie} · ${kunde.ort}`, status, prioritaet: gewichtet([ { wert: "normal" as const, gewicht: 62 }, { wert: "hoch" as const, gewicht: 22 }, { wert: "niedrig" as const, gewicht: 16 }, ]), wert, margeProzent: Math.round(kommazahl(16, 38) * 10) / 10, angelegtAm: toISO(angelegt), startAm, abgeschlossenAm, faelligAm: toISO(addTage(angelegt, laufzeitTage)), foerderung, foerderbetrag: foerderung === "keine" ? 0 : Math.round((wert * kommazahl(0.2, 0.4)) / 50) * 50, verlorenAb, ueberAngebot, }); } return auftraege.sort((a, b) => (a.angelegtAm < b.angelegtAm ? 1 : -1)); } /* ------------------------------------------------------------ Rechnungen */ function baueRechnungen(auftraege: Auftrag[]): Rechnung[] { const rechnungen: Rechnung[] = []; let nr = 0; for (const auftrag of auftraege) { // Rechnung entsteht erst ab Beauftragung (Anzahlung) bzw. Abschluss. if (!["beauftragt", "in_arbeit", "abgeschlossen"].includes(auftrag.status)) continue; const positionen: { betrag: number; basis: string }[] = []; if (auftrag.wert > 8_000 && auftrag.startAm) { // Große Vorhaben: 30 % Anzahlung bei Auftragserteilung. positionen.push({ betrag: Math.round(auftrag.wert * 0.3), basis: auftrag.startAm }); } if (auftrag.status === "abgeschlossen" && auftrag.abgeschlossenAm) { const bereits = positionen.reduce((s, p) => s + p.betrag, 0); positionen.push({ betrag: auftrag.wert - bereits, basis: auftrag.abgeschlossenAm }); } if (positionen.length === 0 && auftrag.status !== "beauftragt" && auftrag.startAm) { positionen.push({ betrag: Math.round(auftrag.wert * 0.5), basis: auftrag.startAm }); } for (const position of positionen) { nr += 1; const gestellt = new Date(position.basis); const zahlungsziel = 21; const faellig = addTage(gestellt, zahlungsziel); const ueberfaelligTage = Math.floor((HEUTE.getTime() - faellig.getTime()) / 86_400_000); let status: Rechnung["status"]; let bezahltAm: string | null = null; if (gestellt > HEUTE) { // Rechnung existiert noch nicht — z. B. Schlussrechnung eines laufenden Auftrags. status = "entwurf"; } else if (ueberfaelligTage < 0) { // Noch innerhalb des Zahlungsziels; ein Teil zahlt sofort. status = rnd() < 0.08 ? "bezahlt" : "gestellt"; if (status === "bezahlt") bezahltAm = toISO(addTage(gestellt, zahl(2, zahlungsziel))); } else if (ueberfaelligTage <= 18) { // Kurz über dem Ziel: das ist Alltag, noch kein Mahnfall. if (rnd() < 0.52) { status = "bezahlt"; bezahltAm = toISO(addTage(faellig, zahl(-6, ueberfaelligTage))); } else { status = "gestellt"; } } else if (ueberfaelligTage <= 60) { if (rnd() < 0.84) { status = "bezahlt"; bezahltAm = toISO(addTage(faellig, zahl(-8, Math.min(ueberfaelligTage, 34)))); } else { status = "ueberfaellig"; } } else if (ueberfaelligTage <= 120) { // Mahnstufe erreicht — der Großteil zahlt spätestens jetzt. if (rnd() < 0.94) { status = "bezahlt"; bezahltAm = toISO(addTage(faellig, zahl(-8, 70))); } else { status = "ueberfaellig"; } } else if (ueberfaelligTage <= 210) { // Letzte Mahnstufe: hier entscheidet sich Zahlung oder Ausfall. if (rnd() < 0.992) { status = "bezahlt"; bezahltAm = toISO(addTage(faellig, zahl(-8, 120))); } else { status = "ueberfaellig"; } } else { // Älter als sieben Monate: eingebracht oder ausgebucht — in beiden // Fällen steht die Forderung nicht mehr in der offenen Liste. status = "bezahlt"; bezahltAm = toISO(addTage(faellig, zahl(-8, 150))); } rechnungen.push({ id: `re-${nr}`, nummer: `R-2026-${String(2000 + nr).slice(-4)}`, auftragId: auftrag.id, kundeId: auftrag.kundeId, richtung: "ausgang", betrag: position.betrag, status, gestelltAm: toISO(gestellt), faelligAm: toISO(faellig), bezahltAm, }); } } return rechnungen; } /* ---------------------------------------------------------- Zeiterfassung */ function baueZeiten(mitarbeiter: Mitarbeiter[], auftraege: Auftrag[]): Zeiteintrag[] { const zeiten: Zeiteintrag[] = []; const laufend = auftraege.filter((a) => a.status === "in_arbeit" || a.status === "abgeschlossen"); const gewerblich = mitarbeiter.filter( (m) => m.status === "aktiv" && m.rolle !== "Büro" ); let nr = 0; // Letzte 12 Wochen, Montag bis Freitag. for (let tagOffset = 84; tagOffset >= 0; tagOffset--) { const tag = addTage(HEUTE, -tagOffset); const wochentag = tag.getDay(); if (wochentag === 0 || wochentag === 6) continue; for (const person of gewerblich) { const abwesend = rnd() < 0.09; if (abwesend) { nr += 1; zeiten.push({ id: `ze-${nr}`, mitarbeiterId: person.id, auftragId: null, datum: toISO(tag), stunden: 8, art: "abwesenheit", abwesenheitsgrund: gewichtet([ { wert: "urlaub" as const, gewicht: 55 }, { wert: "krank" as const, gewicht: 33 }, { wert: "fortbildung" as const, gewicht: 12 }, ]), }); continue; } nr += 1; const auftrag = laufend.length ? aus(laufend) : null; zeiten.push({ id: `ze-${nr}`, mitarbeiterId: person.id, auftragId: auftrag?.id ?? null, datum: toISO(tag), // Produktive Stunden schwanken; nicht jede Stunde ist verrechenbar. stunden: Math.round(kommazahl(4.5, 9.5) * 2) / 2, art: "arbeit", }); } } return zeiten; } /* -------------------------------------------------------- Aggregation */ function ergaenzeKundenkennzahlen( kunden: Kunde[], auftraege: Auftrag[], rechnungen: Rechnung[] ): Kunde[] { const umsatzProKunde = new Map(); for (const r of rechnungen) { if (r.status !== "bezahlt") continue; umsatzProKunde.set(r.kundeId, (umsatzProKunde.get(r.kundeId) ?? 0) + r.betrag); } const auftraegeProKunde = new Map(); for (const a of auftraege) { const liste = auftraegeProKunde.get(a.kundeId) ?? []; liste.push(a); auftraegeProKunde.set(a.kundeId, liste); } return kunden.map((kunde) => { const eigene = auftraegeProKunde.get(kunde.id) ?? []; const letzter = eigene .map((a) => a.angelegtAm) .sort() .at(-1); return { ...kunde, umsatzGesamt: umsatzProKunde.get(kunde.id) ?? 0, auftraegeGesamt: eigene.filter((a) => a.status !== "verloren").length, letzterAuftragAm: letzter ?? null, }; }); } /* --------------------------------------------------------------- Export */ function baueDatensatz(): Datensatz { const mitarbeiter = baueMitarbeiter(); const rohkunden = baueKunden(420); const auftraege = baueAuftraege(rohkunden, mitarbeiter, 1900); const rechnungen = baueRechnungen(auftraege); const kunden = ergaenzeKundenkennzahlen(rohkunden, auftraege, rechnungen); const zeiten = baueZeiten(mitarbeiter, auftraege); return { mitarbeiter, kunden, auftraege, rechnungen, zeiten }; } /** Einmal erzeugter, unveränderlicher Datensatz für die gesamte App. */ export const DATENSATZ: Datensatz = baueDatensatz(); /** Nur für Debug-Ausgaben / die Komponenten-Galerie. */ export const DATENSATZ_INFO = { mitarbeiter: DATENSATZ.mitarbeiter.length, kunden: DATENSATZ.kunden.length, auftraege: DATENSATZ.auftraege.length, rechnungen: DATENSATZ.rechnungen.length, zeiteintraege: DATENSATZ.zeiten.length, statusLabels: Object.values(AUFTRAG_STATUS_LABEL), };