Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e09c1111c0 | ||
|
|
e98834d7de | ||
|
|
e1fbfadd17 | ||
|
|
52fbec3214 | ||
|
|
fd20b07e83 | ||
|
|
b11dc41310 | ||
|
|
cae5e7ed67 | ||
|
|
d16a1a058e |
@@ -0,0 +1,12 @@
|
||||
# Email configuration for SMTP-based password reset mail delivery
|
||||
# Copy this file to .env and fill in the actual values (especially EMAIL_PASSWORD_RESET_PASSWORD)
|
||||
# The .env file is loaded automatically by docker compose and is git-ignored to keep credentials safe
|
||||
|
||||
EMAIL_PROVIDER=Smtp
|
||||
EMAIL_SMTP_HOST=smtps.udag.de
|
||||
EMAIL_SMTP_PORT=587
|
||||
EMAIL_PASSWORD_RESET_USERNAME=passwort@omsorg-pflegedienste.de
|
||||
EMAIL_PASSWORD_RESET_PASSWORD=
|
||||
EMAIL_PASSWORD_RESET_SENDER=passwort@omsorg-pflegedienste.de
|
||||
EMAIL_PASSWORD_RESET_SUBJECT=Ihr OMSORG Passwort-Reset-Code
|
||||
EMAIL_PASSWORD_RESET_BODY_TEMPLATE=Ihr Code lautet: $$RESET_PIN$$\nDer Code ist $$RESET_PIN_EXPIRY_MINUTES$$ Minuten gültig.
|
||||
@@ -1,4 +1,5 @@
|
||||
# Config with credentials
|
||||
.env
|
||||
**/mitarbeiter-app*/lib/config.php
|
||||
**/mitarbeiter-app*/lib/config.secret.php
|
||||
|
||||
|
||||
@@ -75,6 +75,14 @@ services:
|
||||
ConnectionStrings__OmsorgCore: "Host=postgres;Port=5432;Database=omsorg_core;Username=omsorg_core;Password=omsorg_core_dev_password"
|
||||
Jwt__Secret: "CHANGE_ME_LOCAL_DEV_SECRET_MIN_32_CHARS_LONG"
|
||||
Cors__AllowedOrigins__0: "https://app.omsorg-pflegedienste.de"
|
||||
Email__Provider: "${EMAIL_PROVIDER}"
|
||||
Email__Smtp__Host: "${EMAIL_SMTP_HOST}"
|
||||
Email__Smtp__Port: "${EMAIL_SMTP_PORT}"
|
||||
Email__Accounts__PasswordReset__Username: "${EMAIL_PASSWORD_RESET_USERNAME}"
|
||||
Email__Accounts__PasswordReset__Password: "${EMAIL_PASSWORD_RESET_PASSWORD}"
|
||||
Email__Accounts__PasswordReset__SenderAddress: "${EMAIL_PASSWORD_RESET_SENDER}"
|
||||
Email__PasswordResetTemplate__Subject: "${EMAIL_PASSWORD_RESET_SUBJECT}"
|
||||
Email__PasswordResetTemplate__BodyTemplate: "${EMAIL_PASSWORD_RESET_BODY_TEMPLATE}"
|
||||
ports:
|
||||
# Nur auf dem Host erreichbar (für den Reverse-Proxy oben), nie auf der öffentlichen
|
||||
# Schnittstelle - matcht das 127.0.0.1:5173-Muster im bestehenden Host-nginx.
|
||||
|
||||
@@ -240,7 +240,7 @@ Zehn der zwölf Blueprint-19.2-Konditionsfelder (Verrechnungssatz, vier Zuschlä
|
||||
- **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`/`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 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.
|
||||
- Alle zwölf Felder sind **auch beim Anlegen** über `CreateFacilityRequest` setzbar (zusätzlich zu `UpdateFacilityRequest`). `CreateFacilityRequest` enthält die gleichen Konditionsfelder wie `UpdateFacilityRequest` — `CrmStatus` ist Pflicht beim Anlegen. Die Validierung für diese Felder (Wertebereich, Allowlists, FollowUp-Logik) läuft in beiden Cases, nur die `CanTransitionAsync`-Prüfung ("erlauber Statuswechsel") entfällt beim Anlegen, da es dort keinen Vorzustand gibt — jeder aktive CRM-Status ist beim Anlegen wählbar.
|
||||
|
||||
**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.
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ namespace OmsorgCore.Api.Contracts;
|
||||
|
||||
public record CreateFacilityRequest(
|
||||
string Name,
|
||||
string CrmStatus,
|
||||
string? FacilityType,
|
||||
string? Website,
|
||||
string? Street,
|
||||
@@ -11,4 +12,18 @@ public record CreateFacilityRequest(
|
||||
string? BillingStreet,
|
||||
string? BillingPostalCode,
|
||||
string? BillingCity,
|
||||
string? BillingCountry);
|
||||
string? BillingCountry,
|
||||
int? FollowUpDays,
|
||||
decimal? BillingRate,
|
||||
decimal? NightSurchargePercent,
|
||||
decimal? SaturdaySurchargePercent,
|
||||
decimal? SundaySurchargePercent,
|
||||
decimal? HolidaySurchargePercent,
|
||||
decimal? TravelCostRate,
|
||||
string TravelCostMode,
|
||||
decimal? TravelCostPerKm,
|
||||
decimal? MealAllowanceRate,
|
||||
decimal? MinimumHours,
|
||||
string? BillingInterval,
|
||||
int? PaymentTermDays,
|
||||
string? IndividualAgreements);
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace OmsorgCore.Api.Controllers;
|
||||
[Route("api/facilities")]
|
||||
public class FacilitiesController : ControllerBase
|
||||
{
|
||||
private record CrmStatusValidationResult(string? ErrorMessage, DateTime? FollowUpDueDate);
|
||||
|
||||
private const string CrmStatusListKey = "CrmStatus";
|
||||
private const string FacilityTypeListKey = "FacilityType";
|
||||
private const string FollowUpPeriodsListKey = "FollowUpPeriods";
|
||||
@@ -66,6 +68,11 @@ public class FacilitiesController : ControllerBase
|
||||
return BadRequest("Name ist erforderlich und darf maximal 300 Zeichen lang sein.");
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(request.CrmStatus) || request.CrmStatus.Length > 50)
|
||||
{
|
||||
return BadRequest("CrmStatus ist erforderlich und darf maximal 50 Zeichen lang sein.");
|
||||
}
|
||||
|
||||
if (request.FacilityType is { Length: > 100 })
|
||||
{
|
||||
return BadRequest("FacilityType darf maximal 100 Zeichen lang sein.");
|
||||
@@ -97,9 +104,67 @@ public class FacilitiesController : ControllerBase
|
||||
return BadRequest(billingAddressError);
|
||||
}
|
||||
|
||||
var crmStatusValidationError = await ValidateCrmStatusAndFollowUpAsync(
|
||||
request.CrmStatus,
|
||||
request.FollowUpDays,
|
||||
existingCrmStatus: null,
|
||||
cancellationToken);
|
||||
if (crmStatusValidationError.ErrorMessage is not null)
|
||||
{
|
||||
return BadRequest(crmStatusValidationError.ErrorMessage);
|
||||
}
|
||||
|
||||
if (request.IndividualAgreements is { Length: > 2000 })
|
||||
{
|
||||
return BadRequest("IndividualAgreements darf maximal 2000 Zeichen lang sein.");
|
||||
}
|
||||
|
||||
if (request.BillingInterval is not null)
|
||||
{
|
||||
var allowedBillingIntervals = await _valueListRepository.GetActiveValuesAsync(BillingIntervalListKey, cancellationToken);
|
||||
if (!allowedBillingIntervals.Contains(request.BillingInterval))
|
||||
{
|
||||
return BadRequest($"BillingInterval muss einer der folgenden Werte sein: {string.Join(", ", allowedBillingIntervals)}.");
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|| request.SundaySurchargePercent is < 0
|
||||
|| request.HolidaySurchargePercent is < 0
|
||||
|| request.PaymentTermDays is < 0)
|
||||
{
|
||||
return BadRequest("Konditionswerte dürfen nicht negativ sein.");
|
||||
}
|
||||
|
||||
if (!TravelCostModes.Contains(request.TravelCostMode))
|
||||
{
|
||||
return BadRequest($"TravelCostMode muss einer der folgenden Werte sein: {string.Join(", ", TravelCostModes)}.");
|
||||
}
|
||||
|
||||
var facility = new Facility
|
||||
{
|
||||
Name = request.Name,
|
||||
CrmStatus = request.CrmStatus,
|
||||
FollowUpDueDate = crmStatusValidationError.FollowUpDueDate,
|
||||
BillingRate = request.BillingRate,
|
||||
NightSurchargePercent = request.NightSurchargePercent,
|
||||
SaturdaySurchargePercent = request.SaturdaySurchargePercent,
|
||||
SundaySurchargePercent = request.SundaySurchargePercent,
|
||||
HolidaySurchargePercent = request.HolidaySurchargePercent,
|
||||
TravelCostRate = request.TravelCostRate,
|
||||
TravelCostMode = request.TravelCostMode,
|
||||
TravelCostPerKm = request.TravelCostPerKm,
|
||||
MealAllowanceRate = request.MealAllowanceRate,
|
||||
MinimumHours = request.MinimumHours,
|
||||
BillingInterval = request.BillingInterval,
|
||||
PaymentTermDays = request.PaymentTermDays,
|
||||
IndividualAgreements = request.IndividualAgreements,
|
||||
FacilityType = request.FacilityType,
|
||||
Website = request.Website,
|
||||
Street = request.Street,
|
||||
@@ -138,38 +203,21 @@ public class FacilitiesController : ControllerBase
|
||||
return BadRequest("CrmStatus ist erforderlich und darf maximal 50 Zeichen lang sein.");
|
||||
}
|
||||
|
||||
var crmStatusItems = await _valueListRepository.GetItemsAsync(CrmStatusListKey, cancellationToken);
|
||||
var selectedCrmStatusItem = crmStatusItems.FirstOrDefault(i => i.Value == request.CrmStatus);
|
||||
if (selectedCrmStatusItem is null)
|
||||
var crmStatusValidationError = await ValidateCrmStatusAndFollowUpAsync(
|
||||
request.CrmStatus,
|
||||
request.FollowUpDays,
|
||||
existingCrmStatus: existing.CrmStatus,
|
||||
cancellationToken);
|
||||
if (crmStatusValidationError.ErrorMessage is not null)
|
||||
{
|
||||
return BadRequest($"CrmStatus muss einer der folgenden Werte sein: {string.Join(", ", crmStatusItems.Select(i => i.Value))}.");
|
||||
return BadRequest(crmStatusValidationError.ErrorMessage);
|
||||
}
|
||||
|
||||
var currentCrmStatusItem = crmStatusItems.FirstOrDefault(i => i.Value == existing.CrmStatus);
|
||||
if (currentCrmStatusItem is not null
|
||||
&& !await _valueListRepository.CanTransitionAsync(currentCrmStatusItem.Id, selectedCrmStatusItem.Id, cancellationToken))
|
||||
{
|
||||
return BadRequest("Der Statuswechsel ist nicht zulässig.");
|
||||
}
|
||||
|
||||
DateTime? followUpDueDate = null;
|
||||
if (selectedCrmStatusItem.TriggersFollowUp)
|
||||
{
|
||||
if (existing.CrmStatus == request.CrmStatus)
|
||||
var followUpDueDate = crmStatusValidationError.FollowUpDueDate;
|
||||
if (followUpDueDate is null && existing.CrmStatus == request.CrmStatus)
|
||||
{
|
||||
followUpDueDate = existing.FollowUpDueDate;
|
||||
}
|
||||
else
|
||||
{
|
||||
var allowedFollowUpPeriods = await _valueListRepository.GetActiveValuesAsync(FollowUpPeriodsListKey, cancellationToken);
|
||||
if (request.FollowUpDays is null || !allowedFollowUpPeriods.Contains(request.FollowUpDays.Value.ToString()))
|
||||
{
|
||||
return BadRequest($"FollowUpDays ist bei CrmStatus \"{request.CrmStatus}\" erforderlich und muss einer der folgenden Werte sein: {string.Join(", ", allowedFollowUpPeriods)}.");
|
||||
}
|
||||
|
||||
followUpDueDate = DateTime.UtcNow.AddDays(request.FollowUpDays.Value);
|
||||
}
|
||||
}
|
||||
|
||||
if (request.FacilityType is { Length: > 100 })
|
||||
{
|
||||
@@ -277,6 +325,51 @@ public class FacilitiesController : ControllerBase
|
||||
return deleted ? NoContent() : NotFound();
|
||||
}
|
||||
|
||||
private async Task<CrmStatusValidationResult> ValidateCrmStatusAndFollowUpAsync(
|
||||
string crmStatus,
|
||||
int? followUpDays,
|
||||
string? existingCrmStatus,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var crmStatusItems = await _valueListRepository.GetItemsAsync(CrmStatusListKey, cancellationToken);
|
||||
var selectedCrmStatusItem = crmStatusItems.FirstOrDefault(i => i.Value == crmStatus);
|
||||
if (selectedCrmStatusItem is null)
|
||||
{
|
||||
return new($"CrmStatus muss einer der folgenden Werte sein: {string.Join(", ", crmStatusItems.Select(i => i.Value))}.", null);
|
||||
}
|
||||
|
||||
if (existingCrmStatus is not null)
|
||||
{
|
||||
var currentCrmStatusItem = crmStatusItems.FirstOrDefault(i => i.Value == existingCrmStatus);
|
||||
if (currentCrmStatusItem is not null
|
||||
&& !await _valueListRepository.CanTransitionAsync(currentCrmStatusItem.Id, selectedCrmStatusItem.Id, cancellationToken))
|
||||
{
|
||||
return new("Der Statuswechsel ist nicht zulässig.", null);
|
||||
}
|
||||
}
|
||||
|
||||
DateTime? followUpDueDate = null;
|
||||
if (selectedCrmStatusItem.TriggersFollowUp)
|
||||
{
|
||||
if (existingCrmStatus is not null && existingCrmStatus == crmStatus)
|
||||
{
|
||||
followUpDueDate = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
var allowedFollowUpPeriods = await _valueListRepository.GetActiveValuesAsync(FollowUpPeriodsListKey, cancellationToken);
|
||||
if (followUpDays is null || !allowedFollowUpPeriods.Contains(followUpDays.Value.ToString()))
|
||||
{
|
||||
return new($"FollowUpDays ist bei CrmStatus \"{crmStatus}\" erforderlich und muss einer der folgenden Werte sein: {string.Join(", ", allowedFollowUpPeriods)}.", null);
|
||||
}
|
||||
|
||||
followUpDueDate = DateTime.UtcNow.AddDays(followUpDays.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return new(null, followUpDueDate);
|
||||
}
|
||||
|
||||
private static string? ValidateAddressFields(string? street, string? postalCode, string? city, string? country, string prefix)
|
||||
{
|
||||
if (street is { Length: > 200 })
|
||||
|
||||
@@ -31,21 +31,27 @@ html{scroll-behavior:smooth;background:#031522;overflow-x:hidden}
|
||||
body{
|
||||
margin:0;min-height:100vh;color:var(--text);
|
||||
font-family:'Visby',Arial,Helvetica,sans-serif;line-height:1.45;
|
||||
background:#031522 url('assets/omsorg-bg-full.png') center top/cover fixed no-repeat;
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 15% 10%,
|
||||
rgba(16, 215, 230, 0.22),
|
||||
transparent 33%
|
||||
),
|
||||
radial-gradient(
|
||||
circle at 85% 85%,
|
||||
rgba(18, 88, 99, 0.32),
|
||||
transparent 38%
|
||||
),
|
||||
linear-gradient(
|
||||
135deg,
|
||||
#031522 0%,
|
||||
#032a36 38%,
|
||||
#0a3a47 68%,
|
||||
#031522 100%
|
||||
);
|
||||
background-attachment:fixed;
|
||||
overflow-x:hidden;
|
||||
}
|
||||
body::before{
|
||||
content:"";position:fixed;inset:0;z-index:-2;
|
||||
background:linear-gradient(180deg,rgba(0,9,23,.08),rgba(0,9,23,.28) 58%,rgba(0,9,23,.52));
|
||||
pointer-events:none;
|
||||
}
|
||||
body::after{
|
||||
content:"";position:fixed;inset:0;z-index:-1;opacity:.35;
|
||||
background-image:radial-gradient(circle,rgba(0,216,230,.75) 0 2px,transparent 3px);
|
||||
background-size:24px 24px;
|
||||
mask-image:radial-gradient(circle at 0% 34%,#000 0 0,transparent 18%),
|
||||
radial-gradient(circle at 100% 88%,#000 0 0,transparent 17%);
|
||||
pointer-events:none;
|
||||
-webkit-font-smoothing:antialiased;
|
||||
}
|
||||
a{color:inherit;text-decoration:none}
|
||||
img{display:block;max-width:100%}
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
# ref: https://github.com/github/gitignore/blob/master/Composer.gitignore
|
||||
|
||||
composer.phar
|
||||
|
||||
# vendor/ ist bewusst NICHT ausgeschlossen (Standard-Composer-.gitignore normalerweise schon) -
|
||||
# dieser generierte Client wird ohne composer install deployt/containerisiert, siehe
|
||||
# omsorgCore/CLAUDE.md "Generierte API-Clients". Nach jedem `generate.sh`-Lauf wird diese Datei
|
||||
# vom Generator überschrieben, diese Zeile muss dann jedes Mal erneut entfernt werden (git diff
|
||||
# prüfen, analog zum generate-Script in omsorgapp/api-client-ts/package.json).
|
||||
/vendor/
|
||||
|
||||
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
|
||||
# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
Name | Type | Description | Notes
|
||||
------------ | ------------- | ------------- | -------------
|
||||
**name** | **string** | | [optional]
|
||||
**crm_status** | **string** | | [optional]
|
||||
**facility_type** | **string** | | [optional]
|
||||
**website** | **string** | | [optional]
|
||||
**street** | **string** | | [optional]
|
||||
@@ -15,5 +16,19 @@ Name | Type | Description | Notes
|
||||
**billing_postal_code** | **string** | | [optional]
|
||||
**billing_city** | **string** | | [optional]
|
||||
**billing_country** | **string** | | [optional]
|
||||
**follow_up_days** | **int** | | [optional]
|
||||
**billing_rate** | **float** | | [optional]
|
||||
**night_surcharge_percent** | **float** | | [optional]
|
||||
**saturday_surcharge_percent** | **float** | | [optional]
|
||||
**sunday_surcharge_percent** | **float** | | [optional]
|
||||
**holiday_surcharge_percent** | **float** | | [optional]
|
||||
**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]
|
||||
**billing_interval** | **string** | | [optional]
|
||||
**payment_term_days** | **int** | | [optional]
|
||||
**individual_agreements** | **string** | | [optional]
|
||||
|
||||
[[Back to Model list]](../../README.md#models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md)
|
||||
|
||||
@@ -58,6 +58,7 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
*/
|
||||
protected static $openAPITypes = [
|
||||
'name' => 'string',
|
||||
'crm_status' => 'string',
|
||||
'facility_type' => 'string',
|
||||
'website' => 'string',
|
||||
'street' => 'string',
|
||||
@@ -67,7 +68,21 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'billing_street' => 'string',
|
||||
'billing_postal_code' => 'string',
|
||||
'billing_city' => 'string',
|
||||
'billing_country' => 'string'
|
||||
'billing_country' => 'string',
|
||||
'follow_up_days' => 'int',
|
||||
'billing_rate' => 'float',
|
||||
'night_surcharge_percent' => 'float',
|
||||
'saturday_surcharge_percent' => 'float',
|
||||
'sunday_surcharge_percent' => 'float',
|
||||
'holiday_surcharge_percent' => 'float',
|
||||
'travel_cost_rate' => 'float',
|
||||
'travel_cost_mode' => 'string',
|
||||
'travel_cost_per_km' => 'float',
|
||||
'meal_allowance_rate' => 'float',
|
||||
'minimum_hours' => 'float',
|
||||
'billing_interval' => 'string',
|
||||
'payment_term_days' => 'int',
|
||||
'individual_agreements' => 'string'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -79,6 +94,7 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
*/
|
||||
protected static $openAPIFormats = [
|
||||
'name' => null,
|
||||
'crm_status' => null,
|
||||
'facility_type' => null,
|
||||
'website' => null,
|
||||
'street' => null,
|
||||
@@ -88,7 +104,21 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'billing_street' => null,
|
||||
'billing_postal_code' => null,
|
||||
'billing_city' => null,
|
||||
'billing_country' => null
|
||||
'billing_country' => null,
|
||||
'follow_up_days' => 'int32',
|
||||
'billing_rate' => 'double',
|
||||
'night_surcharge_percent' => 'double',
|
||||
'saturday_surcharge_percent' => 'double',
|
||||
'sunday_surcharge_percent' => 'double',
|
||||
'holiday_surcharge_percent' => 'double',
|
||||
'travel_cost_rate' => 'double',
|
||||
'travel_cost_mode' => null,
|
||||
'travel_cost_per_km' => 'double',
|
||||
'meal_allowance_rate' => 'double',
|
||||
'minimum_hours' => 'double',
|
||||
'billing_interval' => null,
|
||||
'payment_term_days' => 'int32',
|
||||
'individual_agreements' => null
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -98,6 +128,7 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
*/
|
||||
protected static array $openAPINullables = [
|
||||
'name' => true,
|
||||
'crm_status' => true,
|
||||
'facility_type' => true,
|
||||
'website' => true,
|
||||
'street' => true,
|
||||
@@ -107,7 +138,21 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'billing_street' => true,
|
||||
'billing_postal_code' => true,
|
||||
'billing_city' => true,
|
||||
'billing_country' => true
|
||||
'billing_country' => true,
|
||||
'follow_up_days' => true,
|
||||
'billing_rate' => true,
|
||||
'night_surcharge_percent' => true,
|
||||
'saturday_surcharge_percent' => true,
|
||||
'sunday_surcharge_percent' => true,
|
||||
'holiday_surcharge_percent' => true,
|
||||
'travel_cost_rate' => true,
|
||||
'travel_cost_mode' => true,
|
||||
'travel_cost_per_km' => true,
|
||||
'meal_allowance_rate' => true,
|
||||
'minimum_hours' => true,
|
||||
'billing_interval' => true,
|
||||
'payment_term_days' => true,
|
||||
'individual_agreements' => true
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -197,6 +242,7 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
*/
|
||||
protected static $attributeMap = [
|
||||
'name' => 'name',
|
||||
'crm_status' => 'crmStatus',
|
||||
'facility_type' => 'facilityType',
|
||||
'website' => 'website',
|
||||
'street' => 'street',
|
||||
@@ -206,7 +252,21 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'billing_street' => 'billingStreet',
|
||||
'billing_postal_code' => 'billingPostalCode',
|
||||
'billing_city' => 'billingCity',
|
||||
'billing_country' => 'billingCountry'
|
||||
'billing_country' => 'billingCountry',
|
||||
'follow_up_days' => 'followUpDays',
|
||||
'billing_rate' => 'billingRate',
|
||||
'night_surcharge_percent' => 'nightSurchargePercent',
|
||||
'saturday_surcharge_percent' => 'saturdaySurchargePercent',
|
||||
'sunday_surcharge_percent' => 'sundaySurchargePercent',
|
||||
'holiday_surcharge_percent' => 'holidaySurchargePercent',
|
||||
'travel_cost_rate' => 'travelCostRate',
|
||||
'travel_cost_mode' => 'travelCostMode',
|
||||
'travel_cost_per_km' => 'travelCostPerKm',
|
||||
'meal_allowance_rate' => 'mealAllowanceRate',
|
||||
'minimum_hours' => 'minimumHours',
|
||||
'billing_interval' => 'billingInterval',
|
||||
'payment_term_days' => 'paymentTermDays',
|
||||
'individual_agreements' => 'individualAgreements'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -216,6 +276,7 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
*/
|
||||
protected static $setters = [
|
||||
'name' => 'setName',
|
||||
'crm_status' => 'setCrmStatus',
|
||||
'facility_type' => 'setFacilityType',
|
||||
'website' => 'setWebsite',
|
||||
'street' => 'setStreet',
|
||||
@@ -225,7 +286,21 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'billing_street' => 'setBillingStreet',
|
||||
'billing_postal_code' => 'setBillingPostalCode',
|
||||
'billing_city' => 'setBillingCity',
|
||||
'billing_country' => 'setBillingCountry'
|
||||
'billing_country' => 'setBillingCountry',
|
||||
'follow_up_days' => 'setFollowUpDays',
|
||||
'billing_rate' => 'setBillingRate',
|
||||
'night_surcharge_percent' => 'setNightSurchargePercent',
|
||||
'saturday_surcharge_percent' => 'setSaturdaySurchargePercent',
|
||||
'sunday_surcharge_percent' => 'setSundaySurchargePercent',
|
||||
'holiday_surcharge_percent' => 'setHolidaySurchargePercent',
|
||||
'travel_cost_rate' => 'setTravelCostRate',
|
||||
'travel_cost_mode' => 'setTravelCostMode',
|
||||
'travel_cost_per_km' => 'setTravelCostPerKm',
|
||||
'meal_allowance_rate' => 'setMealAllowanceRate',
|
||||
'minimum_hours' => 'setMinimumHours',
|
||||
'billing_interval' => 'setBillingInterval',
|
||||
'payment_term_days' => 'setPaymentTermDays',
|
||||
'individual_agreements' => 'setIndividualAgreements'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -235,6 +310,7 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
*/
|
||||
protected static $getters = [
|
||||
'name' => 'getName',
|
||||
'crm_status' => 'getCrmStatus',
|
||||
'facility_type' => 'getFacilityType',
|
||||
'website' => 'getWebsite',
|
||||
'street' => 'getStreet',
|
||||
@@ -244,7 +320,21 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
'billing_street' => 'getBillingStreet',
|
||||
'billing_postal_code' => 'getBillingPostalCode',
|
||||
'billing_city' => 'getBillingCity',
|
||||
'billing_country' => 'getBillingCountry'
|
||||
'billing_country' => 'getBillingCountry',
|
||||
'follow_up_days' => 'getFollowUpDays',
|
||||
'billing_rate' => 'getBillingRate',
|
||||
'night_surcharge_percent' => 'getNightSurchargePercent',
|
||||
'saturday_surcharge_percent' => 'getSaturdaySurchargePercent',
|
||||
'sunday_surcharge_percent' => 'getSundaySurchargePercent',
|
||||
'holiday_surcharge_percent' => 'getHolidaySurchargePercent',
|
||||
'travel_cost_rate' => 'getTravelCostRate',
|
||||
'travel_cost_mode' => 'getTravelCostMode',
|
||||
'travel_cost_per_km' => 'getTravelCostPerKm',
|
||||
'meal_allowance_rate' => 'getMealAllowanceRate',
|
||||
'minimum_hours' => 'getMinimumHours',
|
||||
'billing_interval' => 'getBillingInterval',
|
||||
'payment_term_days' => 'getPaymentTermDays',
|
||||
'individual_agreements' => 'getIndividualAgreements'
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -305,6 +395,7 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
public function __construct(?array $data = null)
|
||||
{
|
||||
$this->setIfExists('name', $data ?? [], null);
|
||||
$this->setIfExists('crm_status', $data ?? [], null);
|
||||
$this->setIfExists('facility_type', $data ?? [], null);
|
||||
$this->setIfExists('website', $data ?? [], null);
|
||||
$this->setIfExists('street', $data ?? [], null);
|
||||
@@ -315,6 +406,20 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
$this->setIfExists('billing_postal_code', $data ?? [], null);
|
||||
$this->setIfExists('billing_city', $data ?? [], null);
|
||||
$this->setIfExists('billing_country', $data ?? [], null);
|
||||
$this->setIfExists('follow_up_days', $data ?? [], null);
|
||||
$this->setIfExists('billing_rate', $data ?? [], null);
|
||||
$this->setIfExists('night_surcharge_percent', $data ?? [], null);
|
||||
$this->setIfExists('saturday_surcharge_percent', $data ?? [], null);
|
||||
$this->setIfExists('sunday_surcharge_percent', $data ?? [], null);
|
||||
$this->setIfExists('holiday_surcharge_percent', $data ?? [], null);
|
||||
$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('billing_interval', $data ?? [], null);
|
||||
$this->setIfExists('payment_term_days', $data ?? [], null);
|
||||
$this->setIfExists('individual_agreements', $data ?? [], null);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -393,6 +498,40 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets crm_status
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getCrmStatus()
|
||||
{
|
||||
return $this->container['crm_status'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets crm_status
|
||||
*
|
||||
* @param string|null $crm_status crm_status
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setCrmStatus($crm_status)
|
||||
{
|
||||
if (is_null($crm_status)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'crm_status');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('crm_status', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['crm_status'] = $crm_status;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets facility_type
|
||||
*
|
||||
@@ -732,6 +871,482 @@ class CreateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets follow_up_days
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getFollowUpDays()
|
||||
{
|
||||
return $this->container['follow_up_days'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets follow_up_days
|
||||
*
|
||||
* @param int|null $follow_up_days follow_up_days
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setFollowUpDays($follow_up_days)
|
||||
{
|
||||
if (is_null($follow_up_days)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'follow_up_days');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('follow_up_days', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['follow_up_days'] = $follow_up_days;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets billing_rate
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function getBillingRate()
|
||||
{
|
||||
return $this->container['billing_rate'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets billing_rate
|
||||
*
|
||||
* @param float|null $billing_rate billing_rate
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setBillingRate($billing_rate)
|
||||
{
|
||||
if (is_null($billing_rate)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'billing_rate');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('billing_rate', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['billing_rate'] = $billing_rate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets night_surcharge_percent
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function getNightSurchargePercent()
|
||||
{
|
||||
return $this->container['night_surcharge_percent'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets night_surcharge_percent
|
||||
*
|
||||
* @param float|null $night_surcharge_percent night_surcharge_percent
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setNightSurchargePercent($night_surcharge_percent)
|
||||
{
|
||||
if (is_null($night_surcharge_percent)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'night_surcharge_percent');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('night_surcharge_percent', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['night_surcharge_percent'] = $night_surcharge_percent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets saturday_surcharge_percent
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function getSaturdaySurchargePercent()
|
||||
{
|
||||
return $this->container['saturday_surcharge_percent'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets saturday_surcharge_percent
|
||||
*
|
||||
* @param float|null $saturday_surcharge_percent saturday_surcharge_percent
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSaturdaySurchargePercent($saturday_surcharge_percent)
|
||||
{
|
||||
if (is_null($saturday_surcharge_percent)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'saturday_surcharge_percent');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('saturday_surcharge_percent', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['saturday_surcharge_percent'] = $saturday_surcharge_percent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets sunday_surcharge_percent
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function getSundaySurchargePercent()
|
||||
{
|
||||
return $this->container['sunday_surcharge_percent'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets sunday_surcharge_percent
|
||||
*
|
||||
* @param float|null $sunday_surcharge_percent sunday_surcharge_percent
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setSundaySurchargePercent($sunday_surcharge_percent)
|
||||
{
|
||||
if (is_null($sunday_surcharge_percent)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'sunday_surcharge_percent');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('sunday_surcharge_percent', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['sunday_surcharge_percent'] = $sunday_surcharge_percent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets holiday_surcharge_percent
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function getHolidaySurchargePercent()
|
||||
{
|
||||
return $this->container['holiday_surcharge_percent'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets holiday_surcharge_percent
|
||||
*
|
||||
* @param float|null $holiday_surcharge_percent holiday_surcharge_percent
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setHolidaySurchargePercent($holiday_surcharge_percent)
|
||||
{
|
||||
if (is_null($holiday_surcharge_percent)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'holiday_surcharge_percent');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('holiday_surcharge_percent', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['holiday_surcharge_percent'] = $holiday_surcharge_percent;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets travel_cost_rate
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function getTravelCostRate()
|
||||
{
|
||||
return $this->container['travel_cost_rate'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets travel_cost_rate
|
||||
*
|
||||
* @param float|null $travel_cost_rate travel_cost_rate
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setTravelCostRate($travel_cost_rate)
|
||||
{
|
||||
if (is_null($travel_cost_rate)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'travel_cost_rate');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('travel_cost_rate', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['travel_cost_rate'] = $travel_cost_rate;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets travel_cost_mode
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getTravelCostMode()
|
||||
{
|
||||
return $this->container['travel_cost_mode'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets travel_cost_mode
|
||||
*
|
||||
* @param string|null $travel_cost_mode travel_cost_mode
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setTravelCostMode($travel_cost_mode)
|
||||
{
|
||||
if (is_null($travel_cost_mode)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'travel_cost_mode');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('travel_cost_mode', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['travel_cost_mode'] = $travel_cost_mode;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets travel_cost_per_km
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function getTravelCostPerKm()
|
||||
{
|
||||
return $this->container['travel_cost_per_km'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets travel_cost_per_km
|
||||
*
|
||||
* @param float|null $travel_cost_per_km travel_cost_per_km
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setTravelCostPerKm($travel_cost_per_km)
|
||||
{
|
||||
if (is_null($travel_cost_per_km)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'travel_cost_per_km');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('travel_cost_per_km', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['travel_cost_per_km'] = $travel_cost_per_km;
|
||||
|
||||
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
|
||||
*
|
||||
* @return float|null
|
||||
*/
|
||||
public function getMinimumHours()
|
||||
{
|
||||
return $this->container['minimum_hours'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets minimum_hours
|
||||
*
|
||||
* @param float|null $minimum_hours minimum_hours
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setMinimumHours($minimum_hours)
|
||||
{
|
||||
if (is_null($minimum_hours)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'minimum_hours');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('minimum_hours', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['minimum_hours'] = $minimum_hours;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets billing_interval
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getBillingInterval()
|
||||
{
|
||||
return $this->container['billing_interval'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets billing_interval
|
||||
*
|
||||
* @param string|null $billing_interval billing_interval
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setBillingInterval($billing_interval)
|
||||
{
|
||||
if (is_null($billing_interval)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'billing_interval');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('billing_interval', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['billing_interval'] = $billing_interval;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets payment_term_days
|
||||
*
|
||||
* @return int|null
|
||||
*/
|
||||
public function getPaymentTermDays()
|
||||
{
|
||||
return $this->container['payment_term_days'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets payment_term_days
|
||||
*
|
||||
* @param int|null $payment_term_days payment_term_days
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setPaymentTermDays($payment_term_days)
|
||||
{
|
||||
if (is_null($payment_term_days)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'payment_term_days');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('payment_term_days', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['payment_term_days'] = $payment_term_days;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets individual_agreements
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function getIndividualAgreements()
|
||||
{
|
||||
return $this->container['individual_agreements'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets individual_agreements
|
||||
*
|
||||
* @param string|null $individual_agreements individual_agreements
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function setIndividualAgreements($individual_agreements)
|
||||
{
|
||||
if (is_null($individual_agreements)) {
|
||||
array_push($this->openAPINullablesSetToNull, 'individual_agreements');
|
||||
} else {
|
||||
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
|
||||
$index = array_search('individual_agreements', $nullablesSetToNull);
|
||||
if ($index !== FALSE) {
|
||||
unset($nullablesSetToNull[$index]);
|
||||
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
|
||||
}
|
||||
}
|
||||
$this->container['individual_agreements'] = $individual_agreements;
|
||||
|
||||
return $this;
|
||||
}
|
||||
/**
|
||||
* Returns true if offset exists. False otherwise.
|
||||
*
|
||||
|
||||
@@ -13,7 +13,25 @@ html{background:#031522}
|
||||
body{
|
||||
margin:0;min-height:100vh;color:var(--text);
|
||||
font-family:Arial,Helvetica,sans-serif;line-height:1.45;
|
||||
background:#031522;
|
||||
background:
|
||||
radial-gradient(
|
||||
circle at 15% 10%,
|
||||
rgba(16, 215, 230, 0.22),
|
||||
transparent 33%
|
||||
),
|
||||
radial-gradient(
|
||||
circle at 85% 85%,
|
||||
rgba(18, 88, 99, 0.32),
|
||||
transparent 38%
|
||||
),
|
||||
linear-gradient(
|
||||
135deg,
|
||||
#031522 0%,
|
||||
#032a36 38%,
|
||||
#0a3a47 68%,
|
||||
#031522 100%
|
||||
);
|
||||
background-attachment:fixed;
|
||||
}
|
||||
a{color:inherit;text-decoration:none}
|
||||
img{display:block;max-width:100%}
|
||||
|
||||
@@ -12,9 +12,9 @@
|
||||
"module": "./dist/esm/index.js",
|
||||
"sideEffects": false,
|
||||
"scripts": {
|
||||
"generate": "bash generate.sh",
|
||||
"build": "tsc && tsc -p tsconfig.esm.json",
|
||||
"prepare": "npm run build",
|
||||
"generate": "./generate.sh"
|
||||
"prepare": "npm run build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^4.0 || ^5.0"
|
||||
|
||||
@@ -41,7 +41,14 @@ export async function me(accessToken) {
|
||||
const api = new AuthApi(configFor(accessToken));
|
||||
const result = await callApi(api.apiAuthMeGetRaw());
|
||||
if (!result.ok) return { ok: false, status: result.status };
|
||||
return { ok: true, username: result.data.username, role: result.data.role, permissions: result.data.permissions };
|
||||
return {
|
||||
ok: true,
|
||||
username: result.data.username,
|
||||
firstName: result.data.firstName,
|
||||
lastName: result.data.lastName,
|
||||
role: result.data.role,
|
||||
permissions: result.data.permissions
|
||||
};
|
||||
}
|
||||
|
||||
export async function requestPasswordReset(username) {
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
import { Bell, LogOut, Search, UserCircle2 } from "lucide-react";
|
||||
import { Bell, LogOut, UserCircle2 } from "lucide-react";
|
||||
import { useAuth } from "../app/AuthContext";
|
||||
|
||||
export default function Header() {
|
||||
const { user, logout } = useAuth();
|
||||
const displayName = user?.firstName || user?.username || "Benutzer";
|
||||
|
||||
return (
|
||||
<header className="header">
|
||||
|
||||
<div className="header-left">
|
||||
|
||||
<h1>Guten Morgen Sabina & Malik ☀️</h1>
|
||||
<h1>Guten Morgen {displayName} ☀️</h1>
|
||||
|
||||
<p>
|
||||
Schön, dass ihr heute wieder da seid.
|
||||
@@ -19,10 +20,6 @@ export default function Header() {
|
||||
|
||||
<div className="header-right">
|
||||
|
||||
<button className="icon-button">
|
||||
<Search size={20} />
|
||||
</button>
|
||||
|
||||
<button className="icon-button notification">
|
||||
|
||||
<Bell size={20} />
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/**
|
||||
* ==========================================================
|
||||
* OMSORG BUSINESS CONTROLS PRO
|
||||
* Component: DecimalInput
|
||||
* Sprint: 0.2.0
|
||||
* ==========================================================
|
||||
*/
|
||||
|
||||
import { sanitizeDecimalInput } from "../../utils/germanDecimal";
|
||||
|
||||
export default function DecimalInput({
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
required = false,
|
||||
disabled = false,
|
||||
id,
|
||||
}) {
|
||||
function handleChange(event) {
|
||||
onChange(sanitizeDecimalInput(event.target.value));
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
type="text"
|
||||
inputMode="decimal"
|
||||
pattern="[0-9]*[.,]?[0-9]*"
|
||||
value={value}
|
||||
onChange={handleChange}
|
||||
placeholder={placeholder}
|
||||
required={required}
|
||||
disabled={disabled}
|
||||
id={id}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import OmsorgButton from "../../components/ui/OmsorgButton";
|
||||
import DecimalInput from "../../components/ui/DecimalInput";
|
||||
import { useValueListItems } from "../../app/useValueListItems";
|
||||
import { parseGermanDecimal } from "../../utils/germanDecimal";
|
||||
|
||||
export const emptyContractForm = {
|
||||
contractType: "",
|
||||
@@ -36,8 +38,8 @@ export function contractFormToPayload(form, { employeeId, includeStatus = false
|
||||
facilityId: null,
|
||||
startDate: form.startDate,
|
||||
endDate: form.endDate || null,
|
||||
weeklyHours: form.weeklyHours === "" ? null : Number(form.weeklyHours),
|
||||
hourlyWage: form.hourlyWage === "" ? null : Number(form.hourlyWage),
|
||||
weeklyHours: parseGermanDecimal(form.weeklyHours),
|
||||
hourlyWage: parseGermanDecimal(form.hourlyWage),
|
||||
allowancesDescription: form.allowancesDescription.trim() || null,
|
||||
overtimeRules: form.overtimeRules.trim() || null,
|
||||
vacationDaysPerYear: form.vacationDaysPerYear === "" ? null : Number(form.vacationDaysPerYear),
|
||||
@@ -59,6 +61,10 @@ export default function ContractForm({ form, onChange, includeStatus = false })
|
||||
return (event) => onChange({ ...form, [field]: event.target.value });
|
||||
}
|
||||
|
||||
function updateDecimalField(field) {
|
||||
return (rawValue) => onChange({ ...form, [field]: rawValue });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="form-grid">
|
||||
<label className="form-field">
|
||||
@@ -101,12 +107,12 @@ export default function ContractForm({ form, onChange, includeStatus = false })
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
<span>Arbeitszeit (Std./Woche)</span>
|
||||
<input type="number" min="0" step="0.5" value={form.weeklyHours} onChange={updateField("weeklyHours")} />
|
||||
<DecimalInput value={form.weeklyHours} onChange={updateDecimalField("weeklyHours")} />
|
||||
</label>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Stundenlohn (€)</span>
|
||||
<input type="number" min="0" step="0.01" value={form.hourlyWage} onChange={updateField("hourlyWage")} />
|
||||
<DecimalInput value={form.hourlyWage} onChange={updateDecimalField("hourlyWage")} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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>}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Pencil, Trash2 } from "lucide-react";
|
||||
import OmsorgCard from "../../components/OmsorgCard";
|
||||
import OmsorgButton from "../../components/ui/OmsorgButton";
|
||||
import { useAuth } from "../../app/AuthContext";
|
||||
import { formatGermanDecimal } from "../../utils/germanDecimal";
|
||||
import EditFacilityDialog from "./EditFacilityDialog";
|
||||
import FacilityContactsList from "./FacilityContactsList";
|
||||
import FacilityQualificationRatesList from "./FacilityQualificationRatesList";
|
||||
@@ -126,14 +127,14 @@ export default function FacilityDetailPanel({ facility, onUpdated }) {
|
||||
|
||||
<div>
|
||||
<strong>Verrechnungssatz</strong>
|
||||
<p>{facility.billingRate != null ? `${facility.billingRate} EUR/Std.` : "—"}</p>
|
||||
<p>{facility.billingRate != null ? `${formatGermanDecimal(facility.billingRate)} EUR/Std.` : "—"}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Zuschläge</strong>
|
||||
<p>
|
||||
Nacht {facility.nightSurchargePercent ?? "—"}% · Sa {facility.saturdaySurchargePercent ?? "—"}% ·
|
||||
So {facility.sundaySurchargePercent ?? "—"}% · Feiertag {facility.holidaySurchargePercent ?? "—"}%
|
||||
Nacht {facility.nightSurchargePercent != null ? formatGermanDecimal(facility.nightSurchargePercent) : "—"}% · Sa {facility.saturdaySurchargePercent != null ? formatGermanDecimal(facility.saturdaySurchargePercent) : "—"}% ·
|
||||
So {facility.sundaySurchargePercent != null ? formatGermanDecimal(facility.sundaySurchargePercent) : "—"}% · Feiertag {facility.holidaySurchargePercent != null ? formatGermanDecimal(facility.holidaySurchargePercent) : "—"}%
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -142,22 +143,22 @@ export default function FacilityDetailPanel({ facility, onUpdated }) {
|
||||
<p>
|
||||
{facility.travelCostMode === "ProKilometer"
|
||||
? facility.travelCostPerKm != null
|
||||
? `${facility.travelCostPerKm} EUR/km`
|
||||
? `${formatGermanDecimal(facility.travelCostPerKm)} EUR/km`
|
||||
: "—"
|
||||
: facility.travelCostRate != null
|
||||
? `${facility.travelCostRate} EUR (Pauschale je Einsatz)`
|
||||
? `${formatGermanDecimal(facility.travelCostRate)} EUR (Pauschale je Einsatz)`
|
||||
: "—"}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Verpflegungsmehraufwand</strong>
|
||||
<p>{facility.mealAllowanceRate != null ? `${facility.mealAllowanceRate} EUR (Pauschale je Einsatz)` : "—"}</p>
|
||||
<p>{facility.mealAllowanceRate != null ? `${formatGermanDecimal(facility.mealAllowanceRate)} EUR (Pauschale je Einsatz)` : "—"}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<strong>Mindeststunden</strong>
|
||||
<p>{facility.minimumHours ?? "—"}</p>
|
||||
<p>{facility.minimumHours != null ? formatGermanDecimal(facility.minimumHours) : "—"}</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import OmsorgButton from "../../components/ui/OmsorgButton";
|
||||
import DecimalInput from "../../components/ui/DecimalInput";
|
||||
import { useValueListItems } from "../../app/useValueListItems";
|
||||
import { parseGermanDecimal } from "../../utils/germanDecimal";
|
||||
|
||||
export const emptyFacilityForm = {
|
||||
name: "",
|
||||
@@ -61,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,
|
||||
@@ -74,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 = form.billingRate === "" ? null : Number(form.billingRate);
|
||||
payload.nightSurchargePercent = form.nightSurchargePercent === "" ? null : Number(form.nightSurchargePercent);
|
||||
payload.saturdaySurchargePercent = form.saturdaySurchargePercent === "" ? null : Number(form.saturdaySurchargePercent);
|
||||
payload.sundaySurchargePercent = form.sundaySurchargePercent === "" ? null : Number(form.sundaySurchargePercent);
|
||||
payload.holidaySurchargePercent = form.holidaySurchargePercent === "" ? null : Number(form.holidaySurchargePercent);
|
||||
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.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) {
|
||||
@@ -116,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
|
||||
@@ -131,6 +126,10 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
|
||||
return (event) => onChange({ ...form, [field]: event.target.value });
|
||||
}
|
||||
|
||||
function updateDecimalField(field) {
|
||||
return (rawValue) => onChange({ ...form, [field]: rawValue });
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="form-grid">
|
||||
<label className="form-field">
|
||||
@@ -138,7 +137,6 @@ 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")}>
|
||||
@@ -149,7 +147,6 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
)}
|
||||
|
||||
<label className="form-field">
|
||||
<span>Art der Einrichtung</span>
|
||||
@@ -220,36 +217,35 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
|
||||
</label>
|
||||
</fieldset>
|
||||
|
||||
{includeCrmStatus && (
|
||||
<fieldset className="form-field form-field--fieldset">
|
||||
<legend>Konditionen</legend>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Verrechnungssatz (EUR/Std.)</span>
|
||||
<input type="number" min="0" step="0.01" value={form.billingRate} onChange={updateField("billingRate")} />
|
||||
<DecimalInput value={form.billingRate} onChange={updateDecimalField("billingRate")} />
|
||||
</label>
|
||||
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
<span>Nachtzuschlag (%)</span>
|
||||
<input type="number" min="0" step="0.1" value={form.nightSurchargePercent} onChange={updateField("nightSurchargePercent")} />
|
||||
<DecimalInput value={form.nightSurchargePercent} onChange={updateDecimalField("nightSurchargePercent")} />
|
||||
</label>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Samstagszuschlag (%)</span>
|
||||
<input type="number" min="0" step="0.1" value={form.saturdaySurchargePercent} onChange={updateField("saturdaySurchargePercent")} />
|
||||
<DecimalInput value={form.saturdaySurchargePercent} onChange={updateDecimalField("saturdaySurchargePercent")} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="form-row">
|
||||
<label className="form-field">
|
||||
<span>Sonntagszuschlag (%)</span>
|
||||
<input type="number" min="0" step="0.1" value={form.sundaySurchargePercent} onChange={updateField("sundaySurchargePercent")} />
|
||||
<DecimalInput value={form.sundaySurchargePercent} onChange={updateDecimalField("sundaySurchargePercent")} />
|
||||
</label>
|
||||
|
||||
<label className="form-field">
|
||||
<span>Feiertagszuschlag (%)</span>
|
||||
<input type="number" min="0" step="0.1" value={form.holidaySurchargePercent} onChange={updateField("holidaySurchargePercent")} />
|
||||
<DecimalInput value={form.holidaySurchargePercent} onChange={updateDecimalField("holidaySurchargePercent")} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -264,7 +260,7 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
|
||||
|
||||
<label className="form-field">
|
||||
<span>Mindeststunden je Einsatz</span>
|
||||
<input type="number" min="0" step="0.5" value={form.minimumHours} onChange={updateField("minimumHours")} />
|
||||
<DecimalInput value={form.minimumHours} onChange={updateDecimalField("minimumHours")} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -272,12 +268,12 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
|
||||
{form.travelCostMode === "ProKilometer" ? (
|
||||
<label className="form-field">
|
||||
<span>Fahrtkosten (EUR/km)</span>
|
||||
<input type="number" min="0" step="0.01" value={form.travelCostPerKm} onChange={updateField("travelCostPerKm")} />
|
||||
<DecimalInput value={form.travelCostPerKm} onChange={updateDecimalField("travelCostPerKm")} />
|
||||
</label>
|
||||
) : (
|
||||
<label className="form-field">
|
||||
<span>Fahrtkosten (EUR, Pauschale je Einsatz)</span>
|
||||
<input type="number" min="0" step="0.01" value={form.travelCostRate} onChange={updateField("travelCostRate")} />
|
||||
<DecimalInput value={form.travelCostRate} onChange={updateDecimalField("travelCostRate")} />
|
||||
</label>
|
||||
)}
|
||||
</div>
|
||||
@@ -285,7 +281,7 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
|
||||
<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")} />
|
||||
<DecimalInput value={form.mealAllowanceRate} onChange={updateDecimalField("mealAllowanceRate")} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
@@ -313,7 +309,6 @@ export default function FacilityForm({ form, onChange, includeCrmStatus = false,
|
||||
<input value={form.individualAgreements} onChange={updateField("individualAgreements")} />
|
||||
</label>
|
||||
</fieldset>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -705,6 +705,7 @@ button.omsorg-button--danger {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 14px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.employees-search {
|
||||
@@ -1015,8 +1016,7 @@ button.omsorg-button--danger {
|
||||
}
|
||||
|
||||
@media (max-width: 680px) {
|
||||
.page-heading,
|
||||
.employees-toolbar {
|
||||
.page-heading {
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
export function sanitizeDecimalInput(rawValue) {
|
||||
if (!rawValue) return "";
|
||||
const cleaned = rawValue.replace(/[^\d,.]/g, "");
|
||||
let separatorIdx = -1;
|
||||
let result = "";
|
||||
for (let i = 0; i < cleaned.length; i++) {
|
||||
if (cleaned[i] === "," || cleaned[i] === ".") {
|
||||
if (separatorIdx === -1) {
|
||||
separatorIdx = result.length;
|
||||
result += cleaned[i];
|
||||
}
|
||||
} else {
|
||||
result += cleaned[i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function parseGermanDecimal(value) {
|
||||
if (value === "" || value == null) return null;
|
||||
const trimmed = String(value).trim();
|
||||
if (!trimmed) return null;
|
||||
const normalized = trimmed.replace(",", ".");
|
||||
if (!/^\d+(\.\d+)?$/.test(normalized)) return null;
|
||||
return Number(normalized);
|
||||
}
|
||||
|
||||
export function formatGermanDecimal(value, { fractionDigits } = {}) {
|
||||
if (value == null) return "";
|
||||
const str = fractionDigits != null ? Number(value).toFixed(fractionDigits) : String(value);
|
||||
return str.replace(".", ",");
|
||||
}
|
||||
Reference in New Issue
Block a user