Files
omsorg/omsorgapp/CLAUDE.md
T
Felix KemmlerandClaude Sonnet 5 ffa2c4a9e7
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 16s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Successful in 5s
Docker-Images bauen und veröffentlichen / build (, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 19s
Add kilometer-based Fahrtkosten billing, self-service distance entry, role deletion
- Facility: TravelCostMode (Pauschale/ProKilometer) + TravelCostPerKm, alongside
  the existing flat rate; fixes FacilityService.UpdateAsync silently dropping all
  Konditionen fields on update.
- New EmployeeFacilityDistance (Mitarbeiter x Einrichtung -> km) with full
  office-side CRUD in omsorgapp, plus a self-service endpoint/UI so field staff
  can maintain their own commute distance via OMSORG Connect (new
  ModuleType.EmployeeFacilityDistances, Own-scope, no Facilities access needed).
- Roles can now be deleted (blocked with a 409 while still assigned to a user).
- Fix employeesApi.js missing the Date-object conversion for dateOfBirth/entryDate/
  exitDate that crashed employee creation whenever a date was filled in; add a
  clear/remove control for those date fields.
- Requirements: flesh out FR-REC-3 with a staged Akquise cadence, add FR-REC-5/6
  for CRM feature scope and success metrics.
- Regenerate api-client-ts and api-client-php for all of the above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 20:15:42 +02:00

22 KiB
Raw Blame History

CLAUDE.md — omsorgapp (OMSORG Desktop)

Gilt zusätzlich zur Root-CLAUDE.md. Dies ist das React/Vite-Browser-Projekt für Büromitarbeiter (Sabina, Malik, Sabrina, Sascha) — kein Electron mehr (Umstieg 2026-08-10, siehe "Login + Session" unten für den Grund).

Stack & Struktur

  • React 18 + Vite, reine Browser-SPA (kein Electron, kein Node-Zugriff aus dem Renderer), reines JSX (kein TS).

  • src/api/ — Adapterschicht zu omsorgCore (HTTP), ersetzt das frühere electron/backend/*.cjs + electron/main.cjs/preload.cjs-IPC 1:1 als reines Browser-JS: config.js (einzige Stelle mit der Backend-URL, Default http://localhost:5245, überschreibbar per Vite-Env VITE_OMSORG_CORE_URL), apiClientHelpers.js (configFor/callApi-Wrapper um den generierten omsorgcore-client-ts-Client, setzt credentials:"include" für die HttpOnly-Refresh-Cookie), session.js (Access-Token nur als Modul-Variable im Speicher, Silent-Refresh-Timer, withAuthRetry, onSessionChanged-Listener — Browser-Ersatz für den früheren Session-State im Electron-Hauptprozess, siehe "Login + Session" unten), authApi.js (login/refresh/logout/me/... gegen /api/auth/*), genericApi.js (Ersatz für den früheren api:get/api:post-IPC-Proxy, für Ressourcen ohne eigene <kategorie>Api.js-Datei, aktuell /api/admin/sessions*//api/admin/email/*), je eine <kategorie>Api.js-Datei pro Ressource (employeesApi.js, facilitiesApi.js, facilityContactsApi.js, facilityQualificationRatesApi.js, employeeFacilityDistancesApi.js, ordersApi.js, absencesApi.js, timeEntriesApi.js, usersApi.js, rolesApi.js, auditLogApi.js, valueListsApi.js, trashApi.js, contractsApi.js, documentsApi.js, analog zu den Controllern in omsorgCore), index.js (buildOmsorgApi() — baut das komplette window.omsorg-Objekt zusammen, wrapped jede Ressourcenfunktion mit session.withAuthRetry).

  • Login + Session: Der Refresh-Token liegt als HttpOnly-Secure-Cookie (von omsorgCores AuthController gesetzt, Path=/api/auth) beim Server — nie per JS lesbar, kein Electron-safeStorage mehr nötig, dafür braucht omsorgCore CORS mit AllowCredentials() (siehe omsorgCore/CLAUDE.md, Abschnitt "Auth-Flow"). Der Access-Token lebt nur als Modul-Variable in src/api/session.js (verschwindet bei Tab-Reload/-Schließen) — session.bootstrapSession() läuft beim App-Start (src/main.jsx) und stellt die Session über die noch gültige Cookie per POST /api/auth/refresh (ohne Body, die Cookie geht automatisch mit) wieder her, damit ein Reload nicht zum erneuten Login zwingt. Silent Refresh läuft zusätzlich per Timer (kurz vor Ablauf) und reaktiv bei 401. window.omsorg.auth (auth.login, auth.logout, auth.getSession, auth.onSessionChanged) bildet dieselbe Oberfläche wie früher über IPC ab, nur als direkte Funktionsaufrufe.

  • src/main.jsx — Renderer-Einstiegspunkt, setzt window.omsorg = buildOmsorgApi() (aus src/api/index.js, ersetzt das frühere contextBridge.exposeInMainWorld), wrappt App in AuthProvider (src/app/AuthContext.jsx) und rendert in #root.

  • src/app/AuthContext.jsx — React-Context um window.omsorg.auth, stellt useAuth() mit { isAuthenticated, user, isLoading, login, logout } bereit.

  • src/app/app.jsx — gated zuerst auf Auth (isLoading → Ladehinweis, !isAuthenticatedsrc/modules/auth/LoginPage.jsx), danach Top-Level-Router: hält activePage als lokalen State (kein Routing-Framework), switcht zwischen Modulen. Noch nicht implementierte Module rendern PlaceholderPage.

  • src/layouts/AppLayout.jsx — Grundlayout: Sidebar + Header + main.page-content.

  • src/components/ — geteilte UI: Sidebar, Header, OmsorgCard, und components/ui/ (OmsorgButton, OmsorgBadge, OmsorgStatCard).

  • src/modules/<modulname>/ — ein Ordner pro Fachmodul, z. B. modules/home/ (HomePage, HomeStats, ContractWidget), modules/employees/ (EmployeesPage, EmployeeDetailPanel, EmployeeTabs), modules/facilities/ (FacilitiesPage, FacilityDetailPanel, FacilityForm, FacilityContactsList/FacilityContactForm für die Ansprechpartner-Unterliste (FR-EIN-2), FacilityQualificationRatesList/FacilityQualificationRateForm für die qualifikationsabhängigen Preise (FR-EIN-4), EmployeeFacilityDistancesList/EmployeeFacilityDistanceForm für die kilometerbasierte Fahrtkostenabrechnung (nur sichtbar, wenn facility.travelCostMode === "ProKilometer") — Referenz war 1:1 modules/employees/, ohne Tabs, da noch keine weiteren Unterobjekte wie Dokumente/Verträge existieren), modules/debug/ (DebugSessionsPage.jsx), modules/settings/ (SettingsPage.jsx, UsersPanel.jsx, ResetUserPasswordDialog.jsx, RolesPanel.jsx, RolePermissionMatrix.jsx, UserOverridesPanel.jsx, StatusManagementPanel.jsx, permissionOptions.js). Neue Module folgen diesem Muster: eigener Ordner unter src/modules/, Einstiegskomponente <Modul>Page.jsx.

  • Benutzer-, Rollen- & Rechteverwaltung (modules/settings/): Admin-UI unter dem Sidebar-Tab "Einstellungen" — sichtbar, sobald View auf mindestens einem von Users/UserManagement/Configuration vorliegt (navPermissions.js, SETTINGS_MODULES). SettingsPage.jsx filtert vier mögliche Tabs jeweils einzeln nach ihrem Modul (hasPermission(tab.module, "View")), ein Nutzer sieht also nur die Tabs, für die er tatsächlich berechtigt ist — kein pauschales Alles-oder-nichts mehr (Hintergrund/Historie: omsorgCore/CLAUDE.md, Abschnitt "Rechtesystem", "Drei getrennte Admin-Rechte"):

    • UsersPanel.jsx (Modul Users) — Benutzerkonten sehen, Rolle ändern, aktivieren/deaktivieren (window.omsorg.users.{list,update}), Passwort zurücksetzen über ResetUserPasswordDialog.jsx (window.omsorg.users.resetPassword, gleiches Invite/Direct-Formular wie beim Account-Anlegen in modules/employees/CreateUserAccountDialog.jsx).
    • RolesPanel.jsx/RolePermissionMatrix.jsx (Modul UserManagement) — Rollen anlegen via window.omsorg.roles.create, Auswahl öffnet die Checkbox-Matrix ModuleType×PermissionAction, speichert über window.omsorg.roles.updatePermissions.
    • UserOverridesPanel.jsx (Modul UserManagement) — Nutzerauswahl über window.omsorg.users.list (braucht dafür zusätzlich Users/View, siehe omsorgCore/CLAUDE.md), individuelle UserPermissionOverride-Ausnahmen je Nutzer via window.omsorg.users.{listPermissionOverrides,addPermissionOverride,deletePermissionOverride}.
    • StatusManagementPanel.jsx (Modul Configuration) — Mitarbeiterstatus, Beschäftigungsart, CRM-Status, Einrichtungstyp, Vertragstyp/-status, Auftragsstatus als admin-editierbare Auswahllisten über window.omsorg.valueLists.*; Löschen eines Werts wird serverseitig verweigert, solange er noch irgendwo gesetzt ist — Details: omsorgCore/CLAUDE.md, Abschnitt "Konfigurierbare Auswahllisten".

    ModuleType/PermissionAction/PermissionEffect-Werte + deutsche Labels sind in permissionOptions.js hart codiert (kein gemeinsames Enum-Modul zwischen Backend und Frontend, analog navPermissions.js). Backend-Details: omsorgCore/CLAUDE.md, Abschnitt "Rechtesystem".

  • Audit-Log (modules/auditLog/AuditLogPage.jsx): rein lesende, paginierte Liste (OmsorgPagination, PAGE_SIZE = 50) über window.omsorg.auditLog.list({ page, pageSize })src/api/auditLogApi.jsGET /api/audit-log in omsorgCore. Keine Bearbeiten-/Löschen-Aktionen (Audit-Einträge sind unveränderlich, es gibt serverseitig keine entsprechenden Endpoints). Im Sidebar.jsx-Menü nur sichtbar, wenn hasPermission("AuditLog", "View") (navPermissions.js, NAV_MODULES["Audit-Log"] = "AuditLog") — per Default nur die Rolle Geschäftsführung (Backend-Details: omsorgCore/CLAUDE.md, Abschnitt "Audit-Log"). ModuleType.AuditLog ist zusätzlich in modules/settings/permissionOptions.jss MODULE_OPTIONS eingetragen, damit Geschäftsführung die Berechtigung über die Rollen-Rechte-Matrix auch anderen Rollen zuweisen kann.

  • Debug-Sicht (modules/debug/DebugSessionsPage.jsx): listet aktive Sessions (GET /api/admin/sessions über den generischen api:get-Proxy, src/api/genericApi.js), erlaubt Einzel- und Gesamt-Widerruf (POST /api/admin/sessions/{id}/revoke, POST /api/admin/sessions/revoke-all) — invalidiert serverseitig sofort alle betroffenen Access-Tokens (Details: omsorgCore/CLAUDE.md Abschnitt "Session-Killswitch"). Im Sidebar.jsx-Menü nur sichtbar, wenn useAuth().hasPermission("UserManagement", "View") — dieselbe granulare Rechteprüfung, die serverseitig über RequirePermission(ModuleType.UserManagement, ...) auf AdminSessionsController erzwungen wird (kein Rollennamen-Vergleich mehr, siehe "Rechtesystem im Client" unten).

  • Rechtesystem im Client: Rechte kommen nicht aus dem JWT (das trägt nur sub/name/role/sst). src/api/session.js lädt nach jedem Login/Refresh zusätzlich GET /api/auth/me (src/api/authApi.js#me) und ersetzt session.user durch { username, role, permissions }permissions ist die vom Backend aufgelöste Liste aus PermissionService.GetGrantedPermissionsAsync (Rollen-Default + Overrides, je { module, action, scope }). AuthContext.jsx stellt darauf hasPermission(module, action) bereit; UI-Komponenten prüfen darüber, nie über user.role direkt. Zusätzlich getScope(module, action) (liefert "All"/"Own"/null) für rein kosmetische UI-Anpassungen bei Own-Scope (z. B. Suchfeld ausblenden) — die eigentliche "nur eigene Daten"-Durchsetzung passiert serverseitig (omsorgCore/CLAUDE.md, Abschnitt "Datenebenen-Scope"), aktuell für die Module Mitarbeiter/Verträge/Abwesenheiten relevant. Admin-Verwaltung dieser dritten Matrix-Dimension: modules/settings/RolePermissionMatrix.jsx (3-Zustands-Auswahl je Zelle für Employees/Contracts/Absences, sonst weiterhin Checkbox) und UserOverridesPanel.jsx (Scope-Auswahl im Override-Formular), Optionen/Labels in permissionOptions.js (SCOPE_OPTIONS, SCOPE_CAPABLE_MODULES).

  • Verbindliche Regel — UI folgt den Rechten, für jedes Modul: Jeder Sidebar-Tab und jede Aktion (Anlegen/Bearbeiten/Löschen/...) muss über hasPermission(module, action) gegated werden — das ist kein Sonderfall für Mitarbeiter, sondern das Standardmuster für jedes neue Modul (mindestens View fürs Sichtbarsein des Tabs, Create/Edit/... für einzelne Aktionen darin). Referenzimplementierung: src/modules/employees/EmployeesPage.jsx (canCreate = hasPermission("Employees", "Create")) und src/modules/employees/EmployeeDetailPanel.jsx (canEdit = hasPermission("Employees", "Edit")).

    • Die Zuordnung Sidebar-Tab → ModuleType steht zentral in src/app/navPermissions.js (NAV_MODULES) und wird sowohl von Sidebar.jsx (blendet nicht erlaubte Tabs aus) als auch von app.jsx (fällt auf "Home" zurück, falls der aktive Tab durch eine Rechteänderung nicht mehr erlaubt ist) genutzt — neue Zuordnungen nur dort eintragen, nicht duplizieren. Zwei Tabs hängen nicht an einem einzelnen Modul, sondern an einer Liste (isNavItemVisible prüft dafür Array.some(...) statt eines einzelnen Lookups): "Papierkorb" (TRASH_MODULES, sichtbar bei Recover auf irgendeinem Objekt mit Soft-Delete) und "Einstellungen" (SETTINGS_MODULES = [Users, UserManagement, Configuration], sichtbar bei View auf irgendeinem der drei — welcher Tab innerhalb der Seite dann tatsächlich erscheint, entscheidet SettingsPage.jsx separat pro Tab, siehe oben).
    • Aktuelles Mapping: Mitarbeiter→Employees, Kunden→Facilities, Disposition→Orders, Abwesenheiten→Absences, Zeiterfassung→TimeEntries, Rechnungen→Invoices, Controlling→Controlling, Debug→UserManagement, Audit-Log→AuditLog, Einstellungen→ siehe SETTINGS_MODULES oben. Home hat kein Modul und ist immer sichtbar. Kalkulation und Fahrzeuge sind reine PlaceholderPage-Stubs ohne Fachlogik und haben (noch) kein passendes ModuleType — bewusst ungegated, bis ein echtes Modul dahintersteht; dann Eintrag in navPermissions.js ergänzen (ggf. mit neuem ModuleType-Wert in omsorgCore, additiv, keine Migration nötig).
  • src/style.css — einziges Stylesheet, kein CSS-Framework.

Wichtige Startbefehle

npm install
npm run dev   # Vite-Devserver, http://127.0.0.1:5173 im Browser öffnen

omsorgCore muss dafür separat laufen (siehe omsorgCore/CLAUDE.md) und dessen Cors:AllowedOrigins http://127.0.0.1:5173 enthalten (per Default in appsettings.Development.json gesetzt).

Aktueller Ist-Stand (siehe auch REQUIREMENTS.md)

  • Vollständig: HomePage, EmployeesPage (Grundgerüst; Daten kommen über src/api/employeesApi.js echt aus omsorgCore, keine hartcodierten Beispieldaten mehr für dieses Modul), FacilitiesPage (Sidebar-Tab "Kunden", ModuleType.Facilities; Grundgerüst analog EmployeesPage, Daten über src/api/facilitiesApi.js echt aus omsorgCore; deckt Stammdaten und eine Ansprechpartner-Liste je Einrichtung ab (FacilityContactsList im FacilityDetailPanel, über src/api/facilityContactsApi.js gegen /api/facilities/{id}/contacts, Anlegen/Bearbeiten, kein Löschen); CRM-Pipeline (FR-EIN-3) und Konditionen (FR-EIN-4, "Konditionen"-Fieldset in FacilityForm.jsx + FacilityQualificationRatesList im FacilityDetailPanel, über src/api/facilityQualificationRatesApi.js gegen /api/facilities/{id}/qualification-rates) sind jetzt ebenfalls umgesetzt — Fahrtkosten (seit 2026-08-10) haben zwei Abrechnungsarten: travelCostMode "Pauschale" (Feld travelCostRate) oder "ProKilometer" (Feld travelCostPerKm, EUR/km); im Pro-Kilometer-Modus zeigt FacilityDetailPanel zusätzlich EmployeeFacilityDistancesList (Entfernung je Mitarbeiter in km, über src/api/employeeFacilityDistancesApi.js gegen /api/facilities/{id}/employee-distances — eigene 1:n-Unterressource, weil jeder Mitarbeiter von einem anderen Wohnort anfährt, siehe omsorgCore/CLAUDE.md "Konditionen einer Einrichtung"). FR-EIN-5 (konsolidierte Historie) bleibt offen), Login-Screen + persistente Session gegen omsorgCore, SettingsPage (Benutzerübersicht + Rollen-Rechte-Matrix + User-Permission-Overrides + Status-Verwaltung, siehe "Benutzer-, Rollen- & Rechteverwaltung" oben), AuditLogPage (siehe "Audit-Log" oben), OrdersPage (Sidebar-Tab "Disposition", ModuleType.Orders; FR-EM-1, Grundgerüst 1:1 analog FacilitiesPage, Daten über src/api/ordersApi.js echt aus omsorgCore, volles CRUD (OrdersPage/OrderDetailPanel/Create-/EditOrderDialog/OrderForm.jsx); Pflichtfelder Einrichtung/Ansprechpartner/Zeitraum/Qualifikation/Schichtart/Anzahl Mitarbeiter/Konditionen/Priorität abgedeckt — Ansprechpartner-Dropdown lädt beim Wechsel der Einrichtung deren Kontakte per window.omsorg.facilityContacts.list(facilityId) nach; Qualifikation/Schichtart/Priorität/Status als admin-editierbare ValueLists über useValueListItems; Statusfeld ist nur im Bearbeiten-Formular sichtbar und zeigt dabei nur die laut /api/value-lists/OrderStatus/transitions erlaubten Zielstatus, analog zum CRM-Status-Dropdown bei Facilities). FR-EM-2 (Dashboard-Sichtbarkeit der Statuspipeline): modules/home/OrderStatusWidget.jsx in HomePage.jsx, listet die aktuelle Auftragsanzahl je OrderStatus-Wert (client-seitig aus window.omsorg.orders.list({ pageSize: 200 }) aggregiert, kein eigener Stats-Endpoint), rechtegegated über hasPermission("Orders","View"), UI-Muster 1:1 von FollowUpWidget.jsx übernommen. AbsencesPage (Sidebar-Tab "Abwesenheiten", ModuleType.Absences; Datenbasis für FR-CON-1/FR-EM-3, Backend-Details omsorgCore/CLAUDE.md "Abwesenheits-/Urlaubs-/Krankmeldungsanträge"): Liste+Filter (Status/Art) + AbsenceDetailPanel mit Genehmigen/Ablehnen-Aktionen (hasPermission("Absences","Approve"), window.omsorg.absences.decide(id, { status, adminNote })) und Bearbeiten (hasPermission("Absences","Edit"), EditAbsenceDialog.jsx/AbsenceForm.jsx nach dem OrderForm.jsx-Muster, window.omsorg.absences.update(id, payload)) — Bearbeiten-Button nur sichtbar, solange der Status noch der initiale ist (statusItems.find(i => i.isInitial)?.value aus useValueListItems("AbsenceStatus") — nicht der Literal "Eingereicht", umbenennbar über die Status-Verwaltung, ohne dass diese Komponente angefasst werden muss; serverseitig ohnehin erzwungen, siehe omsorgCore/CLAUDE.md). Genehmigen/Ablehnen ist bewusst jederzeit möglich, nicht nur solange der Antrag noch im initialen Status ist — der Decide-Endpoint hat serverseitig keine Statusprüfung (anders als Update), damit eine versehentliche Entscheidung korrigierbar bleibt; die UI zeigt bei bereits entschiedenen Anträgen zusätzlich einen Hinweistext ("Bereits entschieden (...) — hier lässt sich die Entscheidung bei Bedarf noch ändern"). Bewusst kein Anlegen-Dialog hier, Anträge stellt ausschließlich der Außendienst über omsorgWeb/mitarbeiter-app (pages/urlaubsantrag.php), omsorgapp prüft/bearbeitet/entscheidet nur. TimeEntriesPage (Sidebar-Tab "Zeiterfassung", ModuleType.TimeEntries; FR-ZE-1/FR-ZE-2, Backend-Details omsorgCore/CLAUDE.md "Zeiterfassung"): Liste+Statusfilter + TimeEntryDetailPanel mit dynamischen Büro-Entscheidungs-Buttons (hasPermission("TimeEntries","Approve"), aus window.omsorg.valueLists.listTransitions("TimeEntryStatus") gefiltert auf requiresApproval===true-Kanten ab dem aktuellen Status, window.omsorg.timeEntries.decide(id, { statusId, adminNote }) — Muster 1:1 von OrderForm.jsxs Statuswechsel-Filterung übernommen, nicht von AbsenceDetailPanel, da hier eine echte Mehrstufen-Pipeline statt einer binären Entscheidung vorliegt) und Bearbeiten (hasPermission("TimeEntries","Edit"), EditTimeEntryDialog.jsx/TimeEntryForm.jsx, window.omsorg.timeEntries.update(id, payload)) — Bearbeiten-Button nur sichtbar, solange timeEntry.isEditableByOwner (direkt aus der TimeEntryResponse, nicht per Statuswert-Vergleich). Bewusst kein Anlegen-Dialog und kein statusId-Feld im Bearbeiten-Formular — Erfassung passiert in omsorgWeb/mitarbeiter-app (pages/stundenerfassung.php), Statuswechsel laufen ausschließlich über submit (dort) bzw. decide (hier).
  • Papierkorb (modules/trash/TrashPage.jsx): tab-basierte Liste, ein Eintrag in TABS pro Objekt mit Soft-Delete (window.omsorg.trash.{list,restore}<Objekt>), inzwischen employees/facilities/contracts/orders/facilityContacts/facilityQualificationRates/employeeFacilityDistances/absences/timeEntries — jeder Tab nur sichtbar mit hasPermission(tab.module, "Recover"). Neue Objekte mit Soft-Delete: Eintrag hier UND in navPermissions.jss TRASH_MODULES ergänzen (beide nötig, siehe oben).
  • Dokumente (FR-MA-3, modules/employees/DocumentsList.jsx/UploadDocumentDialog.jsx/EditDocumentDialog.jsx): eigener Tab in der Personalakte (EmployeeDetailPanel.jsx), nur sichtbar mit hasPermission("Documents","View") (EmployeeTabs.jsx bekommt dafür einen hiddenTabIds-Prop). Liste gruppiert nach Kategorie (admin-editierbare DocumentCategory-Auswahlliste, siehe omsorgCore/CLAUDE.md), Buttons Hochladen/Bearbeiten/Herunterladen/Löschen je nach Documents-Rechten. Datei-Upload läuft über einen normalen <input type="file">; die Bytes gehen als ArrayBuffer (aus file.arrayBuffer()) direkt an src/api/documentsApi.js#uploadDocument, das daraus ein Blob für den generierten DocumentsApi-Multipart-Aufruf baut. Download läuft bewusst NICHT über den generierten Client (apiDocumentsIdDownloadGetRaw ist als VoidApiResponse generiert, verwirft den Response-Body) — documentsApi.downloadDocument macht dafür einen direkten fetch mit Authorization-Header und liefert einen Blob zurück; src/api/index.jss documents.download erzeugt daraus URL.createObjectURL(blob) + einen unsichtbaren <a download>-Klick (Browser-natives Herunterladen statt Electrons dialog.showSaveDialog), documents.view (für DocumentViewerDialog.jsx) reicht denselben Blob als <iframe src={objectUrl}> (PDF) bzw. <img> weiter. "Bearbeiten" ändert nur Metadaten (Kategorie/Beschreibung/Dateiname) über PUT /api/documents/{id}, kein erneuter Datei-Upload.
  • Alle anderen Menüpunkte (Kalkulation, Fahrzeuge, Rechnungen, Controlling) sind reine PlaceholderPage-Platzhalter in app.jsx.
  • Auth, Mitarbeiter, Kunden/Einrichtungen, Aufträge/Disposition, Abwesenheiten und Zeiterfassung sprechen bereits gegen omsorgCore (siehe src/api/employeesApi.js/facilitiesApi.js/ordersApi.js/absencesApi.js/timeEntriesApi.js, zusammengefügt in src/api/index.jss employees/facilities/orders/absences/timeEntries-Namespaces). Die übrigen Fachmodule (Kalkulation, Fahrzeuge, Rechnungen, Controlling) haben noch gar keine Datenhaltung — weder lokal noch über omsorgCore — sondern sind reine PlaceholderPage-Stubs. Nächster Schritt: weitere Fachmodule nach und nach nach demselben Muster (src/api/<kategorie>Api.js + omsorgCore-Endpunkte) umsetzen, analog zum Mitarbeiter-/Kunden-/Auftrags-Vorbild.
  • Keine Verbindung zu omsorgWeb/MySQL — Mitarbeiterdaten hier sind komplett getrennt von denen in OMSORG Connect. Nicht durch neue Kopplungen/Workarounds "beheben"; die eigentliche Lösung ist die gemeinsame Datenbasis in omsorgCore. Abwesenheitsanträge sind der erste Datentyp, der diese Trennung nicht mehr hat: omsorgWeb/mitarbeiter-app legt sie direkt gegen omsorgCore an, omsorgapp liest/entscheidet dieselben Datensätze — kein separater Sync, keine zweite Quelle.

Konventionen

  • Neue Module: Ordner src/modules/<name>/<Name>Page.jsx + Unterkomponenten, in app.jsx-Switch eintragen, in Sidebar.jsx verlinken.
  • UI-Bausteine aus src/components/ui/ wiederverwenden statt neue Button/Badge/Card-Varianten zu bauen.
  • Jeglicher Backend-Datenzugriff nur über die window.omsorg-Brücke aus src/api/index.js, nie direktes fetch/omsorgcore-client-ts in Modul-Komponenten.
  • Neue omsorgCore-Endpunkte: pro Endpunkt-Kategorie eine eigene Datei src/api/<kategorie>Api.js (z. B. künftig invoicesApi.js für den InvoicesController), die ausschließlich apiClientHelpers.jss configFor/callApi nutzt und Routen/Feldnamen genau dieser einen Ressource kapselt — spiegelt die Controller-Aufteilung in omsorgCore 1:1. src/api/index.js importiert nur diese <kategorie>Api.js-Dateien und wrapped sie mit session.withAuthRetry, nie apiClientHelpers.js direkt in einer Modul-Komponente. Solange eine Ressource noch keine eigene Api.js-Datei hat, läuft sie über genericApi.jss window.omsorg.api.get/.post; sobald die Datei existiert, bekommt sie einen eigenen Namespace nach demselben Muster wie employees/orders/etc. Grund: Backend soll austauschbar bleiben, ohne den React-Teil anzufassen (siehe Adapterschicht oben).