From 5060d150ea655d60037b914619136929ccdc12ae Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 7 Oct 2026 14:35:49 +0000 Subject: [PATCH] Auftragsbestaetigung: Dialog-Schritte 1 bis 3 (Projekt, Auftrag, Zahlung und Termine) Co-Authored-By: Claude Sonnet 5.5 --- src/components/auftraege/AuftragSchritte.jsx | 321 +++++++++++++++++++ src/components/auftraege/auftrag.css | 43 +++ 2 files changed, 364 insertions(+) create mode 100644 src/components/auftraege/AuftragSchritte.jsx diff --git a/src/components/auftraege/AuftragSchritte.jsx b/src/components/auftraege/AuftragSchritte.jsx new file mode 100644 index 0000000..41e1916 --- /dev/null +++ b/src/components/auftraege/AuftragSchritte.jsx @@ -0,0 +1,321 @@ +import { Badge } from '../ui' +import FormField from '../finance/FormField' +import { useWorkorders } from '../../hooks/useWorkorders' +import { euro } from '../../lib/financeFormat' +import { customerName, lineTotalCents } from '../../lib/financeView' +import { KUNDENART_OPTIONEN, kundenartLabel } from '../../lib/kundenFelder' +import { + AUFTRAG_LIMITS, + AUFTRAG_WEGE, + formularSummeCents, + kundenartWirksam, + neuePosition, + parseProzent, + parseTage, + schalteZeitquelle, + vorOrtHinweis, + waehleProjekt, + wechselArt, + zahlungsplanOptionen, + zahlungsplanVorschau, + zeitquelleOptionen, +} from '../../lib/auftragView' +import './auftrag.css' + +/** Meldung eines Feldes ohne FormField (Art, Positionen, Zahlungsplan, Zeitquelle). */ +function FeldFehler({ text }) { + return text ?
{text}
: null +} + +/** Schritt 1: Projekt, Titel und (falls am Kunden noch leer) die Kundenart. */ +export function SchrittProjekt({ form, onChange, fehler, kunde, konten, neueFassung = false }) { + const name = customerName(kunde) || 'Kunde' + return ( +
+

+ Kunde: {name} + {kunde?.email ? ` · ${kunde.email}` : ''} +

+ {neueFassung &&

Neue Fassung: Beim Senden ersetzt sie die bisherige Auftragsbestätigung.

} + + + + + onChange({ ...form, titel: e.target.value, titelGeaendert: true })} + /> + + {!kunde?.kundenart ? ( + + + + ) : ( +

Kundenart: {kundenartLabel(kunde.kundenart)}. Änderbar in der Übersicht des Kunden.

+ )} +
+ ) +} + +/** Schritt 2: Absprache (Datum, Weg), Art des Auftrags und Positionen bzw. Stundensatz. */ +export function SchrittAuftrag({ form, onChange, fehler, kunde }) { + const vorOrt = vorOrtHinweis(form.auftragWeg, kundenartWirksam(kunde, form.kundenart)) + const positionen = form.positionen + const setPosition = (uid, feld, wert) => + onChange({ ...form, positionen: positionen.map((p) => (p.uid === uid ? { ...p, [feld]: wert } : p)) }) + const entferne = (uid) => onChange({ ...form, positionen: positionen.filter((p) => p.uid !== uid) }) + const summe = formularSummeCents(form) + + return ( +
+
+ + onChange({ ...form, auftragVom: e.target.value })} /> + + + + +
+ {vorOrt &&

{vorOrt}

} + +
+ Art des Auftrags + + + +
+ + {form.art === 'festpreis' && ( +
+ Positionen + {positionen.map((p, i) => { + const n = i + 1 + const zeile = lineTotalCents(p) + return ( +
+
+ Beschreibung + setPosition(p.uid, 'description', e.target.value)} + /> +
+
+ Menge + setPosition(p.uid, 'quantity', e.target.value)} + /> +
+
+ Einzelpreis in € + setPosition(p.uid, 'price', e.target.value)} + /> +
+
+ Summe + {zeile === null ? '–' : euro(zeile)} +
+
+ +
+
+ ) + })} +
+ +
+
+ Summe + {euro(summe)} +
+

Gemäß § 19 UStG ohne Umsatzsteuer.

+ +
+ )} + + {form.art === 'aufwand' && ( +
+ + onChange({ ...form, stundensatz: e.target.value })} /> + + + onChange({ ...form, schaetzung: e.target.value })} + /> + +
+ )} +
+ ) +} + +/** Zeitquelle: Tickets des Kunden, deren Arbeitsblätter zum Auftrag zählen (höchstens 10). */ +function ZeitquelleAuswahl({ kundeId, form, onChange, projektTicketIds, fehler }) { + const { workorders, loading, error } = useWorkorders({ customerId: kundeId, limit: 100 }) + const optionen = zeitquelleOptionen(workorders, form.zeitquelle, projektTicketIds) + const voll = form.zeitquelle.length >= AUFTRAG_LIMITS.maxZeitquellen + return ( +
+ Zeiterfassung aus diesen Tickets +

Arbeitsblätter dieser Tickets zählen zum Auftrag (Projektkonto, Aufwandsrechnung). Höchstens 10.

+ {loading && optionen.length === 0 &&
Tickets werden geladen …
} + {error &&

Die Tickets konnten nicht geladen werden: {error}. Die bisherige Auswahl bleibt.

} + {!loading && !error && optionen.length === 0 &&

Dieser Kunde hat noch keine Tickets.

} + {optionen.length > 0 && ( +
+ {optionen.map((o) => ( + + ))} +
+ )} + +
+ ) +} + +/** Schritt 3: Zahlungsplan, Zahlungsziel, Termine und Zeitquelle. */ +export function SchrittZahlung({ form, onChange, fehler, kunde, konten }) { + const konto = (Array.isArray(konten) ? konten : []).find((k) => k.projektId && k.projektId === form.projektId) + const projektTicketIds = konto && Array.isArray(konto.tickets) ? konto.tickets.map((t) => t.id) : [] + const vorschau = zahlungsplanVorschau({ + art: form.zahlungsart, + prozent: parseProzent(form.prozent), + text: form.zahlungstext, + summeCents: formularSummeCents(form), + zielTage: parseTage(form.zahlungsziel), + }) + return ( +
+
+ Zahlungsplan + {zahlungsplanOptionen(form.art).map((o) => ( + + ))} + +
+ {form.zahlungsart === 'anzahlung' && ( + + onChange({ ...form, prozent: e.target.value })} /> + + )} + {form.zahlungsart === 'frei' && ( +
+ +