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>
This commit is contained in:
Felix Kemmler
2026-08-10 22:33:27 +02:00
co-authored by Claude Haiku 4.5
parent 52fbec3214
commit e1fbfadd17
9 changed files with 842 additions and 90 deletions
@@ -1,7 +1,9 @@
import { useState } from "react";
import FacilityForm, { emptyFacilityForm, facilityFormToPayload, FormActions } from "./FacilityForm";
import FollowUpDaysDialog from "./FollowUpDaysDialog";
import ModalPortal from "../../components/ui/ModalPortal";
import { useValueListItems } from "../../app/useValueListItems";
function errorMessage(result) {
if (result.status === 403) {
@@ -14,22 +16,17 @@ function errorMessage(result) {
}
export default function CreateFacilityDialog({ onClose, onCreated }) {
const { items: crmStatusOptions } = useValueListItems("CrmStatus");
const [form, setForm] = useState(emptyFacilityForm);
const [isSaving, setIsSaving] = useState(false);
const [error, setError] = useState(null);
const [isFollowUpDialogOpen, setIsFollowUpDialogOpen] = useState(false);
async function handleSubmit(event) {
event.preventDefault();
if (!form.name.trim()) {
setError("Name ist erforderlich.");
return;
}
async function save(followUpDays) {
setIsSaving(true);
setError(null);
const result = await window.omsorg.facilities.create(facilityFormToPayload(form));
const result = await window.omsorg.facilities.create(facilityFormToPayload(form, { followUpDays }));
setIsSaving(false);
@@ -41,6 +38,24 @@ export default function CreateFacilityDialog({ onClose, onCreated }) {
onCreated(result.data);
}
async function handleSubmit(event) {
event.preventDefault();
if (!form.name.trim()) {
setError("Name ist erforderlich.");
return;
}
const selectedCrmStatus = crmStatusOptions.find((option) => option.value === form.crmStatus);
const entersFollowUpTriggerStatus = Boolean(selectedCrmStatus?.triggersFollowUp) && form.crmStatus !== "Lead";
if (entersFollowUpTriggerStatus) {
setIsFollowUpDialogOpen(true);
return;
}
await save(null);
}
return (
<ModalPortal>
<div className="modal-overlay" role="dialog" aria-modal="true" aria-label="Neue Einrichtung">
@@ -48,7 +63,7 @@ export default function CreateFacilityDialog({ onClose, onCreated }) {
<h2>Neue Einrichtung</h2>
<form onSubmit={handleSubmit}>
<FacilityForm form={form} onChange={setForm} />
<FacilityForm form={form} onChange={setForm} currentCrmStatus="Lead" />
{error && <p className="login-error form-error">{error}</p>}
@@ -56,6 +71,16 @@ export default function CreateFacilityDialog({ onClose, onCreated }) {
</form>
</div>
</div>
{isFollowUpDialogOpen && (
<FollowUpDaysDialog
onCancel={() => setIsFollowUpDialogOpen(false)}
onConfirm={(followUpDays) => {
setIsFollowUpDialogOpen(false);
save(followUpDays);
}}
/>
)}
</ModalPortal>
);
}
@@ -26,7 +26,7 @@ export default function EditFacilityDialog({ facility, onClose, onUpdated }) {
setIsSaving(true);
setError(null);
const payload = facilityFormToPayload(form, { includeCrmStatus: true, followUpDays });
const payload = facilityFormToPayload(form, { followUpDays });
const result = await window.omsorg.facilities.update(facility.id, payload);
setIsSaving(false);
@@ -64,7 +64,7 @@ export default function EditFacilityDialog({ facility, onClose, onUpdated }) {
<h2>{facility.name} bearbeiten</h2>
<form onSubmit={handleSubmit}>
<FacilityForm form={form} onChange={setForm} includeCrmStatus currentCrmStatus={facility.crmStatus} />
<FacilityForm form={form} onChange={setForm} currentCrmStatus={facility.crmStatus} />
{error && <p className="login-error form-error">{error}</p>}
@@ -63,9 +63,11 @@ export function facilityToFormValues(facility) {
};
}
export function facilityFormToPayload(form, { includeCrmStatus = false, followUpDays = null } = {}) {
const payload = {
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,
@@ -76,38 +78,29 @@ export function facilityFormToPayload(form, { includeCrmStatus = false, followUp
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,
};
if (includeCrmStatus) {
payload.crmStatus = form.crmStatus;
payload.followUpDays = followUpDays;
payload.billingRate = parseGermanDecimal(form.billingRate);
payload.nightSurchargePercent = parseGermanDecimal(form.nightSurchargePercent);
payload.saturdaySurchargePercent = parseGermanDecimal(form.saturdaySurchargePercent);
payload.sundaySurchargePercent = parseGermanDecimal(form.sundaySurchargePercent);
payload.holidaySurchargePercent = parseGermanDecimal(form.holidaySurchargePercent);
payload.travelCostMode = form.travelCostMode || "Pauschale";
payload.travelCostRate = parseGermanDecimal(form.travelCostRate);
payload.travelCostPerKm = parseGermanDecimal(form.travelCostPerKm);
payload.mealAllowanceRate = parseGermanDecimal(form.mealAllowanceRate);
payload.minimumHours = parseGermanDecimal(form.minimumHours);
payload.billingInterval = form.billingInterval || null;
payload.paymentTermDays = form.paymentTermDays === "" ? null : Number(form.paymentTermDays);
payload.individualAgreements = form.individualAgreements.trim() || null;
}
return payload;
}
export default function FacilityForm({ form, onChange, includeCrmStatus = false, currentCrmStatus = 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(() => {
if (!includeCrmStatus) return;
let cancelled = false;
window.omsorg.valueLists.listTransitions("CrmStatus").then((result) => {
if (!cancelled) {
@@ -118,7 +111,7 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
return () => {
cancelled = true;
};
}, [includeCrmStatus]);
}, []);
const currentCrmStatusItem = crmStatusOptions.find((option) => option.value === currentCrmStatus);
const selectableCrmStatusOptions = currentCrmStatusItem
@@ -144,18 +137,16 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
<input value={form.name} onChange={updateField("name")} required />
</label>
{includeCrmStatus && (
<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>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>
@@ -226,9 +217,8 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
</label>
</fieldset>
{includeCrmStatus && (
<fieldset className="form-field form-field--fieldset">
<legend>Konditionen</legend>
<fieldset className="form-field form-field--fieldset">
<legend>Konditionen</legend>
<label className="form-field">
<span>Verrechnungssatz (EUR/Std.)</span>
@@ -319,7 +309,6 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
<input value={form.individualAgreements} onChange={updateField("individualAgreements")} />
</label>
</fieldset>
)}
</div>
);
}