Files
omsorg/omsorgapp/src/modules/facilities/FacilityForm.jsx
T
Felix KemmlerandClaude Haiku 4.5 e1fbfadd17 Einrichtung: Konditionen und CRM-Status beim Anlegen direkt sichtbar machen
- Backend (omsorgCore): CreateFacilityRequest um Konditionen- und CRM-Status-Felder erweitert, identisch mit UpdateFacilityRequest
- FacilitiesController.Create: Gleiche Validierung wie Update (CrmStatus-Allowlist, FollowUp-Logik, Konditions-Wertebereich), aber ohne Transition-Check (kein Vorzustand beim Anlegen)
- Gemeinsame Validierungslogik in ValidateCrmStatusAndFollowUpAsync extrahiert, um Code-Duplizierung zu vermeiden
- omsorgCore/CLAUDE.md aktualisiert: Konditionen sind jetzt auch beim Anlegen setzbar
- API-Clients neu generiert (TypeScript und PHP)
- Frontend (omsorgapp): FacilityForm.jsx: includeCrmStatus-Flag entfernt, Konditionen und CRM-Status immer sichtbar
- CreateFacilityDialog.jsx: CRM-Status-Auswahl + FollowUpDaysDialog-Workflow (analog zu Edit-Dialog)
- EditFacilityDialog.jsx: vereinfacht (includeCrmStatus-Prop entfernt)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-08-10 22:33:27 +02:00

328 lines
12 KiB
React

