Vertriebsunterstützung an Reisezeit koppeln
- Reisezeit 0 -> Vertriebsunterstützung 0 - Reisezeit < 2h -> Vertriebsunterstützung = Reisezeit (Feld gesperrt) - Reisezeit >= 2h -> Vertriebsunterstützung 2h, aber editierbar - Regel zentral in lib/calc.ts (applyVertriebsregel), im Formular interaktiv und in der API-Validierung erzwungen Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,14 @@
|
||||
|
||||
import { useState } from 'react';
|
||||
import { ORTE, ARBEITSZEIT_KUNDE_FIX, type ArbeitszeitEintrag } from '@/types/arbeitszeit';
|
||||
import { toDecimalHours, mehrarbeit, weekdayName, formatHours, hatZeiten } from '@/lib/calc';
|
||||
import {
|
||||
toDecimalHours,
|
||||
mehrarbeit,
|
||||
weekdayName,
|
||||
formatHours,
|
||||
hatZeiten,
|
||||
vertriebEditierbar,
|
||||
} from '@/lib/calc';
|
||||
import CustomSelect from './CustomSelect';
|
||||
import TimeInput from './TimeInput';
|
||||
|
||||
@@ -25,6 +32,11 @@ function toNum(raw: string): number {
|
||||
return isNaN(n) ? 0 : n;
|
||||
}
|
||||
|
||||
// Zahl als deutscher Dezimalstring (Komma)
|
||||
function numToStr(n: number): string {
|
||||
return String(n).replace('.', ',');
|
||||
}
|
||||
|
||||
export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }: Props) {
|
||||
// Diese Komponente wird beim Wechsel von editEntry über `key` neu gemountet,
|
||||
// daher genügt eine einmalige Initialisierung aus editEntry (kein Sync-Effekt nötig).
|
||||
@@ -32,8 +44,8 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }:
|
||||
const [ort, setOrt] = useState<string>(editEntry?.ort ?? ORTE[0]);
|
||||
const [beginn, setBeginn] = useState(editEntry?.beginn ?? '');
|
||||
const [ende, setEnde] = useState(editEntry?.ende ?? '');
|
||||
const [reisezeit, setReisezeit] = useState(editEntry ? String(editEntry.reisezeit) : '0');
|
||||
const [vertrieb, setVertrieb] = useState(editEntry ? String(editEntry.vertriebsunterstuetzung) : '2');
|
||||
const [reisezeit, setReisezeit] = useState(editEntry ? numToStr(editEntry.reisezeit) : '0');
|
||||
const [vertrieb, setVertrieb] = useState(editEntry ? numToStr(editEntry.vertriebsunterstuetzung) : '0');
|
||||
const [kommentar, setKommentar] = useState(editEntry?.kommentar ?? '');
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [error, setError] = useState('');
|
||||
@@ -42,6 +54,19 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }:
|
||||
const zeiten = hatZeiten(beginn, ende);
|
||||
const arbeitszeit = toDecimalHours(beginn, ende);
|
||||
const mehr = mehrarbeit(arbeitszeit, pause);
|
||||
const vertriebFrei = vertriebEditierbar(toNum(reisezeit));
|
||||
|
||||
// Vertriebsunterstützung an die Reisezeit koppeln (siehe applyVertriebsregel):
|
||||
// =0 ⇒ 0, <2 ⇒ = Reisezeit, ≥2 ⇒ 2 beim Überschreiten der Schwelle, danach frei änderbar.
|
||||
function handleReisezeit(raw: string) {
|
||||
const alt = toNum(reisezeit);
|
||||
const neu = toNum(raw);
|
||||
setReisezeit(raw);
|
||||
if (neu <= 0) setVertrieb('0');
|
||||
else if (neu < 2) setVertrieb(numToStr(neu));
|
||||
else if (alt < 2) setVertrieb('2'); // Schwelle 2h überschritten → Standard 2h
|
||||
// neu ≥ 2 und alt ≥ 2: bestehenden (ggf. geänderten) Wert beibehalten
|
||||
}
|
||||
|
||||
async function handleSubmit(e: React.FormEvent) {
|
||||
e.preventDefault();
|
||||
@@ -145,7 +170,7 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }:
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
value={reisezeit}
|
||||
onChange={(e) => setReisezeit(e.target.value)}
|
||||
onChange={(e) => handleReisezeit(e.target.value)}
|
||||
className={numCls}
|
||||
/>
|
||||
</div>
|
||||
@@ -156,7 +181,9 @@ export default function ArbeitszeitForm({ editEntry, pause, onSaved, onCancel }:
|
||||
inputMode="decimal"
|
||||
value={vertrieb}
|
||||
onChange={(e) => setVertrieb(e.target.value)}
|
||||
className={numCls}
|
||||
disabled={!vertriebFrei}
|
||||
title={vertriebFrei ? undefined : 'Wird automatisch aus der Reisezeit berechnet (< 2 h)'}
|
||||
className={`${numCls} disabled:bg-gray-100 disabled:text-gray-500`}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+17
@@ -43,6 +43,23 @@ export function hatZeiten(beginn: string, ende: string): boolean {
|
||||
return parseHM(beginn) !== null && parseHM(ende) !== null;
|
||||
}
|
||||
|
||||
/** True, wenn die Vertriebsunterstützung bei dieser Reisezeit frei änderbar ist (Reisezeit ≥ 2h). */
|
||||
export function vertriebEditierbar(reisezeit: number): boolean {
|
||||
return reisezeit >= 2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Vertriebsunterstützung an die Reisezeit koppeln:
|
||||
* Reisezeit = 0 → 0
|
||||
* 0 < Reisezeit < 2 → = Reisezeit
|
||||
* Reisezeit ≥ 2 → vorhandener Wert (änderbar), Standard 2
|
||||
*/
|
||||
export function applyVertriebsregel(reisezeit: number, vorhanden: number): number {
|
||||
if (reisezeit <= 0) return 0;
|
||||
if (reisezeit < 2) return reisezeit;
|
||||
return vorhanden;
|
||||
}
|
||||
|
||||
/** Deutscher Wochentagsname für ein "YYYY-MM-DD"-Datum, oder "" bei ungültig. */
|
||||
export function weekdayName(datum: string): string {
|
||||
const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(datum ?? '');
|
||||
|
||||
+5
-2
@@ -1,5 +1,5 @@
|
||||
import { ORTE, type ArbeitszeitInput, type Ort } from '@/types/arbeitszeit';
|
||||
import { parseHM } from './calc';
|
||||
import { parseHM, applyVertriebsregel } from './calc';
|
||||
|
||||
/**
|
||||
* Validiert und normalisiert einen JSON-Body zu einem ArbeitszeitInput.
|
||||
@@ -25,10 +25,13 @@ export function parseArbeitszeitInput(body: unknown): ArbeitszeitInput {
|
||||
}
|
||||
|
||||
const reisezeit = toNumber(b.reisezeit, 0);
|
||||
const vertriebsunterstuetzung = toNumber(b.vertriebsunterstuetzung, 2);
|
||||
let vertriebsunterstuetzung = toNumber(b.vertriebsunterstuetzung, 2);
|
||||
if (reisezeit < 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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user