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:
co-authored by
Claude Haiku 4.5
parent
52fbec3214
commit
e1fbfadd17
@@ -12,7 +12,7 @@
|
||||
"module": "./dist/esm/index.js",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"generate": "./generate.sh",
|
||||
"generate": "bash generate.sh",
|
||||
"build": "tsc && tsc -p tsconfig.esm.json",
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user