import { useEffect, useState } from "react";
import OmsorgButton from "../../components/ui/OmsorgButton";
import DecimalInput from "../../components/ui/DecimalInput";
import { useValueListItems } from "../../app/useValueListItems";
import { parseGermanDecimal } from "../../utils/germanDecimal";
export const emptyFacilityForm = {
name: "",
crmStatus: "Lead",
facilityType: "",
website: "",
street: "",
postalCode: "",
city: "",
country: "",
billingStreet: "",
billingPostalCode: "",
billingCity: "",
billingCountry: "",
billingRate: "",
nightSurchargePercent: "",
saturdaySurchargePercent: "",
sundaySurchargePercent: "",
holidaySurchargePercent: "",
travelCostMode: "Pauschale",
travelCostRate: "",
travelCostPerKm: "",
mealAllowanceRate: "",
minimumHours: "",
billingInterval: "",
paymentTermDays: "",
individualAgreements: "",
};
export function facilityToFormValues(facility) {
return {
name: facility.name ?? "",
crmStatus: facility.crmStatus ?? "Lead",
facilityType: facility.facilityType ?? "",
website: facility.website ?? "",
street: facility.street ?? "",
postalCode: facility.postalCode ?? "",
city: facility.city ?? "",
country: facility.country ?? "",
billingStreet: facility.billingStreet ?? "",
billingPostalCode: facility.billingPostalCode ?? "",
billingCity: facility.billingCity ?? "",
billingCountry: facility.billingCountry ?? "",
billingRate: facility.billingRate ?? "",
nightSurchargePercent: facility.nightSurchargePercent ?? "",
saturdaySurchargePercent: facility.saturdaySurchargePercent ?? "",
sundaySurchargePercent: facility.sundaySurchargePercent ?? "",
holidaySurchargePercent: facility.holidaySurchargePercent ?? "",
travelCostMode: facility.travelCostMode ?? "Pauschale",
travelCostRate: facility.travelCostRate ?? "",
travelCostPerKm: facility.travelCostPerKm ?? "",
mealAllowanceRate: facility.mealAllowanceRate ?? "",
minimumHours: facility.minimumHours ?? "",
billingInterval: facility.billingInterval ?? "",
paymentTermDays: facility.paymentTermDays ?? "",
individualAgreements: facility.individualAgreements ?? "",
};
}
export function facilityFormToPayload(form, { followUpDays = null } = {}) {
return {
name: form.name.trim(),
crmStatus: form.crmStatus,
followUpDays: followUpDays,
facilityType: form.facilityType || null,
website: form.website.trim() || null,
street: form.street.trim() || null,
postalCode: form.postalCode.trim() || null,
city: form.city.trim() || null,
country: form.country.trim() || null,
billingStreet: form.billingStreet.trim() || null,
billingPostalCode: form.billingPostalCode.trim() || null,
billingCity: form.billingCity.trim() || null,
billingCountry: form.billingCountry.trim() || null,
billingRate: parseGermanDecimal(form.billingRate),
nightSurchargePercent: parseGermanDecimal(form.nightSurchargePercent),
saturdaySurchargePercent: parseGermanDecimal(form.saturdaySurchargePercent),
sundaySurchargePercent: parseGermanDecimal(form.sundaySurchargePercent),
holidaySurchargePercent: parseGermanDecimal(form.holidaySurchargePercent),
travelCostMode: form.travelCostMode || "Pauschale",
travelCostRate: parseGermanDecimal(form.travelCostRate),
travelCostPerKm: parseGermanDecimal(form.travelCostPerKm),
mealAllowanceRate: parseGermanDecimal(form.mealAllowanceRate),
minimumHours: parseGermanDecimal(form.minimumHours),
billingInterval: form.billingInterval || null,
paymentTermDays: form.paymentTermDays === "" ? null : Number(form.paymentTermDays),
individualAgreements: form.individualAgreements.trim() || null,
};
}
export default function FacilityForm({ form, onChange, currentCrmStatus = null }) {
const { items: crmStatusOptions } = useValueListItems("CrmStatus");
const { items: facilityTypes } = useValueListItems("FacilityType");
const { items: billingIntervals } = useValueListItems("BillingInterval");
const [transitions, setTransitions] = useState([]);
useEffect(() => {
let cancelled = false;
window.omsorg.valueLists.listTransitions("CrmStatus").then((result) => {
if (!cancelled) {
setTransitions(result.ok ? result.data ?? [] : []);
}
});
return () => {
cancelled = true;
};
}, []);
const currentCrmStatusItem = crmStatusOptions.find((option) => option.value === currentCrmStatus);
const selectableCrmStatusOptions = currentCrmStatusItem
? crmStatusOptions.filter(
(option) =>
option.id === currentCrmStatusItem.id ||
transitions.some((t) => t.fromItemId === currentCrmStatusItem.id && t.toItemId === option.id)
)
: crmStatusOptions;
function updateField(field) {
return (event) => onChange({ ...form, [field]: event.target.value });
}
function updateDecimalField(field) {
return (rawValue) => onChange({ ...form, [field]: rawValue });
}
return (
<div className="form-grid">
<label className="form-field">
<span>Name *</span>
<input value={form.name} onChange={updateField("name")} required />
</label>
<label className="form-field">
<span>CRM-Status</span>
<select value={form.crmStatus} onChange={updateField("crmStatus")}>
{selectableCrmStatusOptions.map((option) => (
<option key={option.id} value={option.value}>
{option.value}
</option>
))}
</select>
</label>
<label className="form-field">
<span>Art der Einrichtung</span>
<select value={form.facilityType} onChange={updateField("facilityType")}>
<option value=""> nicht angegeben </option>
{facilityTypes.map((option) => (
<option key={option.id} value={option.value}>
{option.value}
</option>
))}
</select>
</label>
<label className="form-field">
<span>Website</span>
<input type="url" value={form.website} onChange={updateField("website")} placeholder="https://…" />
</label>
<fieldset className="form-field form-field--fieldset">
<legend>Adresse</legend>
<label className="form-field">
<span>Straße und Hausnummer</span>
<input value={form.street} onChange={updateField("street")} />
</label>
<div className="form-row">
<label className="form-field">
<span>PLZ</span>
<input value={form.postalCode} onChange={updateField("postalCode")} />
</label>
<label className="form-field">
<span>Ort</span>
<input value={form.city} onChange={updateField("city")} />
</label>
</div>
<label className="form-field">
<span>Land</span>
<input value={form.country} onChange={updateField("country")} />
</label>
</fieldset>
<fieldset className="form-field form-field--fieldset">
<legend>Rechnungsadresse</legend>
<label className="form-field">
<span>Straße und Hausnummer</span>
<input value={form.billingStreet} onChange={updateField("billingStreet")} />
</label>
<div className="form-row">
<label className="form-field">
<span>PLZ</span>
<input value={form.billingPostalCode} onChange={updateField("billingPostalCode")} />
</label>
<label className="form-field">
<span>Ort</span>
<input value={form.billingCity} onChange={updateField("billingCity")} />
</label>
</div>
<label className="form-field">
<span>Land</span>
<input value={form.billingCountry} onChange={updateField("billingCountry")} />
</label>
</fieldset>
<fieldset className="form-field form-field--fieldset">
<legend>Konditionen</legend>
<label className="form-field">
<span>Verrechnungssatz (EUR/Std.)</span>
<DecimalInput value={form.billingRate} onChange={updateDecimalField("billingRate")} />
</label>
<div className="form-row">
<label className="form-field">
<span>Nachtzuschlag (%)</span>
<DecimalInput value={form.nightSurchargePercent} onChange={updateDecimalField("nightSurchargePercent")} />
</label>
<label className="form-field">
<span>Samstagszuschlag (%)</span>
<DecimalInput value={form.saturdaySurchargePercent} onChange={updateDecimalField("saturdaySurchargePercent")} />
</label>
</div>
<div className="form-row">
<label className="form-field">
<span>Sonntagszuschlag (%)</span>
<DecimalInput value={form.sundaySurchargePercent} onChange={updateDecimalField("sundaySurchargePercent")} />
</label>
<label className="form-field">
<span>Feiertagszuschlag (%)</span>
<DecimalInput value={form.holidaySurchargePercent} onChange={updateDecimalField("holidaySurchargePercent")} />
</label>
</div>
<div className="form-row">
<label className="form-field">
<span>Fahrtkosten Abrechnungsart</span>
<select value={form.travelCostMode} onChange={updateField("travelCostMode")}>
<option value="Pauschale">Pauschale je Einsatz</option>
<option value="ProKilometer">Pro Kilometer</option>
</select>
</label>
<label className="form-field">
<span>Mindeststunden je Einsatz</span>
<DecimalInput value={form.minimumHours} onChange={updateDecimalField("minimumHours")} />
</label>
</div>
<div className="form-row">
{form.travelCostMode === "ProKilometer" ? (
<label className="form-field">
<span>Fahrtkosten (EUR/km)</span>
<DecimalInput value={form.travelCostPerKm} onChange={updateDecimalField("travelCostPerKm")} />
</label>
) : (
<label className="form-field">
<span>Fahrtkosten (EUR, Pauschale je Einsatz)</span>
<DecimalInput value={form.travelCostRate} onChange={updateDecimalField("travelCostRate")} />
</label>
)}
</div>
<div className="form-row">
<label className="form-field">
<span>Verpflegungsmehraufwand (EUR, Pauschale je Einsatz)</span>
<DecimalInput value={form.mealAllowanceRate} onChange={updateDecimalField("mealAllowanceRate")} />
</label>
</div>
<div className="form-row">
<label className="form-field">
<span>Abrechnungsintervall</span>
<select value={form.billingInterval} onChange={updateField("billingInterval")}>
<option value=""> nicht angegeben </option>
{billingIntervals.map((option) => (
<option key={option.id} value={option.value}>
{option.value}
</option>
))}
</select>
</label>
<label className="form-field">
<span>Zahlungsziel (Tage)</span>
<input type="number" min="0" step="1" value={form.paymentTermDays} onChange={updateField("paymentTermDays")} />
</label>
</div>
<label className="form-field">
<span>Individuelle Vereinbarungen</span>
<input value={form.individualAgreements} onChange={updateField("individualAgreements")} />
</label>
</fieldset>
</div>
);
}
export function FormActions({ onCancel, isSaving, submitLabel }) {
return (
<div className="form-actions">
<OmsorgButton variant="secondary" type="button" onClick={onCancel} disabled={isSaving}>
Abbrechen
</OmsorgButton>
<OmsorgButton type="submit" disabled={isSaving}>
{isSaving ? "Speichert..." : submitLabel}
</OmsorgButton>
</div>
);
}