Compare commits
3
Commits
v0.1.6
...
019b4286fd
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
019b4286fd | ||
|
|
99b5e07390 | ||
|
|
43c82e655b |
+2
-1
@@ -80,6 +80,7 @@ Umfasst alle drei Plattform-Ebenen: OMSORG Desktop, OMSORG Connect, OMSORG Backe
|
||||
| FR-MA-4 | Eintrittsdatum löst Dashboard-Hinweis auf bevorstehenden Mitarbeiterstart aus [Blueprint 7] | alle Büro-Rollen | X Tage vor Eintritt erscheint Engine-Hinweis im Dashboard | ⬜ (abhängig von Engine, siehe 4.10) |
|
||||
| FR-MA-5 | Bei Büromitarbeitern kann der Mitarbeiterdatensatz mit einem Benutzerkonto + individuellen Rechten verknüpft werden [Blueprint 19.1] | Sabina, Malik | Rechteliste pro Modul (sehen/anlegen/bearbeiten/löschen/exportieren/freigeben) editierbar | ✅ (in `omsorgCore`, nicht mehr `omsorgWeb`: `User.EmployeeId` verknüpft ein Benutzerkonto mit dem `Employee`-Datensatz, `UsersController.Create`/`UserService.CreateForEmployeeAsync` erzwingt genau 1 Konto je Mitarbeiter; Rechte sind granular pro Modul × Aktion — Rolle als Vorlage (`RolePermission`) plus individuelle Grant/Revoke-Ausnahmen je Nutzer (`UserPermissionOverride`), inkl. `Recover` seit dem Soft-Delete-Schritt; Admin-UI in `omsorgapp`: `RolesPanel`/`RolePermissionMatrix`/`UserOverridesPanel` unter "Einstellungen") |
|
||||
| FR-MA-6 | Außendienstmitarbeiter sehen ausschließlich eigene Daten [Blueprint 4.2] | Außendienst | Query/Route liefert nur Datensätze mit `user_id = aktueller Nutzer` | ✅ (Session-basierter Zugriff in `omsorgWeb/mitarbeiter-app`, z. B. `pages/urlaubsantrag.php`) |
|
||||
| FR-MA-7 | Geburtstags-Erinnerung im Dashboard: 5 Tage vor dem Geburtstag eines aktiven Mitarbeiters erscheint ein Hinweis | alle Büro-Rollen mit Mitarbeiter-Leserecht | Widget zeigt Mitarbeiter mit Geburtstag in den nächsten 5 Tagen, verschwindet danach automatisch wieder | ⬜ |
|
||||
|
||||
### 4.3 Einrichtungen & CRM
|
||||
|
||||
@@ -88,7 +89,7 @@ Umfasst alle drei Plattform-Ebenen: OMSORG Desktop, OMSORG Connect, OMSORG Backe
|
||||
| FR-EIN-1 | Stammdaten (Name, Art, Adresse, Rechnungsadresse, Telefon, E-Mail, Website, Status) [Blueprint 19.2] | Sabina, Malik, Sabrina, Sascha (Leads) | Datensatz anlegen/bearbeiten | ✅ (`Facility`-Entity + volles Repository/Service/Controller (`FacilitiesController`, `GET/POST/PUT /api/facilities`) in `omsorgCore`, serverseitige Rechteprüfung über `[RequirePermission(ModuleType.Facilities, ...)]`; Felder Name/Art(`FacilityType`)/Adresse/Rechnungsadresse/Website/Status(`CrmStatus`) abgedeckt — Adresse und Rechnungsadresse sind je strukturierte Felder (Straße/PLZ/Ort/Land, analog Mitarbeiter-Adresse), kein Freitext; UI-Modul in `omsorgapp` vorhanden (`FacilitiesPage`, Sidebar-Tab "Kunden", analog `EmployeesPage`) — Anlegen/Bearbeiten funktionsfähig gegen `omsorgCore`. **Bewusste Abweichung von der Zeile:** kein allgemeines Telefon/E-Mail direkt auf `Facility` — Kontaktwege leben ausschließlich pro Ansprechpartner auf `FacilityContact` (FR-EIN-2), da eine Einrichtung i. d. R. mehrere Ansprechpartner mit je eigenen Kontaktdaten hat und ein zusätzliches, nicht klar zuordenbares "allgemeines" Einrichtungstelefon eine zweite, redundante Kontakt-Datenquelle wäre (widerspricht dem Grundsatz "jede Information wird nur einmal gespeichert", siehe Root-`CLAUDE.md`). Diese Entscheidung ist absichtlich, kein offener Punkt.) |
|
||||
| FR-EIN-2 | Mehrere Ansprechpartner je Einrichtung mit Funktion, Abteilung, Kontaktwegen, Notizen | Sabina, Malik, Sabrina, Sascha | Liste von Ansprechpartnern editierbar, mind. 1:n-Beziehung | ✅ (neue Entität `FacilityContact` (1:n zu `Facility`) in `omsorgCore`, `FacilityContactsController` unter `GET/POST/PUT/DELETE /api/facilities/{facilityId}/contacts[/...]`, gegated über dieselben `ModuleType.Facilities`-Rechte wie die Einrichtung selbst; Felder Name/Funktion(`Role`)/Abteilung(`Department`)/Telefon/E-Mail/Notizen abgedeckt; UI-Liste im `FacilityDetailPanel` (`omsorgapp`) anlegen/bearbeiten/löschen funktionsfähig; Löschen ist Soft-Delete (`IsDeleted`/`DeletedAt`) und über die neue "Papierkorb"-Seite (`ModuleType.Facilities`+`PermissionAction.Recover`) wiederherstellbar) |
|
||||
| FR-EIN-3 | CRM-Status-Pipeline: Lead → kontaktiert → kein Bedarf → Wiedervorlage → Interesse → Angebot → Kunde → Bestandskunde [Blueprint, omsorg.md] | Sascha, Sabrina | Statuswechsel wird protokolliert; bei „kein Bedarf" wird automatisch Wiedervorlage in 14 Tagen erzeugt | ✅ (Statuswechsel-Protokollierung läuft automatisch über den bestehenden `AuditSaveChangesInterceptor`, kein Zusatzcode nötig; neues Feld `Facility.FollowUpDueDate` (nullable), gesetzt über `FacilitiesController.Update`, wenn der neu gewählte `CrmStatus`-Wert das Flag `TriggersFollowUp` trägt (aktuell nur "Kein Bedarf" — generisch statt hartcodiert, damit künftig weitere Status dieselbe Mechanik nutzen können) — serverseitig muss dann ein `FollowUpDays` aus der admin-editierbaren `ValueList` `"FollowUpPeriods"` (7/14/21/31 Tage, Default 14) mitgegeben werden; `omsorgapp` zeigt dafür beim Statuswechsel auf einen solchen Status einen Auswahldialog (`FollowUpDaysDialog.jsx` in `EditFacilityDialog.jsx`, 14 Tage vorausgewählt) und das Fälligkeitsdatum im `FacilityDetailPanel`; Dashboard-Widget "Fällige Wiedervorlagen" (`FollowUpWidget.jsx`, in `HomePage.jsx` eingebunden) zeigt fällige Einrichtungen an; die Statuspipeline erzwingt wie bei `OrderStatus` erlaubte Übergänge (`ValueListItemTransition` für `"CrmStatus"`, geseedet mit der Vorwärtskette der Pipeline plus jederzeitigem Rückfall auf "Kein Bedarf"/"Wiedervorlage", admin-editierbar über dieselbe "Erlaubte Übergänge"-Matrix wie beim Auftragsstatus, `FacilitiesController.Update` prüft serverseitig via `CanTransitionAsync`, das CRM-Status-Dropdown in `FacilityForm.jsx` (nur im `EditFacilityDialog`) zeigt dem Nutzer dabei ebenfalls nur den aktuellen Status plus die laut `GET /api/value-lists/CrmStatus/transitions` erlaubten Zielstatus an — vorher listete das Dropdown ungefiltert alle CRM-Status auf, ein unzulässiger Wechsel wurde erst nach "Speichern" per 400-Fehler vom Server abgelehnt). Die wählbare Frist statt einer starren 14-Tage-Automatik ist eine bewusste, finale Designentscheidung — kein offener Punkt.) |
|
||||
| FR-EIN-4 | Konditionen (Verrechnungssatz, Zuschläge, Fahrtkosten, Mindeststunden, Zahlungsziel etc.) je Einrichtung [Blueprint 19.2] | Sabina, Malik, Sabrina | Konditionssatz ist Grundlage für Rechnungserstellung (siehe FR-RE-1) | ✅ (Backend: elf der zwölf Blueprint-19.2-Felder als flache, nullable Spalten auf `Facility` — Verrechnungssatz, vier Zuschläge (als Prozent), Fahrtkosten (Pauschale je Einsatz), Mindeststunden, Pausenregelung, Abrechnungsintervall (validiert gegen neue `ValueList` `"BillingInterval"`), Zahlungsziel, individuelle Vereinbarungen —, nur über `PUT /api/facilities/{id}` pflegbar; qualifikationsabhängige Preise als neue 1:n-Unterressource `FacilityQualificationRate` (`GET/POST/PUT/DELETE /api/facilities/{facilityId}/qualification-rates[/...]`, Qualifikation gegen die bestehende `ValueList` `"Qualification"` validiert) nach dem `FacilityContact`-Muster, inkl. Soft-Delete/Papierkorb; Migration `AddFacilityConditionsAndQualificationRates` erfolgreich gegen die echte Postgres-Instanz angewendet (automatisch beim API-Start); `omsorgapp`-UI (Konditionen-Fieldset in `FacilityForm.jsx`, `FacilityQualificationRatesList` im `FacilityDetailPanel`) vorhanden, `omsorgapp/api-client-ts` regeneriert und gebaut (Methodennamen/Feldnamen gegen den generierten Client verifiziert). Akzeptanzkriterium "Grundlage für Rechnungserstellung" im engeren Sinn bewusst weiterhin nicht vollständig erfüllt, da FR-RE-1/`Invoice` diese Daten noch nicht konsumiert — dieser Schritt legt nur die Datenbasis.) |
|
||||
| FR-EIN-4 | Konditionen (Verrechnungssatz, Zuschläge, Fahrtkosten, Mindeststunden, Zahlungsziel etc.) je Einrichtung [Blueprint 19.2] | Sabina, Malik, Sabrina | Konditionssatz ist Grundlage für Rechnungserstellung (siehe FR-RE-1) | ✅ (Backend: flache, nullable Spalten auf `Facility` — Verrechnungssatz, vier Zuschläge (als Prozent), Fahrtkosten (zwei Abrechnungsarten: `TravelCostMode` "Pauschale" je Einsatz oder "ProKilometer" × `EmployeeFacilityDistance.DistanceKm`, siehe unten), Mindeststunden, Abrechnungsintervall (validiert gegen `ValueList` `"BillingInterval"`), Zahlungsziel, individuelle Vereinbarungen — nur über `PUT /api/facilities/{id}` pflegbar (Pausenregelung wurde am 2026-08-10 als fachlich nicht benötigt wieder entfernt); qualifikationsabhängige Preise als 1:n-Unterressource `FacilityQualificationRate` (`GET/POST/PUT/DELETE /api/facilities/{facilityId}/qualification-rates[/...]`, Qualifikation gegen die `ValueList` `"Qualification"` validiert) nach dem `FacilityContact`-Muster, inkl. Soft-Delete/Papierkorb; Entfernung je Mitarbeiter für die Pro-Kilometer-Abrechnung als weitere 1:n-Unterressource `EmployeeFacilityDistance` (`.../employee-distances[/...]` für Büro-Rollen, zusätzlich `/api/me/facility-distances` als Selbstbedienungs-Endpoint für den Außendienst über `omsorgWeb/mitarbeiter-app`, eigener `ModuleType.EmployeeFacilityDistances`); `omsorgapp`-UI (Konditionen-Fieldset in `FacilityForm.jsx` inkl. Abrechnungsart-Umschalter, `FacilityQualificationRatesList`/`EmployeeFacilityDistancesList` im `FacilityDetailPanel`) vorhanden, beide generierten API-Clients regeneriert und gebaut. Akzeptanzkriterium "Grundlage für Rechnungserstellung" im engeren Sinn bewusst weiterhin nicht vollständig erfüllt, da FR-RE-1/`Invoice` diese Daten noch nicht konsumiert — dieser Schritt legt nur die Datenbasis.) |
|
||||
| FR-EIN-5 | Verknüpfte Historie: Verträge, Aufträge, zugewiesene Mitarbeiter, Nachweise, Rechnungen, Zahlungen, Mahnungen, Kommunikation | alle Büro-Rollen (rechteabhängig) | Einrichtungsakte zeigt konsolidierte Historie ohne Datenduplizierung | ⬜ |
|
||||
|
||||
### 4.4 Einsatzmanagement (Auftrag → Einsatz → Zuweisung)
|
||||
|
||||
@@ -164,7 +164,7 @@ Vollständig implementiert: `PasswordResetCode`-Entity + `PasswordResetService`
|
||||
- Connection-String-Key: `ConnectionStrings:OmsorgCore` (Format `Host=...;Port=5432;Database=omsorg_core;Username=...;Password=...`).
|
||||
- `dotnet-ef` ist als lokales Tool eingerichtet (`.config/dotnet-tools.json`) — vor erster Nutzung `dotnet tool restore`. Wird nur noch zum **Erzeugen** neuer Migrationen gebraucht (`dotnet ef migrations add ...`), nicht mehr zum Anwenden.
|
||||
- **`Program.cs` ruft bei jedem Start `db.Database.MigrateAsync()` auf, in jeder Umgebung** (nicht nur Development) — ausstehende Migrationen werden automatisch angewendet, bevor der Server Requests annimmt. Ein manuelles `dotnet ef database update` ist dadurch nur noch zum gezielten Vorab-Prüfen/Debuggen einer Migration nötig, nicht mehr für den normalen Start/Deploy. Schlägt die Migration fehl, crasht der Start bewusst fatal (fail-fast) statt mit einem veralteten Schema weiterzulaufen.
|
||||
- Migrationen `InitialCreate`, `AddRefreshTokens`, `AddUserSecurityStamp`, `AddEmployeeContactFieldConstraints` und `AddAuditableSoftDelete` existieren (`src/OmsorgCore.Infrastructure/Persistence/Migrations/`) und wurden erfolgreich gegen eine echte PostgreSQL-Instanz angewendet. `AddContractDetailsAndQueryFilter` ist erzeugt, aber noch nicht gegen eine echte Instanz verifiziert (wird beim nächsten API-Start automatisch angewendet). `AddPermissionScope` (fügt `Scope` auf `role_permissions`/`user_permission_overrides` hinzu, siehe "Rechtesystem") wurde per `dotnet ef database update` erfolgreich gegen die echte Instanz angewendet. `AddFacilityConditionsAndQualificationRates` (Konditionen-Felder auf `Facility` + Tabelle `facility_qualification_rates`, FR-EIN-4), `AddAbsences` (Tabelle `absences`, FR-CON-1/FR-EM-3) und `AddTimeEntryStatusAndSurchargeHours` (`TimeEntry.StatusId`+Zuschlagsstunden, `ValueListItem.IsEditableByOwner`, `ValueListItemTransition.RequiresApproval`, FR-ZE-1/FR-ZE-2) wurden beim automatischen API-Start erfolgreich gegen die echte Instanz angewendet. `AddTravelCostModeAndEmployeeFacilityDistances` (`Facility.TravelCostMode`/`TravelCostPerKm` + Tabelle `employee_facility_distances`, siehe "Konditionen einer Einrichtung") ist erzeugt (`dotnet build`/`dotnet test` grün), aber noch nicht gegen eine echte Instanz verifiziert.
|
||||
- Migrationen `InitialCreate`, `AddRefreshTokens`, `AddUserSecurityStamp`, `AddEmployeeContactFieldConstraints` und `AddAuditableSoftDelete` existieren (`src/OmsorgCore.Infrastructure/Persistence/Migrations/`) und wurden erfolgreich gegen eine echte PostgreSQL-Instanz angewendet. `AddContractDetailsAndQueryFilter` ist erzeugt, aber noch nicht gegen eine echte Instanz verifiziert (wird beim nächsten API-Start automatisch angewendet). `AddPermissionScope` (fügt `Scope` auf `role_permissions`/`user_permission_overrides` hinzu, siehe "Rechtesystem") wurde per `dotnet ef database update` erfolgreich gegen die echte Instanz angewendet. `AddFacilityConditionsAndQualificationRates` (Konditionen-Felder auf `Facility` + Tabelle `facility_qualification_rates`, FR-EIN-4), `AddAbsences` (Tabelle `absences`, FR-CON-1/FR-EM-3) und `AddTimeEntryStatusAndSurchargeHours` (`TimeEntry.StatusId`+Zuschlagsstunden, `ValueListItem.IsEditableByOwner`, `ValueListItemTransition.RequiresApproval`, FR-ZE-1/FR-ZE-2) wurden beim automatischen API-Start erfolgreich gegen die echte Instanz angewendet. `AddTravelCostModeAndEmployeeFacilityDistances` (`Facility.TravelCostMode`/`TravelCostPerKm` + Tabelle `employee_facility_distances`, siehe "Konditionen einer Einrichtung") und `RemoveFacilityBreakPolicy` (löscht die Spalte `BreakPolicy` von `facilities` wieder) sind erzeugt (`dotnet build`/`dotnet test` grün), aber noch nicht gegen eine echte Instanz verifiziert.
|
||||
|
||||
## Build- und Run-Befehle
|
||||
|
||||
@@ -233,14 +233,14 @@ Dropdown-Werte, die früher als hartcodierte Arrays im `omsorgapp`-Frontend lebt
|
||||
|
||||
## Konditionen einer Einrichtung (FR-EIN-4)
|
||||
|
||||
Elf der zwölf Blueprint-19.2-Konditionsfelder (Verrechnungssatz, vier Zuschläge, Fahrtkosten, Mindeststunden, Pausenregelung, Abrechnungsintervall, Zahlungsziel, individuelle Vereinbarungen) sind flache, nullable Felder direkt auf `Facility` — kein `OwnsOne`/keine eigene Tabelle, analog zu Adresse/Rechnungsadresse auf `Facility` selbst und den Finanzfeldern auf `Contract`:
|
||||
Zehn der zwölf Blueprint-19.2-Konditionsfelder (Verrechnungssatz, vier Zuschläge, Fahrtkosten, Mindeststunden, Abrechnungsintervall, Zahlungsziel, individuelle Vereinbarungen) sind flache, nullable Felder direkt auf `Facility` — kein `OwnsOne`/keine eigene Tabelle, analog zu Adresse/Rechnungsadresse auf `Facility` selbst und den Finanzfeldern auf `Contract`. **Pausenregelung wurde am 2026-08-10 wieder entfernt** (fachlich nicht gebraucht, `BreakPolicy` auf `Facility`/`UpdateFacilityRequest`/`FacilityResponse` gelöscht, Migration `RemoveFacilityBreakPolicy`, beide generierten Clients neu erzeugt, `FacilityForm.jsx`/`FacilityDetailPanel.jsx` bereinigt) — falls sie doch wieder gebraucht wird, ist das ein Neuanlegen, kein Wiederherstellen (Hard-Delete der Spalte, kein Soft-Delete-Konzept für Schema-Felder):
|
||||
|
||||
- `BillingRate` (Verrechnungssatz, EUR/Std.), `NightSurchargePercent`/`SaturdaySurchargePercent`/`SundaySurchargePercent`/`HolidaySurchargePercent` (Zuschläge als **Prozent** auf den Verrechnungssatz, nicht als EUR-Betrag), `MinimumHours` (Mindeststunden je Einsatz), `BreakPolicy` (Pausenregelung, Freitext), `PaymentTermDays` (Zahlungsziel in Tagen), `IndividualAgreements` (Freitext).
|
||||
- `BillingRate` (Verrechnungssatz, EUR/Std.), `NightSurchargePercent`/`SaturdaySurchargePercent`/`SundaySurchargePercent`/`HolidaySurchargePercent` (Zuschläge als **Prozent** auf den Verrechnungssatz, nicht als EUR-Betrag), `MealAllowanceRate` (Verpflegungsmehraufwand, EUR-Pauschale je Einsatz — bewusst kein tageweises/gesetzliches Stufenmodell wie beim echten VMA, sondern wie `TravelCostRate` ein einziger fixer Betrag je Einrichtung), `MinimumHours` (Mindeststunden je Einsatz), `PaymentTermDays` (Zahlungsziel in Tagen), `IndividualAgreements` (Freitext).
|
||||
- **Fahrtkosten (seit 2026-08-10 zwei Modi statt nur Pauschale):** `TravelCostMode` (`"Pauschale"` oder `"ProKilometer"`, Default `"Pauschale"`, im Controller gegen ein festes Literal-Array geprüft — **bewusst keine `ValueList`**, da die Rechnungserstellung später hart zwischen genau diesen zwei Fällen unterscheiden muss, ein dritter admin-hinzufügbarer Wert würde die künftige `FR-RE-1`-Berechnungslogik lautlos brechen, analog zur Begründung bei `AbsenceStatus.IsInitial`) entscheidet, welches Feld gilt: `TravelCostRate` (Pauschale je Einsatz, EUR) oder `TravelCostPerKm` (EUR/km) × `EmployeeFacilityDistance.DistanceKm`. Eine feste Facility-Distanz reicht nicht, weil jeder Mitarbeiter von einem anderen Wohnort anfährt — daher `EmployeeFacilityDistance` (`Facility`×`Employee` → `DistanceKm`, `AuditableEntity`) als **zweite** 1:n-Unterressource von Facility, exakt nach dem `FacilityQualificationRate`-Muster: `GET/POST/PUT/DELETE /api/facilities/{facilityId}/employee-distances[/...]` (`EmployeeFacilityDistancesController`), gegated über dieselben `Facilities`-Rechte, kein eigener `ModuleType`. `Create` validiert zusätzlich, dass `EmployeeId` auf einen existierenden Mitarbeiter zeigt (`IEmployeeService.GetByIdAsync`) und dass für das Facility/Employee-Paar noch keine (nicht gelöschte) Distanz existiert. Soft-Delete, über `TrashController` (`api/trash/employee-facility-distances/...`) wiederherstellbar.
|
||||
- **Selbstbedienung durch den Außendienst (seit 2026-08-10, `MyFacilityDistancesController`, Route `api/me/facility-distances`):** Mitarbeiter sollen ihre eigene Fahrtstrecke selbst über `omsorgWeb/mitarbeiter-app` pflegen können, statt dass das Büro jede Kilometerangabe manuell einträgt. Dafür bewusst **kein** `Facilities`-Recht (das würde Konditionen/CRM-Daten offenlegen, die dem Außendienst laut Rechtematrix nicht zustehen), sondern ein neuer, eigenständiger `ModuleType.EmployeeFacilityDistances` (siehe "Rechtesystem" oben) — `GET /api/me/facility-distances` (eigene Distanzen inkl. `FacilityName`), `GET /api/me/facility-distances/facilities` (minimale Einrichtungsauswahl, nur `Id`/`Name` über `FacilityOptionResponse`, für das Formular-Dropdown), `POST`/`PUT` (Anlegen/Bearbeiten). `EmployeeId` kommt bei jeder Aktion ausschließlich aus `ICurrentUserService.EmployeeId` (JWT-Claim), nie vom Client — bei fehlender Verknüpfung `400` statt eines FK-Fehlers, analog zum `AbsenceService.CreateAsync`-Fallback. Bewusst **kein** `DELETE` hier (Löschen bleibt Büro-Aufgabe über den Papierkorb). Nutzt intern denselben `IEmployeeFacilityDistanceService`/dieselbe Tabelle wie `EmployeeFacilityDistancesController` — zwei Controller auf demselben Application-Service, unterschiedliche Zugriffsrechte, kein Datenmodell-Unterschied.
|
||||
- **Zugleich behobener Bestandsfehler:** `FacilityService.UpdateAsync` kopierte die elf Konditionsfelder bislang gar nicht auf die getrackte Entität — der Controller validierte sie korrekt, aber `PUT /api/facilities/{id}` verwarf sie stillschweigend (kein Fehler, kein Log, das Feld blieb einfach `null`/unverändert). Betraf `BillingRate`/alle vier Zuschläge/`TravelCostRate`/`MinimumHours`/`BreakPolicy`/`BillingInterval`/`PaymentTermDays`/`IndividualAgreements` seit deren Einführung. Jetzt behoben, Regressionstest: `FacilityServiceTests.UpdateAsync_PersistsKonditionenFields`.
|
||||
- **Zugleich behobener Bestandsfehler:** `FacilityService.UpdateAsync` kopierte die elf Konditionsfelder bislang gar nicht auf die getrackte Entität — der Controller validierte sie korrekt, aber `PUT /api/facilities/{id}` verwarf sie stillschweigend (kein Fehler, kein Log, das Feld blieb einfach `null`/unverändert). Betraf `BillingRate`/alle vier Zuschläge/`TravelCostRate`/`MinimumHours`/`BillingInterval`/`PaymentTermDays`/`IndividualAgreements` seit deren Einführung (damals inkl. `BreakPolicy`, seit 2026-08-10 entfernt, siehe oben). Jetzt behoben, Regressionstest: `FacilityServiceTests.UpdateAsync_PersistsKonditionenFields`.
|
||||
- `BillingInterval` wird wie `FacilityType`/`ContractType` gegen die admin-editierbare `ValueList` `"BillingInterval"` (Wöchentlich/Monatlich/Quartalsweise) validiert — siehe "Konfigurierbare Auswahllisten".
|
||||
- Alle elf Felder sind nur über `PUT /api/facilities/{id}` (`UpdateFacilityRequest`) setzbar, nicht beim Anlegen (`CreateFacilityRequest`) — analog zu `CrmStatus`, der ebenfalls erst nach dem Anlegen über "Bearbeiten" gepflegt wird.
|
||||
- Alle zwölf Felder sind nur über `PUT /api/facilities/{id}` (`UpdateFacilityRequest`) setzbar, nicht beim Anlegen (`CreateFacilityRequest`) — analog zu `CrmStatus`, der ebenfalls erst nach dem Anlegen über "Bearbeiten" gepflegt wird.
|
||||
|
||||
**Ausnahme "Qualifikationsabhängige Preise":** eine variable Liste (ein Satz je Qualifikationsstufe) lässt sich nicht als feste Spaltengruppe abbilden — dafür die neue Entität `FacilityQualificationRate` (1:n zu `Facility`, `Qualification` gegen die ValueList `"Qualification"` validiert — dieselbe Liste wie `Employee.Qualification`/`Order.RequiredQualification`, siehe "Wo welches Feld referenziert wird" oben) als 1:n-Unterressource unter `GET/POST/PUT/DELETE /api/facilities/{facilityId}/qualification-rates[/...]` (`FacilityQualificationRatesController`) — exakt nach dem Muster von `FacilityContact`, kein eigener `ModuleType`, gegated über dieselben `Facilities`-Rechte. Löschen ist Soft-Delete, über `TrashController` (`api/trash/facility-qualification-rates/...`) wiederherstellbar.
|
||||
|
||||
|
||||
@@ -23,8 +23,8 @@ public record FacilityResponse(
|
||||
decimal? TravelCostRate,
|
||||
string TravelCostMode,
|
||||
decimal? TravelCostPerKm,
|
||||
decimal? MealAllowanceRate,
|
||||
decimal? MinimumHours,
|
||||
string? BreakPolicy,
|
||||
string? BillingInterval,
|
||||
int? PaymentTermDays,
|
||||
string? IndividualAgreements);
|
||||
|
||||
@@ -22,8 +22,8 @@ public record UpdateFacilityRequest(
|
||||
decimal? TravelCostRate,
|
||||
string TravelCostMode,
|
||||
decimal? TravelCostPerKm,
|
||||
decimal? MealAllowanceRate,
|
||||
decimal? MinimumHours,
|
||||
string? BreakPolicy,
|
||||
string? BillingInterval,
|
||||
int? PaymentTermDays,
|
||||
string? IndividualAgreements);
|
||||
|
||||
@@ -202,11 +202,6 @@ public class FacilitiesController : ControllerBase
|
||||
return BadRequest(billingAddressError);
|
||||
}
|
||||
|
||||
if (request.BreakPolicy is { Length: > 1000 })
|
||||
{
|
||||
return BadRequest("BreakPolicy darf maximal 1000 Zeichen lang sein.");
|
||||
}
|
||||
|
||||
if (request.IndividualAgreements is { Length: > 2000 })
|
||||
{
|
||||
return BadRequest("IndividualAgreements darf maximal 2000 Zeichen lang sein.");
|
||||
@@ -224,6 +219,7 @@ public class FacilitiesController : ControllerBase
|
||||
if (request.BillingRate is < 0
|
||||
|| request.TravelCostRate is < 0
|
||||
|| request.TravelCostPerKm is < 0
|
||||
|| request.MealAllowanceRate is < 0
|
||||
|| request.MinimumHours is < 0
|
||||
|| request.NightSurchargePercent is < 0
|
||||
|| request.SaturdaySurchargePercent is < 0
|
||||
@@ -252,8 +248,8 @@ public class FacilitiesController : ControllerBase
|
||||
TravelCostRate = request.TravelCostRate,
|
||||
TravelCostMode = request.TravelCostMode,
|
||||
TravelCostPerKm = request.TravelCostPerKm,
|
||||
MealAllowanceRate = request.MealAllowanceRate,
|
||||
MinimumHours = request.MinimumHours,
|
||||
BreakPolicy = request.BreakPolicy,
|
||||
BillingInterval = request.BillingInterval,
|
||||
PaymentTermDays = request.PaymentTermDays,
|
||||
IndividualAgreements = request.IndividualAgreements,
|
||||
@@ -330,8 +326,8 @@ public class FacilitiesController : ControllerBase
|
||||
facility.TravelCostRate,
|
||||
facility.TravelCostMode,
|
||||
facility.TravelCostPerKm,
|
||||
facility.MealAllowanceRate,
|
||||
facility.MinimumHours,
|
||||
facility.BreakPolicy,
|
||||
facility.BillingInterval,
|
||||
facility.PaymentTermDays,
|
||||
facility.IndividualAgreements);
|
||||
|
||||
@@ -63,8 +63,8 @@ public class FacilityService : IFacilityService
|
||||
facility.TravelCostRate = updates.TravelCostRate;
|
||||
facility.TravelCostMode = updates.TravelCostMode;
|
||||
facility.TravelCostPerKm = updates.TravelCostPerKm;
|
||||
facility.MealAllowanceRate = updates.MealAllowanceRate;
|
||||
facility.MinimumHours = updates.MinimumHours;
|
||||
facility.BreakPolicy = updates.BreakPolicy;
|
||||
facility.BillingInterval = updates.BillingInterval;
|
||||
facility.PaymentTermDays = updates.PaymentTermDays;
|
||||
facility.IndividualAgreements = updates.IndividualAgreements;
|
||||
|
||||
@@ -39,8 +39,8 @@ public class Facility : AuditableEntity
|
||||
public string TravelCostMode { get; set; } = "Pauschale";
|
||||
public decimal? TravelCostRate { get; set; }
|
||||
public decimal? TravelCostPerKm { get; set; }
|
||||
public decimal? MealAllowanceRate { get; set; }
|
||||
public decimal? MinimumHours { get; set; }
|
||||
public string? BreakPolicy { get; set; }
|
||||
public string? BillingInterval { get; set; }
|
||||
public int? PaymentTermDays { get; set; }
|
||||
public string? IndividualAgreements { get; set; }
|
||||
|
||||
+1
-1
@@ -31,8 +31,8 @@ public class FacilityConfiguration : IEntityTypeConfiguration<Facility>
|
||||
builder.Property(f => f.TravelCostRate).HasColumnType("decimal(10,2)");
|
||||
builder.Property(f => f.TravelCostMode).IsRequired().HasMaxLength(20);
|
||||
builder.Property(f => f.TravelCostPerKm).HasColumnType("decimal(10,2)");
|
||||
builder.Property(f => f.MealAllowanceRate).HasColumnType("decimal(10,2)");
|
||||
builder.Property(f => f.MinimumHours).HasColumnType("decimal(5,2)");
|
||||
builder.Property(f => f.BreakPolicy).HasMaxLength(1000);
|
||||
builder.Property(f => f.BillingInterval).HasMaxLength(50);
|
||||
builder.Property(f => f.IndividualAgreements).HasMaxLength(2000);
|
||||
|
||||
|
||||
+1388
File diff suppressed because it is too large
Load Diff
+29
@@ -0,0 +1,29 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace OmsorgCore.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class RemoveFacilityBreakPolicy : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BreakPolicy",
|
||||
table: "facilities");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "BreakPolicy",
|
||||
table: "facilities",
|
||||
type: "character varying(1000)",
|
||||
maxLength: 1000,
|
||||
nullable: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+1391
File diff suppressed because it is too large
Load Diff
+28
@@ -0,0 +1,28 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace OmsorgCore.Infrastructure.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddFacilityMealAllowanceRate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<decimal>(
|
||||
name: "MealAllowanceRate",
|
||||
table: "facilities",
|
||||
type: "numeric(10,2)",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "MealAllowanceRate",
|
||||
table: "facilities");
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-4
@@ -426,10 +426,6 @@ namespace OmsorgCore.Infrastructure.Persistence.Migrations
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<string>("BreakPolicy")
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<string>("City")
|
||||
.HasMaxLength(100)
|
||||
.HasColumnType("character varying(100)");
|
||||
@@ -466,6 +462,9 @@ namespace OmsorgCore.Infrastructure.Persistence.Migrations
|
||||
b.Property<bool>("IsDeleted")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<decimal?>("MealAllowanceRate")
|
||||
.HasColumnType("decimal(10,2)");
|
||||
|
||||
b.Property<decimal?>("MinimumHours")
|
||||
.HasColumnType("decimal(5,2)");
|
||||
|
||||
|
||||
@@ -103,8 +103,8 @@ public class FacilityServiceTests
|
||||
TravelCostMode = "ProKilometer",
|
||||
TravelCostRate = 10,
|
||||
TravelCostPerKm = 0.42m,
|
||||
MealAllowanceRate = 14,
|
||||
MinimumHours = 4,
|
||||
BreakPolicy = "30 Min. ab 6 Std.",
|
||||
BillingInterval = "Monatlich",
|
||||
PaymentTermDays = 14,
|
||||
IndividualAgreements = "Sonderkonditionen Wochenende"
|
||||
@@ -121,8 +121,8 @@ public class FacilityServiceTests
|
||||
Assert.Equal("ProKilometer", updated.TravelCostMode);
|
||||
Assert.Equal(10, updated.TravelCostRate);
|
||||
Assert.Equal(0.42m, updated.TravelCostPerKm);
|
||||
Assert.Equal(14, updated.MealAllowanceRate);
|
||||
Assert.Equal(4, updated.MinimumHours);
|
||||
Assert.Equal("30 Min. ab 6 Std.", updated.BreakPolicy);
|
||||
Assert.Equal("Monatlich", updated.BillingInterval);
|
||||
Assert.Equal(14, updated.PaymentTermDays);
|
||||
Assert.Equal("Sonderkonditionen Wochenende", updated.IndividualAgreements);
|
||||
|
||||
@@ -231,13 +231,3 @@ lib/Model/ValueListTransitionResponse.php
|
||||
lib/Model/ValueListUsageResponse.php
|
||||
lib/ObjectSerializer.php
|
||||
phpunit.xml.dist
|
||||
test/Api/EmployeeFacilityDistancesApiTest.php
|
||||
test/Api/MyFacilityDistancesApiTest.php
|
||||
test/Model/CreateEmployeeFacilityDistanceRequestTest.php
|
||||
test/Model/CreateMyFacilityDistanceRequestTest.php
|
||||
test/Model/EmployeeFacilityDistanceResponseTest.php
|
||||
test/Model/FacilityOptionResponseTest.php
|
||||
test/Model/MyFacilityDistanceResponseTest.php
|
||||
test/Model/TrashEmployeeFacilityDistanceResponseTest.php
|
||||
test/Model/UpdateEmployeeFacilityDistanceRequestTest.php
|
||||
test/Model/UpdateMyFacilityDistanceRequestTest.php
|
||||
|
||||
@@ -143,6 +143,7 @@ Class | Method | HTTP request | Description
|
||||
*OrdersApi* | [**apiOrdersIdPut**](docs/Api/OrdersApi.md#apiordersidput) | **PUT** /api/orders/{id} |
|
||||
*OrdersApi* | [**apiOrdersPost**](docs/Api/OrdersApi.md#apiorderspost) | **POST** /api/orders |
|
||||
*RolesApi* | [**apiRolesGet**](docs/Api/RolesApi.md#apirolesget) | **GET** /api/roles |
|
||||
*RolesApi* | [**apiRolesIdDelete**](docs/Api/RolesApi.md#apirolesiddelete) | **DELETE** /api/roles/{id} |
|
||||
*RolesApi* | [**apiRolesIdGet**](docs/Api/RolesApi.md#apirolesidget) | **GET** /api/roles/{id} |
|
||||
*RolesApi* | [**apiRolesIdPermissionsPut**](docs/Api/RolesApi.md#apirolesidpermissionsput) | **PUT** /api/roles/{id}/permissions |
|
||||
*RolesApi* | [**apiRolesPost**](docs/Api/RolesApi.md#apirolespost) | **POST** /api/roles |
|
||||
|
||||
@@ -5,6 +5,7 @@ All URIs are relative to http://localhost, except if the operation defines anoth
|
||||
| Method | HTTP request | Description |
|
||||
| ------------- | ------------- | ------------- |
|
||||
| [**apiRolesGet()**](RolesApi.md#apiRolesGet) | **GET** /api/roles | |
|
||||
| [**apiRolesIdDelete()**](RolesApi.md#apiRolesIdDelete) | **DELETE** /api/roles/{id} | |
|
||||
| [**apiRolesIdGet()**](RolesApi.md#apiRolesIdGet) | **GET** /api/roles/{id} | |
|
||||
| [**apiRolesIdPermissionsPut()**](RolesApi.md#apiRolesIdPermissionsPut) | **PUT** /api/roles/{id}/permissions | |
|
||||
| [**apiRolesPost()**](RolesApi.md#apiRolesPost) | **POST** /api/roles | |
|
||||
@@ -65,6 +66,63 @@ This endpoint does not need any parameter.
|
||||
[[Back to Model list]](../../README.md#models)
|
||||
[[Back to README]](../../README.md)
|
||||
|
||||
## `apiRolesIdDelete()`
|
||||
|
||||
```php
|
||||
apiRolesIdDelete($id)
|
||||
```
|
||||
|
||||
|
||||
|
||||
### Example
|
||||
|
||||
```php
|
||||
<?php
|
||||
require_once(__DIR__ . '/vendor/autoload.php');
|
||||
|
||||
|
||||
// Configure Bearer (JWT) authorization: Bearer
|
||||
$config = OmsorgCoreClient\Configuration::getDefaultConfiguration()->setAccessToken('YOUR_ACCESS_TOKEN');
|
||||
|
||||
|
||||
$apiInstance = new OmsorgCoreClient\Api\RolesApi(
|
||||
// If you want use custom http client, pass your client which implements `GuzzleHttp\ClientInterface`.
|
||||
// This is optional, `GuzzleHttp\Client` will be used as default.
|
||||
new GuzzleHttp\Client(),
|
||||
$config
|
||||
);
|
||||
$id = 'id_example'; // string
|
||||
|
||||
try {
|
||||
$apiInstance->apiRolesIdDelete($id);
|
||||
} catch (Exception $e) {
|
||||
echo 'Exception when calling RolesApi->apiRolesIdDelete: ', $e->getMessage(), PHP_EOL;
|
||||
}
|
||||
```
|
||||
|
||||
### Parameters
|
||||
|
||||
| Name | Type | Description | Notes |
|
||||
| ------------- | ------------- | ------------- | ------------- |
|
||||
| **id** | **string**| | |
|
||||
|
||||
### Return type
|
||||
|
||||
void (empty response body)
|
||||
|
||||
### Authorization
|
||||
|
||||
[Bearer](../../README.md#Bearer)
|
||||
|
||||
### HTTP request headers
|
||||
|
||||
- **Content-Type**: Not defined
|
||||
- **Accept**: Not defined
|
||||
|
||||
[[Back to top]](#) [[Back to API list]](../../README.md#endpoints)
|
||||
[[Back to Model list]](../../README.md#models)
|
||||
[[Back to README]](../../README.md)
|
||||
|
||||
## `apiRolesIdGet()`
|
||||
|
||||
```php
|
||||
|
||||
@@ -26,8 +26,8 @@ Name | Type | Description | Notes
|
||||
**travel_cost_rate** | **float** | | [optional]
|
||||
**travel_cost_mode** | **string** | | [optional]
|
||||
**travel_cost_per_km** | **float** | | [optional]
|
||||
**meal_allowance_rate** | **float** | | [optional]
|
||||
**minimum_hours** | **float** | | [optional]
|
||||
**break_policy** | **string** | | [optional]
|
||||
**billing_interval** | **string** | | [optional]
|
||||
**payment_term_days** | **int** | | [optional]
|
||||
**individual_agreements** | **string** | | [optional]
|
||||
|
||||
@@ -25,8 +25,8 @@ Name | Type | Description | Notes
|
||||
**travel_cost_rate** | **float** | | [optional]
|
||||
**travel_cost_mode** | **string** | | [optional]
|
||||
**travel_cost_per_km** | **float** | | [optional]
|
||||
**meal_allowance_rate** | **float** | | [optional]
|
||||
**minimum_hours** | **float** | | [optional]
|
||||
**break_policy** | **string** | | [optional]
|
||||
**billing_interval** | **string** | | [optional]
|
||||
**payment_term_days** | **int** | | [optional]
|
||||
**individual_agreements** | **string** | | [optional]
|
||||
|
||||
@@ -77,6 +77,9 @@ class RolesApi
|
||||
'apiRolesGet' => [
|
||||
'application/json',
|
||||
],
|
||||
'apiRolesIdDelete' => [
|
||||
'application/json',
|
||||
],
|
||||
'apiRolesIdGet' => [
|
||||
'application/json',
|
||||
],
|
||||
@@ -382,6 +385,220 @@ class RolesApi
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Operation apiRolesIdDelete
|
||||
*
|
||||
* @param string $id id (required)
|
||||
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiRolesIdDelete'] to see the possible values for this operation
|
||||
*
|
||||
* @throws \OmsorgCoreClient\ApiException on non-2xx response or if the response body is not in the expected format
|
||||
* @throws \InvalidArgumentException
|
||||
* @return void
|
||||
*/
|
||||
public function apiRolesIdDelete($id, string $contentType = self::contentTypes['apiRolesIdDelete'][0])
|
||||
{
|
||||
$this->apiRolesIdDeleteWithHttpInfo($id, $contentType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Operation apiRolesIdDeleteWithHttpInfo
|
||||
*
|
||||
* @param string $id (required)
|
||||
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiRolesIdDelete'] to see the possible values for this operation
|
||||
*
|
||||
* @throws \OmsorgCoreClient\ApiException on non-2xx response or if the response body is not in the expected format
|
||||
* @throws \InvalidArgumentException
|
||||
* @return array of null, HTTP status code, HTTP response headers (array of strings)
|
||||
*/
|
||||
public function apiRolesIdDeleteWithHttpInfo($id, string $contentType = self::contentTypes['apiRolesIdDelete'][0])
|
||||
{
|
||||
$request = $this->apiRolesIdDeleteRequest($id, $contentType);
|
||||
|
||||
try {
|
||||
$options = $this->createHttpClientOption();
|
||||
try {
|
||||
$response = $this->client->send($request, $options);
|
||||
} catch (RequestException $e) {
|
||||
throw new ApiException(
|
||||
"[{$e->getCode()}] {$e->getMessage()}",
|
||||
(int) $e->getCode(),
|
||||
$e->getResponse() ? $e->getResponse()->getHeaders() : null,
|
||||
$e->getResponse() ? (string) $e->getResponse()->getBody() : null
|
||||
);
|
||||
} catch (ConnectException $e) {
|
||||
throw new ApiException(
|
||||
"[{$e->getCode()}] {$e->getMessage()}",
|
||||
(int) $e->getCode(),
|
||||
null,
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
$statusCode = $response->getStatusCode();
|
||||
|
||||
|
||||
return [null, $statusCode, $response->getHeaders()];
|
||||
} catch (ApiException $e) {
|
||||
switch ($e->getCode()) {
|
||||
}
|
||||
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Operation apiRolesIdDeleteAsync
|
||||
*
|
||||
* @param string $id (required)
|
||||
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiRolesIdDelete'] to see the possible values for this operation
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @return \GuzzleHttp\Promise\PromiseInterface
|
||||
*/
|
||||
public function apiRolesIdDeleteAsync($id, string $contentType = self::contentTypes['apiRolesIdDelete'][0])
|
||||
{
|
||||
return $this->apiRolesIdDeleteAsyncWithHttpInfo($id, $contentType)
|
||||
->then(
|
||||
function ($response) {
|
||||
return $response[0];
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Operation apiRolesIdDeleteAsyncWithHttpInfo
|
||||
*
|
||||
* @param string $id (required)
|
||||
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiRolesIdDelete'] to see the possible values for this operation
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @return \GuzzleHttp\Promise\PromiseInterface
|
||||
*/
|
||||
public function apiRolesIdDeleteAsyncWithHttpInfo($id, string $contentType = self::contentTypes['apiRolesIdDelete'][0])
|
||||
{
|
||||
$returnType = '';
|
||||
$request = $this->apiRolesIdDeleteRequest($id, $contentType);
|
||||
|
||||
return $this->client
|
||||
->sendAsync($request, $this->createHttpClientOption())
|
||||
->then(
|
||||
function ($response) use ($returnType) {
|
||||
return [null, $response->getStatusCode(), $response->getHeaders()];
|
||||
},
|
||||
function ($exception) {
|
||||
$response = $exception->getResponse();
|
||||
$statusCode = $response->getStatusCode();
|
||||
throw new ApiException(
|
||||
sprintf(
|
||||
'[%d] Error connecting to the API (%s)',
|
||||
$statusCode,
|
||||
$exception->getRequest()->getUri()
|
||||
),
|
||||
$statusCode,
|
||||
$response->getHeaders(),
|
||||
(string) $response->getBody()
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create request for operation 'apiRolesIdDelete'
|
||||
*
|
||||
* @param string $id (required)
|
||||
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiRolesIdDelete'] to see the possible values for this operation
|
||||
*
|
||||
* @throws \InvalidArgumentException
|
||||
* @return \GuzzleHttp\Psr7\Request
|
||||
*/
|
||||
public function apiRolesIdDeleteRequest($id, string $contentType = self::contentTypes['apiRolesIdDelete'][0])
|
||||
{
|
||||
|
||||
// verify the required parameter 'id' is set
|
||||
if ($id === null || (is_array($id) && count($id) === 0)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Missing the required parameter $id when calling apiRolesIdDelete'
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$resourcePath = '/api/roles/{id}';
|
||||
$formParams = [];
|
||||
$queryParams = [];
|
||||
$headerParams = [];
|
||||
$httpBody = '';
|
||||
$multipart = false;
|
||||
|
||||
|
||||
|
||||
// path params
|
||||
if ($id !== null) {
|
||||
$resourcePath = str_replace(
|
||||
'{' . 'id' . '}',
|
||||
ObjectSerializer::toPathValue($id),
|
||||
$resourcePath
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$headers = $this->headerSelector->selectHeaders(
|
||||
[],
|
||||
$contentType,
|
||||
$multipart
|
||||
);
|
||||
|
||||
// for model (json/xml)
|
||||
if (count($formParams) > 0) {
|
||||
if ($multipart) {
|
||||
$multipartContents = [];
|
||||
foreach ($formParams as $formParamName => $formParamValue) {
|
||||
$formParamValueItems = is_array($formParamValue) ? $formParamValue : [$formParamValue];
|
||||
foreach ($formParamValueItems as $formParamValueItem) {
|
||||
$multipartContents[] = [
|
||||
'name' => $formParamName,
|
||||
'contents' => $formParamValueItem
|
||||
];
|
||||
}
|
||||
}
|
||||
// for HTTP post (form)
|
||||
$httpBody = new MultipartStream($multipartContents);
|
||||
|
||||
} elseif (stripos($headers['Content-Type'], 'application/json') !== false) {
|
||||
# if Content-Type contains "application/json", json_encode the form parameters
|
||||
$httpBody = \GuzzleHttp\Utils::jsonEncode($formParams);
|
||||
} else {
|
||||
// for HTTP post (form)
|
||||
$httpBody = ObjectSerializer::buildQuery($formParams);
|
||||
}
|
||||
}
|
||||
|
||||
// this endpoint requires Bearer (JWT) authentication (access token)
|
||||
if (!empty($this->config->getAccessToken())) {
|
||||
$headers['Authorization'] = 'Bearer ' . $this->config->getAccessToken();
|
||||
}
|
||||
|
||||
$defaultHeaders = [];
|
||||
if ($this->config->getUserAgent()) {
|
||||
$defaultHeaders['User-Agent'] = $this->config->getUserAgent();
|
||||
}
|
||||
|
||||
$headers = array_merge(
|
||||
$defaultHeaders,
|
||||
$headerParams,
|
||||
$headers
|
||||
);
|
||||
|
||||
$operationHost = $this->config->getHost();
|
||||
$query = ObjectSerializer::buildQuery($queryParams);
|
||||
return new Request(
|
||||
'DELETE',
|
||||
$operationHost . $resourcePath . ($query ? "?{$query}" : ''),
|
||||
$headers,
|
||||
$httpBody
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Operation apiRolesIdGet
|
||||
*
|
||||
|
||||
@@ -79,8 +79,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
|
||||
'travel_cost_rate' => 'float',
|
||||
'travel_cost_mode' => 'string',
|
||||
'travel_cost_per_km' => 'float',
|
||||
'meal_allowance_rate' => 'float',
|
||||
'minimum_hours' => 'float',
|
||||
'break_policy' => 'string',
|
||||
'billing_interval' => 'string',
|
||||
'payment_term_days' => 'int',
|
||||
'individual_agreements' => 'string'
|
||||
@@ -116,8 +116,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
|
||||
'travel_cost_rate' => 'double',
|
||||
'travel_cost_mode' => null,
|
||||
'travel_cost_per_km' => 'double',
|
||||
'meal_allowance_rate' => 'double',
|
||||
'minimum_hours' => 'double',
|
||||
'break_policy' => null,
|
||||
'billing_interval' => null,
|
||||
'payment_term_days' => 'int32',
|
||||
'individual_agreements' => null
|
||||
@@ -151,8 +151,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
|
||||
'travel_cost_rate' => true,
|
||||
'travel_cost_mode' => true,
|
||||
'travel_cost_per_km' => true,
|
||||
'meal_allowance_rate' => true,
|
||||
'minimum_hours' => true,
|
||||
'break_policy' => true,
|
||||
'billing_interval' => true,
|
||||
'payment_term_days' => true,
|
||||
'individual_agreements' => true
|
||||
@@ -266,8 +266,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
|
||||
'travel_cost_rate' => 'travelCostRate',
|
||||
'travel_cost_mode' => 'travelCostMode',
|
||||
'travel_cost_per_km' => 'travelCostPerKm',
|
||||
'meal_allowance_rate' => 'mealAllowanceRate',
|
||||
'minimum_hours' => 'minimumHours',
|
||||
'break_policy' => 'breakPolicy',
|
||||
'billing_interval' => 'billingInterval',
|
||||
'payment_term_days' => 'paymentTermDays',
|
||||
'individual_agreements' => 'individualAgreements'
|
||||
@@ -301,8 +301,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
|
||||
'travel_cost_rate' => 'setTravelCostRate',
|
||||
'travel_cost_mode' => 'setTravelCostMode',
|
||||
'travel_cost_per_km' => 'setTravelCostPerKm',
|
||||
'meal_allowance_rate' => 'setMealAllowanceRate',
|
||||
'minimum_hours' => 'setMinimumHours',
|
||||
'break_policy' => 'setBreakPolicy',
|
||||
'billing_interval' => 'setBillingInterval',
|
||||
'payment_term_days' => 'setPaymentTermDays',
|
||||
'individual_agreements' => 'setIndividualAgreements'
|
||||
@@ -336,8 +336,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
|
||||
'travel_cost_rate' => 'getTravelCostRate',
|
||||
'travel_cost_mode' => 'getTravelCostMode',
|
||||
'travel_cost_per_km' => 'getTravelCostPerKm',
|
||||
'meal_allowance_rate' => 'getMealAllowanceRate',
|
||||
'minimum_hours' => 'getMinimumHours',
|
||||
'break_policy' => 'getBreakPolicy',
|
||||
'billing_interval' => 'getBillingInterval',
|
||||
'payment_term_days' => 'getPaymentTermDays',
|
||||
'individual_agreements' => 'getIndividualAgreements'
|
||||
@@ -422,8 +422,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
|
||||
$this->setIfExists('travel_cost_rate', $data ?? [], null);
|
||||
$this->setIfExists('travel_cost_mode', $data ?? [], null);
|
||||
$this->setIfExists('travel_cost_per_km', $data ?? [], null);
|
||||
$this->setIfExists('meal_allowance_rate', $data ?? [], null);
|
||||
$this->setIfExists('minimum_hours', $data ?? [], null);
|
||||
$this->setIfExists('break_policy', $data ?? [], null);
|
||||
$this->setIfExists('billing_interval', $data ?? [], null);
|
||||
$this->setIfExists('payment_term_days', $data ?? [], null);
|
||||
$this->setIfExists('individual_agreements', $data ?? [], null);
|
||||
@@ -1212,6 +1212,40 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets meal_allowance_rate
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function getMealAllowanceRate()
|
||||
{
|
||||
return $this->container['meal_allowance_rate'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets meal_allowance_rate
|
||||
*
|
||||
* @param float|null $meal_allowance_rate meal_allowance_rate
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMealAllowanceRate($meal_allowance_rate)
|
||||
{
|
||||
if (is_null($meal_allowance_rate)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'meal_allowance_rate');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('meal_allowance_rate', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['meal_allowance_rate'] = $meal_allowance_rate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets minimum_hours
|
||||
*
|
||||
@@ -1246,40 +1280,6 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets break_policy
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getBreakPolicy()
|
||||
{
|
||||
return $this->container['break_policy'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets break_policy
|
||||
*
|
||||
* @param string|null $break_policy break_policy
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setBreakPolicy($break_policy)
|
||||
{
|
||||
if (is_null($break_policy)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'break_policy');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('break_policy', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['break_policy'] = $break_policy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets billing_interval
|
||||
*
|
||||
|
||||
@@ -78,8 +78,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'travel_cost_rate' => 'float',
|
||||
'travel_cost_mode' => 'string',
|
||||
'travel_cost_per_km' => 'float',
|
||||
'meal_allowance_rate' => 'float',
|
||||
'minimum_hours' => 'float',
|
||||
'break_policy' => 'string',
|
||||
'billing_interval' => 'string',
|
||||
'payment_term_days' => 'int',
|
||||
'individual_agreements' => 'string'
|
||||
@@ -114,8 +114,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'travel_cost_rate' => 'double',
|
||||
'travel_cost_mode' => null,
|
||||
'travel_cost_per_km' => 'double',
|
||||
'meal_allowance_rate' => 'double',
|
||||
'minimum_hours' => 'double',
|
||||
'break_policy' => null,
|
||||
'billing_interval' => null,
|
||||
'payment_term_days' => 'int32',
|
||||
'individual_agreements' => null
|
||||
@@ -148,8 +148,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'travel_cost_rate' => true,
|
||||
'travel_cost_mode' => true,
|
||||
'travel_cost_per_km' => true,
|
||||
'meal_allowance_rate' => true,
|
||||
'minimum_hours' => true,
|
||||
'break_policy' => true,
|
||||
'billing_interval' => true,
|
||||
'payment_term_days' => true,
|
||||
'individual_agreements' => true
|
||||
@@ -262,8 +262,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'travel_cost_rate' => 'travelCostRate',
|
||||
'travel_cost_mode' => 'travelCostMode',
|
||||
'travel_cost_per_km' => 'travelCostPerKm',
|
||||
'meal_allowance_rate' => 'mealAllowanceRate',
|
||||
'minimum_hours' => 'minimumHours',
|
||||
'break_policy' => 'breakPolicy',
|
||||
'billing_interval' => 'billingInterval',
|
||||
'payment_term_days' => 'paymentTermDays',
|
||||
'individual_agreements' => 'individualAgreements'
|
||||
@@ -296,8 +296,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'travel_cost_rate' => 'setTravelCostRate',
|
||||
'travel_cost_mode' => 'setTravelCostMode',
|
||||
'travel_cost_per_km' => 'setTravelCostPerKm',
|
||||
'meal_allowance_rate' => 'setMealAllowanceRate',
|
||||
'minimum_hours' => 'setMinimumHours',
|
||||
'break_policy' => 'setBreakPolicy',
|
||||
'billing_interval' => 'setBillingInterval',
|
||||
'payment_term_days' => 'setPaymentTermDays',
|
||||
'individual_agreements' => 'setIndividualAgreements'
|
||||
@@ -330,8 +330,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'travel_cost_rate' => 'getTravelCostRate',
|
||||
'travel_cost_mode' => 'getTravelCostMode',
|
||||
'travel_cost_per_km' => 'getTravelCostPerKm',
|
||||
'meal_allowance_rate' => 'getMealAllowanceRate',
|
||||
'minimum_hours' => 'getMinimumHours',
|
||||
'break_policy' => 'getBreakPolicy',
|
||||
'billing_interval' => 'getBillingInterval',
|
||||
'payment_term_days' => 'getPaymentTermDays',
|
||||
'individual_agreements' => 'getIndividualAgreements'
|
||||
@@ -415,8 +415,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
$this->setIfExists('travel_cost_rate', $data ?? [], null);
|
||||
$this->setIfExists('travel_cost_mode', $data ?? [], null);
|
||||
$this->setIfExists('travel_cost_per_km', $data ?? [], null);
|
||||
$this->setIfExists('meal_allowance_rate', $data ?? [], null);
|
||||
$this->setIfExists('minimum_hours', $data ?? [], null);
|
||||
$this->setIfExists('break_policy', $data ?? [], null);
|
||||
$this->setIfExists('billing_interval', $data ?? [], null);
|
||||
$this->setIfExists('payment_term_days', $data ?? [], null);
|
||||
$this->setIfExists('individual_agreements', $data ?? [], null);
|
||||
@@ -1178,6 +1178,40 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets meal_allowance_rate
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function getMealAllowanceRate()
|
||||
{
|
||||
return $this->container['meal_allowance_rate'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets meal_allowance_rate
|
||||
*
|
||||
* @param float|null $meal_allowance_rate meal_allowance_rate
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMealAllowanceRate($meal_allowance_rate)
|
||||
{
|
||||
if (is_null($meal_allowance_rate)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'meal_allowance_rate');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('meal_allowance_rate', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['meal_allowance_rate'] = $meal_allowance_rate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets minimum_hours
|
||||
*
|
||||
@@ -1212,40 +1246,6 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets break_policy
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getBreakPolicy()
|
||||
{
|
||||
return $this->container['break_policy'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets break_policy
|
||||
*
|
||||
* @param string|null $break_policy break_policy
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setBreakPolicy($break_policy)
|
||||
{
|
||||
if (is_null($break_policy)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'break_policy');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('break_policy', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['break_policy'] = $break_policy;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets billing_interval
|
||||
*
|
||||
|
||||
@@ -156,13 +156,13 @@ export interface FacilityResponse {
|
||||
* @type {number}
|
||||
* @memberof FacilityResponse
|
||||
*/
|
||||
minimumHours?: number | null;
|
||||
mealAllowanceRate?: number | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @type {number}
|
||||
* @memberof FacilityResponse
|
||||
*/
|
||||
breakPolicy?: string | null;
|
||||
minimumHours?: number | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
@@ -222,8 +222,8 @@ export function FacilityResponseFromJSONTyped(json: any, ignoreDiscriminator: bo
|
||||
'travelCostRate': json['travelCostRate'] == null ? undefined : json['travelCostRate'],
|
||||
'travelCostMode': json['travelCostMode'] == null ? undefined : json['travelCostMode'],
|
||||
'travelCostPerKm': json['travelCostPerKm'] == null ? undefined : json['travelCostPerKm'],
|
||||
'mealAllowanceRate': json['mealAllowanceRate'] == null ? undefined : json['mealAllowanceRate'],
|
||||
'minimumHours': json['minimumHours'] == null ? undefined : json['minimumHours'],
|
||||
'breakPolicy': json['breakPolicy'] == null ? undefined : json['breakPolicy'],
|
||||
'billingInterval': json['billingInterval'] == null ? undefined : json['billingInterval'],
|
||||
'paymentTermDays': json['paymentTermDays'] == null ? undefined : json['paymentTermDays'],
|
||||
'individualAgreements': json['individualAgreements'] == null ? undefined : json['individualAgreements'],
|
||||
@@ -263,8 +263,8 @@ export function FacilityResponseToJSONTyped(value?: FacilityResponse | null, ign
|
||||
'travelCostRate': value['travelCostRate'],
|
||||
'travelCostMode': value['travelCostMode'],
|
||||
'travelCostPerKm': value['travelCostPerKm'],
|
||||
'mealAllowanceRate': value['mealAllowanceRate'],
|
||||
'minimumHours': value['minimumHours'],
|
||||
'breakPolicy': value['breakPolicy'],
|
||||
'billingInterval': value['billingInterval'],
|
||||
'paymentTermDays': value['paymentTermDays'],
|
||||
'individualAgreements': value['individualAgreements'],
|
||||
|
||||
@@ -150,13 +150,13 @@ export interface UpdateFacilityRequest {
|
||||
* @type {number}
|
||||
* @memberof UpdateFacilityRequest
|
||||
*/
|
||||
minimumHours?: number | null;
|
||||
mealAllowanceRate?: number | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
* @type {number}
|
||||
* @memberof UpdateFacilityRequest
|
||||
*/
|
||||
breakPolicy?: string | null;
|
||||
minimumHours?: number | null;
|
||||
/**
|
||||
*
|
||||
* @type {string}
|
||||
@@ -215,8 +215,8 @@ export function UpdateFacilityRequestFromJSONTyped(json: any, ignoreDiscriminato
|
||||
'travelCostRate': json['travelCostRate'] == null ? undefined : json['travelCostRate'],
|
||||
'travelCostMode': json['travelCostMode'] == null ? undefined : json['travelCostMode'],
|
||||
'travelCostPerKm': json['travelCostPerKm'] == null ? undefined : json['travelCostPerKm'],
|
||||
'mealAllowanceRate': json['mealAllowanceRate'] == null ? undefined : json['mealAllowanceRate'],
|
||||
'minimumHours': json['minimumHours'] == null ? undefined : json['minimumHours'],
|
||||
'breakPolicy': json['breakPolicy'] == null ? undefined : json['breakPolicy'],
|
||||
'billingInterval': json['billingInterval'] == null ? undefined : json['billingInterval'],
|
||||
'paymentTermDays': json['paymentTermDays'] == null ? undefined : json['paymentTermDays'],
|
||||
'individualAgreements': json['individualAgreements'] == null ? undefined : json['individualAgreements'],
|
||||
@@ -255,8 +255,8 @@ export function UpdateFacilityRequestToJSONTyped(value?: UpdateFacilityRequest |
|
||||
'travelCostRate': value['travelCostRate'],
|
||||
'travelCostMode': value['travelCostMode'],
|
||||
'travelCostPerKm': value['travelCostPerKm'],
|
||||
'mealAllowanceRate': value['mealAllowanceRate'],
|
||||
'minimumHours': value['minimumHours'],
|
||||
'breakPolicy': value['breakPolicy'],
|
||||
'billingInterval': value['billingInterval'],
|
||||
'paymentTermDays': value['paymentTermDays'],
|
||||
'individualAgreements': value['individualAgreements'],
|
||||
|
||||
@@ -21,20 +21,31 @@ export const emptyEmployeeForm = {
|
||||
qualification: "",
|
||||
};
|
||||
|
||||
// Der generierte Client wandelt Datumsfelder aus der API-Antwort automatisch in Date-Objekte um
|
||||
// (siehe employeesApi.js) - <input type="date"> braucht dafür einen "YYYY-MM-DD"-String, sonst
|
||||
// bleibt das Feld leer (Muster analog AbsenceForm.jsx/OrderForm.jsx/TimeEntryForm.jsx).
|
||||
function toDateInputValue(value) {
|
||||
if (!value) {
|
||||
return "";
|
||||
}
|
||||
const date = value instanceof Date ? value : new Date(value);
|
||||
return Number.isNaN(date.getTime()) ? "" : date.toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
export function employeeToFormValues(employee) {
|
||||
return {
|
||||
firstName: employee.firstName ?? "",
|
||||
lastName: employee.lastName ?? "",
|
||||
status: employee.status ?? "Aktiv",
|
||||
dateOfBirth: employee.dateOfBirth ?? "",
|
||||
dateOfBirth: toDateInputValue(employee.dateOfBirth),
|
||||
street: employee.street ?? "",
|
||||
postalCode: employee.postalCode ?? "",
|
||||
city: employee.city ?? "",
|
||||
country: employee.country ?? "",
|
||||
phoneNumber: employee.phoneNumber ?? "",
|
||||
email: employee.email ?? "",
|
||||
entryDate: employee.entryDate ?? "",
|
||||
exitDate: employee.exitDate ?? "",
|
||||
entryDate: toDateInputValue(employee.entryDate),
|
||||
exitDate: toDateInputValue(employee.exitDate),
|
||||
emergencyContactName: employee.emergencyContactName ?? "",
|
||||
emergencyContactPhone: employee.emergencyContactPhone ?? "",
|
||||
emergencyContactRelation: employee.emergencyContactRelation ?? "",
|
||||
|
||||
@@ -151,13 +151,13 @@ export default function FacilityDetailPanel({ facility, onUpdated }) {
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Mindeststunden</strong>
|
||||
<p>{facility.minimumHours ?? "—"}</p>
|
||||
<strong>Verpflegungsmehraufwand</strong>
|
||||
<p>{facility.mealAllowanceRate != null ? `${facility.mealAllowanceRate} EUR (Pauschale je Einsatz)` : "—"}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Pausenregelung</strong>
|
||||
<p>{facility.breakPolicy ?? "—"}</p>
|
||||
<strong>Mindeststunden</strong>
|
||||
<p>{facility.minimumHours ?? "—"}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -24,8 +24,8 @@ export const emptyFacilityForm = {
|
||||
travelCostMode: "Pauschale",
|
||||
travelCostRate: "",
|
||||
travelCostPerKm: "",
|
||||
mealAllowanceRate: "",
|
||||
minimumHours: "",
|
||||
breakPolicy: "",
|
||||
billingInterval: "",
|
||||
paymentTermDays: "",
|
||||
individualAgreements: "",
|
||||
@@ -53,8 +53,8 @@ export function facilityToFormValues(facility) {
|
||||
travelCostMode: facility.travelCostMode ?? "Pauschale",
|
||||
travelCostRate: facility.travelCostRate ?? "",
|
||||
travelCostPerKm: facility.travelCostPerKm ?? "",
|
||||
mealAllowanceRate: facility.mealAllowanceRate ?? "",
|
||||
minimumHours: facility.minimumHours ?? "",
|
||||
breakPolicy: facility.breakPolicy ?? "",
|
||||
billingInterval: facility.billingInterval ?? "",
|
||||
paymentTermDays: facility.paymentTermDays ?? "",
|
||||
individualAgreements: facility.individualAgreements ?? "",
|
||||
@@ -87,8 +87,8 @@ export function facilityFormToPayload(form, { includeCrmStatus = false, followUp
|
||||
payload.travelCostMode = form.travelCostMode || "Pauschale";
|
||||
payload.travelCostRate = form.travelCostRate === "" ? null : Number(form.travelCostRate);
|
||||
payload.travelCostPerKm = form.travelCostPerKm === "" ? null : Number(form.travelCostPerKm);
|
||||
payload.mealAllowanceRate = form.mealAllowanceRate === "" ? null : Number(form.mealAllowanceRate);
|
||||
payload.minimumHours = form.minimumHours === "" ? null : Number(form.minimumHours);
|
||||
payload.breakPolicy = form.breakPolicy.trim() || null;
|
||||
payload.billingInterval = form.billingInterval || null;
|
||||
payload.paymentTermDays = form.paymentTermDays === "" ? null : Number(form.paymentTermDays);
|
||||
payload.individualAgreements = form.individualAgreements.trim() || null;
|
||||
@@ -282,10 +282,12 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
|
||||
)}
|
||||
</div>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Pausenregelung</span>
|
||||
<input value={form.breakPolicy} onChange={updateField("breakPolicy")} />
|
||||
</label>
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
<span>Verpflegungsmehraufwand (EUR, Pauschale je Einsatz)</span>
|
||||
<input type="number" min="0" step="0.01" value={form.mealAllowanceRate} onChange={updateField("mealAllowanceRate")} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { Cake } from "lucide-react";
|
||||
|
||||
import OmsorgCard from "../../components/OmsorgCard";
|
||||
import { useAuth } from "../../app/AuthContext";
|
||||
|
||||
const WIDGET_PAGE_SIZE = 200;
|
||||
const REMINDER_DAYS_AHEAD = 5;
|
||||
|
||||
// Nächstes Vorkommen des Geburtstags (dieses Jahr, oder nächstes Jahr falls schon vorbei) - Alter
|
||||
// spielt für die Erinnerung keine Rolle, nur der Tag/Monat.
|
||||
function daysUntilNextBirthday(dateOfBirth) {
|
||||
const birth = dateOfBirth instanceof Date ? dateOfBirth : new Date(dateOfBirth);
|
||||
if (Number.isNaN(birth.getTime())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
|
||||
let next = new Date(today.getFullYear(), birth.getMonth(), birth.getDate());
|
||||
next.setHours(0, 0, 0, 0);
|
||||
if (next < today) {
|
||||
next = new Date(today.getFullYear() + 1, birth.getMonth(), birth.getDate());
|
||||
}
|
||||
|
||||
return Math.round((next - today) / (1000 * 60 * 60 * 24));
|
||||
}
|
||||
|
||||
function remainingLabel(days) {
|
||||
if (days === 0) {
|
||||
return "heute";
|
||||
}
|
||||
if (days === 1) {
|
||||
return "morgen";
|
||||
}
|
||||
return `in ${days} Tagen`;
|
||||
}
|
||||
|
||||
export default function BirthdayWidget() {
|
||||
const { hasPermission } = useAuth();
|
||||
const canView = hasPermission("Employees", "View");
|
||||
|
||||
const [employees, setEmployees] = useState([]);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canView) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let cancelled = false;
|
||||
|
||||
async function load() {
|
||||
setIsLoading(true);
|
||||
const result = await window.omsorg.employees.list({ status: "Aktiv", page: 1, pageSize: WIDGET_PAGE_SIZE });
|
||||
|
||||
if (!cancelled) {
|
||||
setEmployees(result.ok ? result.data?.items ?? [] : []);
|
||||
setIsLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
load();
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [canView]);
|
||||
|
||||
if (!canView) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const upcoming = employees
|
||||
.filter((employee) => employee.dateOfBirth)
|
||||
.map((employee) => ({ employee, days: daysUntilNextBirthday(employee.dateOfBirth) }))
|
||||
.filter(({ days }) => days !== null && days >= 0 && days <= REMINDER_DAYS_AHEAD)
|
||||
.sort((a, b) => a.days - b.days);
|
||||
|
||||
return (
|
||||
<OmsorgCard title="Anstehende Geburtstage">
|
||||
<div className="contract-widget">
|
||||
<div className="contract-widget__intro">
|
||||
<Cake size={20} />
|
||||
|
||||
<p>Mitarbeiter, die in den nächsten {REMINDER_DAYS_AHEAD} Tagen Geburtstag haben.</p>
|
||||
</div>
|
||||
|
||||
{!isLoading && upcoming.length === 0 && <p>Keine anstehenden Geburtstage.</p>}
|
||||
|
||||
<div className="contract-widget__list">
|
||||
{upcoming.map(({ employee, days }) => (
|
||||
<article key={employee.id} className={`contract-item contract-item--${days === 0 ? "danger" : "info"}`}>
|
||||
<div className="contract-item__header">
|
||||
<div>
|
||||
<strong>
|
||||
{employee.firstName} {employee.lastName}
|
||||
</strong>
|
||||
</div>
|
||||
|
||||
<strong>{remainingLabel(days)}</strong>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</OmsorgCard>
|
||||
);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import HomeStats from "./HomeStats";
|
||||
import OmsorgBadge from "../../components/ui/OmsorgBadge";
|
||||
import FollowUpWidget from "./FollowUpWidget";
|
||||
import OrderStatusWidget from "./OrderStatusWidget";
|
||||
import BirthdayWidget from "./BirthdayWidget";
|
||||
export default function HomePage() {
|
||||
return (
|
||||
<>
|
||||
@@ -31,6 +32,7 @@ export default function HomePage() {
|
||||
<section className="dashboard-grid home-grid">
|
||||
<FollowUpWidget />
|
||||
<OrderStatusWidget />
|
||||
<BirthdayWidget />
|
||||
|
||||
<OmsorgCard title="Schnellaktionen">
|
||||
<div className="home-actions">
|
||||
|
||||
Reference in New Issue
Block a user