import { ORTE, type ArbeitszeitInput, type Ort } from '@/types/arbeitszeit'; import { parseHM, applyVertriebsregel, toDecimalHours } from './calc'; import { getPause } from './repo'; /** * Validiert und normalisiert einen JSON-Body zu einem ArbeitszeitInput. * Wirft Error mit deutscher Meldung bei ungültigen Daten. */ export function parseArbeitszeitInput(body: unknown): ArbeitszeitInput { const b = (body ?? {}) as Record; const datum = String(b.datum ?? ''); if (!/^\d{4}-\d{2}-\d{2}$/.test(datum)) throw new Error('Ungültiges Datum.'); const ort = String(b.ort ?? '') as Ort; if (!ORTE.includes(ort)) throw new Error('Ungültiger Ort.'); // Beginn/Ende sind optional. Wenn angegeben, müssen sie gültig sein; // entweder beide leer oder beide gesetzt. const beginn = String(b.beginn ?? ''); const ende = String(b.ende ?? ''); if (beginn !== '' && parseHM(beginn) === null) throw new Error('Ungültige Startzeit.'); if (ende !== '' && parseHM(ende) === null) throw new Error('Ungültige Endzeit.'); if ((beginn === '') !== (ende === '')) { throw new Error('Bitte Start- und Endzeit gemeinsam angeben oder beide leer lassen.'); } // Reise-Start/-Ende sind optional (analog Beginn/Ende). Wenn angegeben, // müssen sie gültig sein; entweder beide leer oder beide gesetzt. const reise_beginn = String(b.reise_beginn ?? ''); const reise_ende = String(b.reise_ende ?? ''); if (reise_beginn !== '' && parseHM(reise_beginn) === null) throw new Error('Ungültige Reise-Startzeit.'); if (reise_ende !== '' && parseHM(reise_ende) === null) throw new Error('Ungültige Reise-Endzeit.'); if ((reise_beginn === '') !== (reise_ende === '')) { throw new Error('Bitte Reise-Start und -Ende gemeinsam angeben oder beide leer lassen.'); } // Reisezeit wird zur Laufzeit aus den Reise-Zeiten berechnet (nicht gespeichert), // dient hier aber zur Kopplung der Vertriebsunterstützung. const reisezeit = toDecimalHours(reise_beginn, reise_ende); // Pause pro Eintrag (dezimale Stunden). Standard ist die globale Einstellung. const pause = toNumber(b.pause, getPause()); let vertriebsunterstuetzung = toNumber(b.vertriebsunterstuetzung, 2); if (pause < 0 || vertriebsunterstuetzung < 0) { throw new Error('Zeitwerte dürfen nicht negativ sein.'); } // Regel: Vertriebsunterstützung an die Reisezeit koppeln. // 0h ⇒ 0; <2h ⇒ = Reisezeit; ≥2h ⇒ bereitgestellter Wert (änderbar, default 2). vertriebsunterstuetzung = applyVertriebsregel(reisezeit, vertriebsunterstuetzung); const kommentar = String(b.kommentar ?? '').slice(0, 500); return { datum, ort, beginn, ende, reise_beginn, reise_ende, pause, vertriebsunterstuetzung, kommentar }; } function toNumber(value: unknown, fallback: number): number { if (value === '' || value === null || value === undefined) return fallback; const n = typeof value === 'number' ? value : parseFloat(String(value).replace(',', '.')); return isNaN(n) ? fallback : n; } export { toNumber };