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

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 20:15:42 +02:00
Felix KemmlerandClaude Sonnet 5 09dce2ab98 Fix broken login on omsorgWeb: restore refreshToken in auth responses
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 14s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Successful in 5s
Docker-Images bauen und veröffentlichen / build (, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 17s
The cookie-only refresh-token migration earlier this session broke both
mitarbeiter-app and mitarbeiter-app-legacy: they're server-to-server PHP
clients (cURL/Guzzle calling omsorgCore directly) with no browser cookie
jar, so dropping refreshToken from the login/refresh response body left
them with nothing to store - login appeared to succeed, redirected to
the dashboard, but the very next page's session check failed silently
(mitarbeiter-app's _ensure_fresh_token() bails out whenever
$_SESSION['omsorgcore_refresh_token'] is empty), bouncing the user back
to the login form every time.

Fix: dual-mode refresh token transport instead of cookie-only.
- LoginResponse includes refreshToken again (restores the pre-migration
  contract PHP already expected) alongside the HttpOnly cookie.
- AuthController.Refresh/Logout accept an optional body-carried
  RefreshRequest/LogoutRequest as a fallback: cookie is checked first
  (browser/omsorgapp), body second (server-to-server clients).
- omsorgapp keeps ignoring the body's refreshToken and relies solely on
  the cookie (XSS-safe) - only its authApi.js needed a small update since
  the regenerated client now requires an explicit (empty) parameter
  object for refresh/logout.
- Regenerated omsorgcore-client-ts; api-client-php's lib/ was already
  consistent (never regenerated during the original migration, so it
  still expected refreshToken all along - only the backend had stopped
  providing it).

Verified end-to-end against a live instance: PHP login+refresh via
omsorgcore_login()/omsorgcore_refresh(), and the browser cookie-only
flow via curl with Origin/credentials headers - both work.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 19:02:16 +02:00
Felix KemmlerandClaude Sonnet 5 dca0349e8c Actually commit api-client-php's vendor/ - it was silently gitignored
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 3s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Successful in 6s
Docker-Images bauen und veröffentlichen / build (, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 4s
omsorgCore/CLAUDE.md documents vendor/ as committed so the app can run
without a composer install step, but the generator's own .gitignore
(api-client-php/.gitignore) excludes /vendor/ - it only ever existed
untracked on disk locally, which is why the Docker build worked for me
but mitarbeiter-app crashed at runtime on a real (fresh-checkout) deploy:
"Failed opening required '.../api-client-php/vendor/autoload.php'".
Verified by building from a git-archive-simulated fresh checkout with
this fix applied - mitarbeiter-app's login page now renders without the
fatal error.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 18:51:21 +02:00
Felix KemmlerandClaude Sonnet 5 c8a514af9b Only build/push Docker images on version tags, not every main push
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 3s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Successful in 4s
Docker-Images bauen und veröffentlichen / build (, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 3s
Rebuilding and pushing all three images on every commit was unnecessary
churn - the workflow now triggers exclusively on v* tags, so :latest
tracks the last released version instead of the last commit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 18:41:23 +02:00
Felix KemmlerandClaude Sonnet 5 0d767d7edf Fix infinite redirect loop on omsorgWeb behind the TLS-terminating proxy
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 3s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Successful in 4s
Docker-Images bauen und veröffentlichen / build (, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 3s
The root .htaccess forces HTTPS via `RewriteCond %{HTTPS} !=on`. Behind a
reverse proxy that terminates TLS and forwards to Apache over plain HTTP,
%{HTTPS} is always "off" - verified via mod_rewrite trace logging that
this is NOT spoofable via SetEnvIf or a RewriteRule E-flag, despite that
being commonly recommended; %{HTTPS} reflects only the actual TLS
connection to Apache. Every request was therefore redirected to https://,
which the proxy forwarded back over HTTP, looping forever (browser: "the
page isn't redirecting properly").

Fix: .htaccess's redirect condition also accepts a trusted
X-Forwarded-Proto: https header as evidence the request is already
HTTPS. omsorgWeb/docker/000-default.conf additionally sets HTTPS=on in
the request environment when that header is present, so mod_headers'
`env=HTTPS` condition (HSTS header) still fires correctly - this part
doesn't affect mod_rewrite's %{HTTPS} but is unrelated to the redirect fix.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 18:37:06 +02:00
Felix KemmlerandClaude Sonnet 5 dcc8ea1510 Make omsorgapp's backend URL runtime-configurable, use dedicated API subdomain
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 3s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Successful in 3s
Docker-Images bauen und veröffentlichen / build (, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 3s
- omsorgapp now reads the omsorgCore URL from a runtime env-config.js
  generated by the container entrypoint from OMSORG_CORE_URL, instead of
  only baking it in at image build time - docker-compose.yml pulls
  pre-built images from the registry, so a build-time-only value couldn't
  be changed without a rebuild.
- docker-compose.yml: omsorgCore gets its own subdomain
  (core.omsorg-pflegedienste.de) rather than being proxied under the
  frontend's domain - omsorgWeb never needs browser-side access to it
  anyway (server-side cURL only), and a dedicated API host is more
  future-proof without being any less secure (backend still only bound to
  127.0.0.1). Includes the nginx server-block snippet needed for the new
  subdomain.
- Drop the now-unused OMSORG_CORE_PUBLIC_URL build-arg wiring from the
  Gitea Actions workflow.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 18:14:32 +02:00
Felix KemmlerandClaude Sonnet 5 2a05c791f5 Use registry images in compose, drop MySQL/named volumes, fix omsorgWeb build
Docker-Images bauen und veröffentlichen / build (, omsorgCore/Dockerfile, omsorgcore) (push) Successful in 3s
Docker-Images bauen und veröffentlichen / build (, omsorgWeb/Dockerfile, omsorgweb) (push) Successful in 3s
Docker-Images bauen und veröffentlichen / build (VITE_OMSORG_CORE_URL=${{ vars.OMSORG_CORE_PUBLIC_URL }}, omsorgapp/Dockerfile, omsorgapp) (push) Successful in 3s
- docker-compose.yml: pull omsorgcore/omsorgapp/omsorgweb from the Gitea
  registry (pinnable via OMSORG_IMAGE_TAG) instead of building locally;
  remove the MySQL service (mitarbeiter-app-legacy is unmaintained legacy
  code) and switch all persistent storage from named Docker volumes to
  bind mounts under /root/data/.
- omsorgWeb/Dockerfile: create the upload/download/etc. directories before
  chown'ing them - they're excluded by .gitignore, so a fresh CI checkout
  doesn't have them and the build failed there (only worked locally because
  of leftover local test files).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-10 17:58:29 +02:00
2706 changed files with 358954 additions and 134 deletions
+5 -9
View File
@@ -2,7 +2,6 @@ name: Docker-Images bauen und veröffentlichen
on: on:
push: push:
branches: [main]
tags: ["v*"] tags: ["v*"]
env: env:
@@ -20,7 +19,7 @@ jobs:
build_args: "" build_args: ""
- image: omsorgapp - image: omsorgapp
dockerfile: omsorgapp/Dockerfile dockerfile: omsorgapp/Dockerfile
build_args: "VITE_OMSORG_CORE_URL=${{ vars.OMSORG_CORE_PUBLIC_URL }}" build_args: ""
- image: omsorgweb - image: omsorgweb
dockerfile: omsorgWeb/Dockerfile dockerfile: omsorgWeb/Dockerfile
build_args: "" build_args: ""
@@ -39,13 +38,10 @@ jobs:
fi fi
IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.image }}" IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAMESPACE }}/${{ matrix.image }}"
TAGS="-t $IMAGE:latest -t $IMAGE:${{ gitea.sha }}" # Läuft nur noch bei einem Tag-Push (siehe `on:` oben) - :latest zeigt damit immer auf
# den zuletzt getaggten Release, nicht auf jeden main-Commit. :<sha> bleibt zusätzlich
# Bei einem Tag-Push (z.B. v0.1.0) zusätzlich mit dem Tag-Namen selbst versionieren, # als exakter Build-Beleg, :<tag-name> (z.B. v0.1.3) zum gezielten Pinnen.
# damit ein bestimmter Release pinnbar bleibt statt nur :latest/:<sha>. TAGS="-t $IMAGE:latest -t $IMAGE:${{ gitea.sha }} -t $IMAGE:${{ gitea.ref_name }}"
if [ "${{ gitea.ref_type }}" = "tag" ]; then
TAGS="$TAGS -t $IMAGE:${{ gitea.ref_name }}"
fi
docker buildx build \ docker buildx build \
--push \ --push \
+10 -1
View File
@@ -1,6 +1,15 @@
# HTTPS erzwingen # HTTPS erzwingen - %{HTTPS} spiegelt nur die tatsächliche TLS-Verbindung zu Apache wider und lässt
# sich über keine Env-Var vortäuschen (auch nicht per SetEnvIf/RewriteRule-E-Flag, siehe
# omsorgWeb/docker/000-default.conf). Hinter einem TLS-terminierenden Reverse-Proxy (z.B. der
# Docker-Deployment, siehe docker-compose.yml) ist %{HTTPS} deshalb IMMER "off", auch bei einer
# echten HTTPS-Anfrage - ohne die zweite Bedingung würde das einen endlosen Redirect-Loop erzeugen
# (Proxy leitet HTTPS-Request per HTTP weiter -> Apache hält es für HTTP -> redirected auf https://
# -> Proxy nimmt HTTPS-Request an, leitet wieder per HTTP weiter -> ...). Die zweite Bedingung lässt
# den Request durch, wenn der (vertrauenswürdige) Proxy per X-Forwarded-Proto bestätigt, dass die
# ursprüngliche Anfrage bereits HTTPS war.
RewriteEngine On RewriteEngine On
RewriteCond %{HTTPS} !=on RewriteCond %{HTTPS} !=on
RewriteCond %{HTTP:X-Forwarded-Proto} !=https
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L] RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [R=301,L]
# Verzeichnis-Listing verbieten # Verzeichnis-Listing verbieten
+4 -2
View File
@@ -125,8 +125,10 @@ Umfasst alle drei Plattform-Ebenen: OMSORG Desktop, OMSORG Connect, OMSORG Backe
|---|---|---|---|---| |---|---|---|---|---|
| FR-REC-1 | Bewerberpipeline: Interessent → Bewerbung → Erstkontakt → Vorstellung → Unterlagen → Angebot → Einstellung → geplanter Eintritt [Blueprint 3.2] | Sascha | Bewerberdatensatz mit Statuswechsel | ⬜ | | FR-REC-1 | Bewerberpipeline: Interessent → Bewerbung → Erstkontakt → Vorstellung → Unterlagen → Angebot → Einstellung → geplanter Eintritt [Blueprint 3.2] | Sascha | Bewerberdatensatz mit Statuswechsel | ⬜ |
| FR-REC-2 | Werbung/Empfehlung: Kanäle Indeed, Facebook, Instagram, Mitarbeiterempfehlung erfassbar [omsorg.md] | Sascha | Herkunft je Bewerber taggable, auswertbar | 🔶 (`pages/werben.php`/`actions/submit-werben.php` erfasst Mitarbeiterempfehlungen bereits; Kanaltracking für externe Werbung fehlt) | | FR-REC-2 | Werbung/Empfehlung: Kanäle Indeed, Facebook, Instagram, Mitarbeiterempfehlung erfassbar [omsorg.md] | Sascha | Herkunft je Bewerber taggable, auswertbar | 🔶 (`pages/werben.php`/`actions/submit-werben.php` erfasst Mitarbeiterempfehlungen bereits; Kanaltracking für externe Werbung fehlt) |
| FR-REC-3 | Einrichtungsakquise: Lead anlegen → Ansprechpartner erfassen → Kontakt → Gesprächsnotiz → Ergebnis → automatische Wiedervorlage nach 14 Tagen [Blueprint 3.5] | Sascha | Wiedervorlage wird ohne manuellen Trigger vom System erzeugt | ⬜ | | FR-REC-3 | Einrichtungsakquise: Lead anlegen → Ansprechpartner erfassen → Kontakt → Gesprächsnotiz → Ergebnis → automatische Wiedervorlage nach gestaffeltem Akquise-Rhythmus [Blueprint 3.5]. Empfohlener Rhythmus (fachliche Vorgabe, nicht Blueprint-Wortlaut): Tag 1 Erstanruf, Tag 1 E-Mail mit Kurzvorstellung, nach 1014 Tagen zweiter Anruf, nach 46 Wochen erneuter Kontakt falls kein Bedarf bestand, danach regelmäßige Wiedervorlagen zum freundlichen In-Erinnerung-Bleiben | Sascha | Wiedervorlage wird ohne manuellen Trigger vom System erzeugt, mit dem zur jeweiligen Akquise-Stufe passenden Fristvorschlag (1014 Tage nach Erstkontakt, 46 Wochen nach zweitem Kontakt ohne Bedarf, danach wiederkehrend) statt einer einzigen festen Frist | ⬜ |
| FR-REC-4 | Sascha hat keinen Zugriff auf Rechnungen, Zahlungen, Gewinn, Margen, Controlling [Blueprint 6.2] | System | Rollenprüfung blockiert Zugriff serverseitig | ⬜ | | FR-REC-4 | Sascha hat keinen Zugriff auf Rechnungen, Zahlungen, Gewinn, Margen, Controlling [Blueprint 6.2] | System | Rollenprüfung blockiert Zugriff serverseitig | ⬜ |
| FR-REC-5 | Kaltakquise vollständig im CRM abgebildet statt einer losen Telefonnummernliste: Lead anlegen, Ansprechpartner speichern, Telefonat dokumentieren (Gesprächsprotokoll je Kontaktversuch), automatische Wiedervorlage je Akquise-Stufe (siehe FR-REC-3), E-Mail-Versand aus dem CRM heraus (z. B. Kurzvorstellung am Erstkontakttag), Angebotsstatus je Lead | Sascha | Jeder Akquise-Schritt (Anruf, E-Mail, Gesprächsnotiz, Status-/Angebotswechsel) ist am Lead-Datensatz nachvollziehbar, ohne Werkzeug außerhalb der Software | ⬜ |
| FR-REC-6 | Akquise-Erfolgsmessung: Abschlussquote je Mitarbeiter, Dashboard mit täglichen Anrufen, Terminen und gewonnenen Kunden [omsorg.md] | Sascha, Sabina, Malik | Kennzahlen werden aus den dokumentierten Akquise-Aktivitäten (FR-REC-5) berechnet, nicht manuell gepflegt | ⬜ |
### 4.8 Controlling ### 4.8 Controlling
@@ -258,7 +260,7 @@ Für zukünftige Büromitarbeiter: Rolle als Vorlage, zusätzlich granular je Mo
| Phase 2 Stammdaten | FR-MA-1..5, FR-EIN-1..5 | Blockiert durch Phase 1 | | Phase 2 Stammdaten | FR-MA-1..5, FR-EIN-1..5 | Blockiert durch Phase 1 |
| Phase 3 Operatives Geschäft | FR-EM-1..5 | Blockiert durch Phase 1/2 | | Phase 3 Operatives Geschäft | FR-EM-1..5 | Blockiert durch Phase 1/2 |
| Phase 4 Zeit und Abrechnung | FR-ZE-1..4, FR-RE-1..4 | Teilbasis vorhanden (Connect-Upload), aber ohne Core nicht rechnungsfähig | | Phase 4 Zeit und Abrechnung | FR-ZE-1..4, FR-RE-1..4 | Teilbasis vorhanden (Connect-Upload), aber ohne Core nicht rechnungsfähig |
| Phase 5 Recruiting und CRM | FR-REC-1..4 | Teilbasis vorhanden (Werben), CRM-Pipeline fehlt | | Phase 5 Recruiting und CRM | FR-REC-1..6 | Teilbasis vorhanden (Werben), CRM-Pipeline fehlt |
| Phase 6 OMSORG Connect | FR-CON-1..4 | 🔶 größtenteils vorhanden, Fahrtenbuch + Sync offen | | Phase 6 OMSORG Connect | FR-CON-1..4 | 🔶 größtenteils vorhanden, Fahrtenbuch + Sync offen |
| Phase 7 Dashboard und Controlling | FR-DASH-1..4, FR-CTL-1..3, FR-OUT-1 | Teilbasis (`omsorgapp` Dashboard-Ansätze) vorhanden | | Phase 7 Dashboard und Controlling | FR-DASH-1..4, FR-CTL-1..3, FR-OUT-1 | Teilbasis (`omsorgapp` Dashboard-Ansätze) vorhanden |
+70 -59
View File
@@ -1,12 +1,54 @@
name: omsorg name: omsorg
# WICHTIG (der fehleranfälligste Punkt in diesem Setup): # Zieht fertig gebaute Images aus der Gitea-Registry (siehe .gitea/workflows/docker-build.yml),
# - omsorgapp.build.args.VITE_OMSORG_CORE_URL muss die vom BROWSER erreichbare Adresse von # baut hier bewusst NICHT lokal - die Dockerfiles in omsorgCore/, omsorgapp/, omsorgWeb/ sind
# omsorgcore sein (hier: der auf dem Host published Port), NICHT der interne Compose-DNS-Name - # weiterhin da, werden aber nur noch von der CI verwendet. Vor `docker compose pull`/`up` einmal
# Vite bäckt diese URL zur Build-Zeit in den JS-Bundle ein (siehe omsorgapp/src/api/config.js). # `docker login git.omsorg-pflegedienste.de` auf dem Server ausführen, falls die Images/Packages
# - omsorgweb.environment.OMSORG_CORE_URL ist dagegen der interne Servicename (http://omsorgcore:8080), # nicht öffentlich lesbar sind. Über OMSORG_IMAGE_TAG lässt sich ein bestimmter Versions-Tag
# da PHP dort serverseitig per cURL aufruft (kein Browser-Kontext), siehe # pinnen (z.B. `OMSORG_IMAGE_TAG=v0.1.0 docker compose up -d`), Default ist `latest`.
# omsorgWeb/docker/bootstrap-config.php. #
# omsorgCore bekommt eine eigene Subdomain (core.omsorg-pflegedienste.de), nicht denselben
# Hostnamen wie omsorgapp - Begründung: omsorgWeb (beide mitarbeiter-app*-Apps) braucht ohnehin nie
# Browser-Zugriff auf omsorgCore (PHP ruft serverseitig per cURL auf, siehe
# omsorgWeb/docker/bootstrap-config.php/OMSORG_CORE_URL unten), nur omsorgapp tut das - eine eigene
# Subdomain macht die API trotzdem unabhängig von omsorgapps Hosting adressierbar (künftige
# Clients, Doku, Swagger) und ist nicht unsicherer als Pfad-basiertes Proxying: in beiden Fällen
# ist omsorgCore nur über 127.0.0.1 erreichbar (Port-Mapping unten), nie direkt öffentlich.
# Browser-seitiges Cross-Origin läuft über CORS + HttpOnly-Cookie mit credentials:"include" (siehe
# omsorgCore/CLAUDE.md, Abschnitt "Auth-Flow") - dafür muss Cors__AllowedOrigins__0 unten exakt
# der Origin von omsorgapp entsprechen (https://app.omsorg-pflegedienste.de).
#
# Nginx-Server-Block für die neue Subdomain (analog zum bestehenden app.omsorg-pflegedienste.de-
# Block, eigenes Zertifikat z.B. per `certbot --nginx -d core.omsorg-pflegedienste.de`):
#
# server {
# server_name core.omsorg-pflegedienste.de;
# location / {
# proxy_pass http://127.0.0.1:8080;
# proxy_http_version 1.1;
# proxy_set_header Host $host;
# proxy_set_header X-Real-IP $remote_addr;
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Forwarded-Proto $scheme;
# }
# listen 443 ssl; # managed by Certbot
# ssl_certificate /etc/letsencrypt/live/core.omsorg-pflegedienste.de/fullchain.pem;
# ssl_certificate_key /etc/letsencrypt/live/core.omsorg-pflegedienste.de/privkey.pem;
# include /etc/letsencrypt/options-ssl-nginx.conf;
# ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;
# }
#
# Alle persistenten Daten liegen bewusst als Bind-Mounts unter /root/data/ auf der Platte (keine
# benannten Docker-Volumes) - vor dem ersten `docker compose up` einmalig anlegen:
# mkdir -p /root/data/postgres /root/data/omsorgcore/documents \
# /root/data/omsorgweb/{uploads,downloads,fortbildung-materials,avatars,data}
# Postgres läuft im Container als uid 999 (nicht root) - /root/data/postgres muss dieser uid
# gehören, sonst schlägt der Start mit einem Permission-Fehler fehl:
# chown -R 999:999 /root/data/postgres
#
# mitarbeiter-app-legacy (Teil des omsorgweb-Images) ist Legacy-Code und braucht eigentlich MySQL -
# bewusst kein MySQL-Dienst mehr hier, die Legacy-App bleibt dadurch ohne DB-Anbindung (siehe
# omsorgWeb/CLAUDE.md - mitarbeiter-app-legacy wird nicht mehr weiterentwickelt).
services: services:
postgres: postgres:
@@ -16,32 +58,15 @@ services:
POSTGRES_USER: omsorg_core POSTGRES_USER: omsorg_core
POSTGRES_PASSWORD: omsorg_core_dev_password POSTGRES_PASSWORD: omsorg_core_dev_password
volumes: volumes:
- postgres_data:/var/lib/postgresql/data - /root/data/postgres:/var/lib/postgresql/data
healthcheck: healthcheck:
test: ["CMD-SHELL", "pg_isready -U omsorg_core"] test: ["CMD-SHELL", "pg_isready -U omsorg_core"]
interval: 5s interval: 5s
timeout: 5s timeout: 5s
retries: 10 retries: 10
mysql:
image: mysql:8.0
environment:
MYSQL_DATABASE: omsorg_web
MYSQL_USER: omsorg_web
MYSQL_PASSWORD: omsorg_web_dev_password
MYSQL_ROOT_PASSWORD: omsorg_web_root_dev_password
volumes:
- mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 5s
timeout: 5s
retries: 10
omsorgcore: omsorgcore:
build: image: git.omsorg-pflegedienste.de/admin/omsorg/omsorgcore:${OMSORG_IMAGE_TAG:-latest}
context: .
dockerfile: omsorgCore/Dockerfile
depends_on: depends_on:
postgres: postgres:
condition: service_healthy condition: service_healthy
@@ -49,53 +74,39 @@ services:
ASPNETCORE_ENVIRONMENT: Development ASPNETCORE_ENVIRONMENT: Development
ConnectionStrings__OmsorgCore: "Host=postgres;Port=5432;Database=omsorg_core;Username=omsorg_core;Password=omsorg_core_dev_password" 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" Jwt__Secret: "CHANGE_ME_LOCAL_DEV_SECRET_MIN_32_CHARS_LONG"
Cors__AllowedOrigins__0: "http://localhost:5173" Cors__AllowedOrigins__0: "https://app.omsorg-pflegedienste.de"
ports: ports:
- "8080:8080" # 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.
- "127.0.0.1:8080:8080"
volumes: volumes:
- omsorgcore_documents:/app/App_Data/documents - /root/data/omsorgcore/documents:/app/App_Data/documents
omsorgapp: omsorgapp:
build: image: git.omsorg-pflegedienste.de/admin/omsorg/omsorgapp:${OMSORG_IMAGE_TAG:-latest}
context: . environment:
dockerfile: omsorgapp/Dockerfile # Vom BROWSER erreichbare Adresse von omsorgCore - der Container-Entrypoint schreibt das
args: # zur Laufzeit in env-config.js (siehe omsorgapp/docker-entrypoint.sh/src/api/config.js),
VITE_OMSORG_CORE_URL: "http://localhost:8080" # kein Rebuild bei einer geänderten Backend-URL nötig.
OMSORG_CORE_URL: "https://core.omsorg-pflegedienste.de"
ports: ports:
- "5173:80" # Nur auf dem Host, wie omsorgcore oben - der Host-nginx proxied bereits 127.0.0.1:5173.
- "127.0.0.1:5173:80"
depends_on: depends_on:
- omsorgcore - omsorgcore
omsorgweb: omsorgweb:
build: image: git.omsorg-pflegedienste.de/admin/omsorg/omsorgweb:${OMSORG_IMAGE_TAG:-latest}
context: .
dockerfile: omsorgWeb/Dockerfile
depends_on: depends_on:
mysql:
condition: service_healthy
omsorgcore: omsorgcore:
condition: service_started condition: service_started
environment: environment:
DB_HOST: mysql
DB_NAME: omsorg_web
DB_USER: omsorg_web
DB_PASSWORD: omsorg_web_dev_password
OMSORG_CORE_URL: "http://omsorgcore:8080" OMSORG_CORE_URL: "http://omsorgcore:8080"
ports: ports:
- "8081:80" - "8081:80"
volumes: volumes:
- web_uploads:/var/www/html/mitarbeiter-app-legacy/uploads - /root/data/omsorgweb/uploads:/var/www/html/mitarbeiter-app-legacy/uploads
- web_downloads:/var/www/html/mitarbeiter-app-legacy/downloads - /root/data/omsorgweb/downloads:/var/www/html/mitarbeiter-app-legacy/downloads
- web_fortbildung:/var/www/html/mitarbeiter-app-legacy/fortbildung-materials - /root/data/omsorgweb/fortbildung-materials:/var/www/html/mitarbeiter-app-legacy/fortbildung-materials
- web_avatars:/var/www/html/mitarbeiter-app-legacy/assets/avatars - /root/data/omsorgweb/avatars:/var/www/html/mitarbeiter-app-legacy/assets/avatars
- web_data:/var/www/html/mitarbeiter-app-legacy/data - /root/data/omsorgweb/data:/var/www/html/mitarbeiter-app-legacy/data
volumes:
postgres_data:
mysql_data:
omsorgcore_documents:
web_uploads:
web_downloads:
web_fortbildung:
web_avatars:
web_data:
+13 -6
View File
@@ -68,6 +68,8 @@ omsorgCore/
Rolle liefert Standard-Rechte (`RolePermission`: Modul × Aktion), ein individueller `UserPermissionOverride` (Grant/Revoke) gewinnt immer gegen den Rollen-Default — siehe `PermissionService.HasPermissionAsync` (`src/OmsorgCore.Application/Services/PermissionService.cs`). Deckt Blueprint 6.5 ("Rolle als Vorlage + individuelle Rechte") ab. Rolle liefert Standard-Rechte (`RolePermission`: Modul × Aktion), ein individueller `UserPermissionOverride` (Grant/Revoke) gewinnt immer gegen den Rollen-Default — siehe `PermissionService.HasPermissionAsync` (`src/OmsorgCore.Application/Services/PermissionService.cs`). Deckt Blueprint 6.5 ("Rolle als Vorlage + individuelle Rechte") ab.
**`ModuleType.EmployeeFacilityDistances`** (additiv hinzugefügt 2026-08-10) gated ausschließlich `MyFacilityDistancesController` (Selbstbedienung, siehe "Konditionen einer Einrichtung" unten) — bewusst getrennt von `Facilities`, damit der Außendienst seine eigene Fahrtstrecke pflegen kann, ohne Konditionen/CRM-Daten von Einrichtungen zu sehen. Nur `Außendienst` bekommt es im Basis-Seed (`Create`/`View`/`Edit`, Scope `Own`, siehe `DbSeeder.SeedBaseRolesAsync`); Büro-Rollen verwalten Distanzen weiterhin über `EmployeeFacilityDistancesController`/`Facilities`-Recht.
Die aufgelösten Rechte eines Users (nicht nur eine einzelne Prüfung) liefert `PermissionService.GetGrantedPermissionsAsync` als Liste von `PermissionGrant(Module, Action, Scope)`. Exponiert über `GET /api/auth/me` (`AuthController.Me`, `[Authorize]`) als `MeResponse { username, role, permissions: [{ module, action, scope }, ...] }` — der einzige Weg, wie granulare Rechte den Client erreichen (das JWT trägt nur den Rollennamen). `omsorgapp` ruft diesen Endpunkt nach Login/Refresh auf (siehe `omsorgapp/CLAUDE.md`, "Rechtesystem im Client") und trifft UI-Entscheidungen darüber statt über einen Rollennamen-Vergleich. Die aufgelösten Rechte eines Users (nicht nur eine einzelne Prüfung) liefert `PermissionService.GetGrantedPermissionsAsync` als Liste von `PermissionGrant(Module, Action, Scope)`. Exponiert über `GET /api/auth/me` (`AuthController.Me`, `[Authorize]`) als `MeResponse { username, role, permissions: [{ module, action, scope }, ...] }` — der einzige Weg, wie granulare Rechte den Client erreichen (das JWT trägt nur den Rollennamen). `omsorgapp` ruft diesen Endpunkt nach Login/Refresh auf (siehe `omsorgapp/CLAUDE.md`, "Rechtesystem im Client") und trifft UI-Entscheidungen darüber statt über einen Rollennamen-Vergleich.
Rechteprüfung auf Controller-Actions: Rechteprüfung auf Controller-Actions:
@@ -87,6 +89,8 @@ Der Basis-Rollen-Seed (`DbSeeder.SeedBaseRolesAsync`, siehe unten) wurde bei die
**Rollen-Rechte-Matrix und User-Overrides verwalten (Admin-Flow):** Eine neu angelegte Rolle (`RoleService.CreateAsync`) hat zunächst keine `RolePermission`-Einträge — die Rechte-Matrix wird separat gesetzt über `GET /api/roles/{id}` (Rolle inkl. ihrer aktuellen `RolePermission`-Liste, `RoleService.GetByIdWithPermissionsAsync`) und `PUT /api/roles/{id}/permissions` (`RoleService.UpdatePermissionsAsync` — ersetzt die komplette `RolePermission`-Menge der Rolle durch die übergebene Menge, kein inkrementelles Patchen). Individuelle `UserPermissionOverride`-Ausnahmen eines Users werden über `GET/POST/DELETE /api/users/{id}/permission-overrides[...]` verwaltet (`UserService.GetPermissionOverridesAsync`/`AddPermissionOverrideAsync`/`RemovePermissionOverrideAsync``AddPermissionOverrideAsync` ist ein Upsert: existiert bereits ein Override für dasselbe Modul+Aktion bei diesem User, werden dessen `Effect` **und** `Scope` aktualisiert statt dupliziert). Diese Endpoints liegen auf `RolesController`/`UsersController` (die `.../permission-overrides`-Routen), gegated über `[RequirePermission(ModuleType.UserManagement, View|Edit)]`. Admin-UI dazu: `omsorgapp/src/modules/settings/` (`SettingsPage`, `RolesPanel`, `RolePermissionMatrix`, `UserOverridesPanel`). **Rollen-Rechte-Matrix und User-Overrides verwalten (Admin-Flow):** Eine neu angelegte Rolle (`RoleService.CreateAsync`) hat zunächst keine `RolePermission`-Einträge — die Rechte-Matrix wird separat gesetzt über `GET /api/roles/{id}` (Rolle inkl. ihrer aktuellen `RolePermission`-Liste, `RoleService.GetByIdWithPermissionsAsync`) und `PUT /api/roles/{id}/permissions` (`RoleService.UpdatePermissionsAsync` — ersetzt die komplette `RolePermission`-Menge der Rolle durch die übergebene Menge, kein inkrementelles Patchen). Individuelle `UserPermissionOverride`-Ausnahmen eines Users werden über `GET/POST/DELETE /api/users/{id}/permission-overrides[...]` verwaltet (`UserService.GetPermissionOverridesAsync`/`AddPermissionOverrideAsync`/`RemovePermissionOverrideAsync``AddPermissionOverrideAsync` ist ein Upsert: existiert bereits ein Override für dasselbe Modul+Aktion bei diesem User, werden dessen `Effect` **und** `Scope` aktualisiert statt dupliziert). Diese Endpoints liegen auf `RolesController`/`UsersController` (die `.../permission-overrides`-Routen), gegated über `[RequirePermission(ModuleType.UserManagement, View|Edit)]`. Admin-UI dazu: `omsorgapp/src/modules/settings/` (`SettingsPage`, `RolesPanel`, `RolePermissionMatrix`, `UserOverridesPanel`).
**Rolle löschen (seit 2026-08-10):** `DELETE /api/roles/{id}` (`[RequirePermission(ModuleType.UserManagement, PermissionAction.Delete)]`) — Löschschutz analog zu `ValueListService.DeleteItemAsync`, aber ohne die generische `IValueListUsageChecker`-Abstraktion, da eine Rolle nur über genau einen Ownership-Anker referenziert wird (`User.RoleId`, echte FK statt Freitext): `RoleService.DeleteAsync` prüft über `IUserRepository.GetUserIdsByRoleAsync` (bereits für die Cache-Invalidierung bei Rechte-Änderungen vorhanden), ob noch Benutzer dieser Rolle zugewiesen sind, und lehnt mit `409` + Anzahl im Klartext ab (`DeleteRoleResult`/`DeleteRoleFailureReason`), statt zu löschen. Hard-Delete (kein Soft-Delete/Papierkorb) — `Role` ist `Entity`, nicht `AuditableEntity`, kein eigenständiges Core-Objekt mit Löschprotokoll-Anforderung. **Wichtig:** `DbSeeder.SeedBaseRolesAsync` legt eine der vier Basis-Rollen (Geschäftsführung/Disposition/Buchhaltung/Recruiting/Außendienst) bei fehlendem Namen bei jedem Start erneut an (idempotent pro Rollenname, siehe dort) — eine gelöschte Basis-Rolle taucht nach einem Neustart automatisch wieder auf, das ist bestehendes, dokumentiertes Verhalten, keine Neuerung dieses Schritts. Frontend: `RolesPanel.jsx` zeigt einen Löschen-Button je Rolle (nur mit `UserManagement`/`Delete`), zeigt die `409`-Fehlermeldung des Servers an statt sie zu verschlucken.
**Datenebenen-Scope (`PermissionScope`, "nur eigene Daten"):** Dritte Dimension neben Modul×Aktion — jede `RolePermission`/`UserPermissionOverride`-Zeile trägt zusätzlich `Scope` (`All` oder `Own`, `src/OmsorgCore.Domain/Enums/PermissionScope.cs`). `PermissionService.GetScopeAsync(userId, module, action)` löst das auf und liefert `PermissionScope?` (`null` = gar nicht gewährt) — ein Override ersetzt dabei die komplette Zelle (Grant **und** Scope) der Rolle, es wird nicht gemergt, analog zur bestehenden Effect-Semantik. `HasPermissionAsync`/`RequirePermissionAttribute` bleiben bewusst scope-unabhängig (ein Own-User muss den Endpunkt-Gate trotzdem passieren) — die eigentliche Einschränkung passiert in den Application-Services: `EmployeeService`/`ContractService` konsultieren `GetScopeAsync` vor `GetPagedAsync`/`GetByIdAsync` und filtern bei `Own` auf `ICurrentUserService.EmployeeId` (neuer JWT-Claim `"employeeId"`, aus `User.EmployeeId`, nur eingebettet wenn gesetzt — wirkt daher erst mit dem nächsten Token-Refresh, wenn die Verknüpfung sich ändert). Own ohne verknüpfte `EmployeeId` liefert bewusst keine Datensätze (nicht "alle", nicht 500). **`Employees`, `Contracts`, `Absences` und `TimeEntries` werten den Scope aktuell aus** (alle vier haben einen Ownership-Anker: `User.EmployeeId`, `Contract.EmployeeId`, `Absence.EmployeeId`, `TimeEntry.EmployeeId` — siehe "Abwesenheits-/Urlaubs-/Krankmeldungsanträge" und "Zeiterfassung" unten) — alle anderen Module ignorieren `Scope` faktisch, weil ihnen kein Ownership-Anker zugrunde liegt (`Order` z. B. hat noch keinen Mitarbeiter-Bezug). **Datenebenen-Scope (`PermissionScope`, "nur eigene Daten"):** Dritte Dimension neben Modul×Aktion — jede `RolePermission`/`UserPermissionOverride`-Zeile trägt zusätzlich `Scope` (`All` oder `Own`, `src/OmsorgCore.Domain/Enums/PermissionScope.cs`). `PermissionService.GetScopeAsync(userId, module, action)` löst das auf und liefert `PermissionScope?` (`null` = gar nicht gewährt) — ein Override ersetzt dabei die komplette Zelle (Grant **und** Scope) der Rolle, es wird nicht gemergt, analog zur bestehenden Effect-Semantik. `HasPermissionAsync`/`RequirePermissionAttribute` bleiben bewusst scope-unabhängig (ein Own-User muss den Endpunkt-Gate trotzdem passieren) — die eigentliche Einschränkung passiert in den Application-Services: `EmployeeService`/`ContractService` konsultieren `GetScopeAsync` vor `GetPagedAsync`/`GetByIdAsync` und filtern bei `Own` auf `ICurrentUserService.EmployeeId` (neuer JWT-Claim `"employeeId"`, aus `User.EmployeeId`, nur eingebettet wenn gesetzt — wirkt daher erst mit dem nächsten Token-Refresh, wenn die Verknüpfung sich ändert). Own ohne verknüpfte `EmployeeId` liefert bewusst keine Datensätze (nicht "alle", nicht 500). **`Employees`, `Contracts`, `Absences` und `TimeEntries` werten den Scope aktuell aus** (alle vier haben einen Ownership-Anker: `User.EmployeeId`, `Contract.EmployeeId`, `Absence.EmployeeId`, `TimeEntry.EmployeeId` — siehe "Abwesenheits-/Urlaubs-/Krankmeldungsanträge" und "Zeiterfassung" unten) — alle anderen Module ignorieren `Scope` faktisch, weil ihnen kein Ownership-Anker zugrunde liegt (`Order` z. B. hat noch keinen Mitarbeiter-Bezug).
**Wichtig:** Wird ein neuer `ModuleType` oder `PermissionAction`-Wert hinzugefügt, oder ändert sich sonst das Rollen-/Rechtesystem, muss dieser Abschnitt (Rechtesystem) im selben Change aktualisiert werden — diese Dokumentation ist keine Momentaufnahme, sondern muss mit der Software mitwachsen. **Wichtig:** Wird ein neuer `ModuleType` oder `PermissionAction`-Wert hinzugefügt, oder ändert sich sonst das Rollen-/Rechtesystem, muss dieser Abschnitt (Rechtesystem) im selben Change aktualisiert werden — diese Dokumentation ist keine Momentaufnahme, sondern muss mit der Software mitwachsen.
@@ -160,7 +164,7 @@ Vollständig implementiert: `PasswordResetCode`-Entity + `PasswordResetService`
- Connection-String-Key: `ConnectionStrings:OmsorgCore` (Format `Host=...;Port=5432;Database=omsorg_core;Username=...;Password=...`). - 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. - `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. - **`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. - 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.
## Build- und Run-Befehle ## Build- und Run-Befehle
@@ -190,7 +194,7 @@ ASPNETCORE_ENVIRONMENT=Development dotnet run --project src/OmsorgCore.Api
- `omsorgapp`s `authClient.cjs` erfolgreich gegen den laufenden Server getestet (Login/Refresh/Logout-Fehlerfälle). - `omsorgapp`s `authClient.cjs` erfolgreich gegen den laufenden Server getestet (Login/Refresh/Logout-Fehlerfälle).
- **Kompletter Login-Flow end-to-end mit echtem Postgres verifiziert:** Login mit `admin`/`abersicher` (Seed) → gültiges Token-Paar; `refresh` rotiert korrekt (neues Paar, alter Refresh-Token danach 401 bei Wiederverwendung); `GET /api/employees` mit frischem Access-Token → 200 (Administrator-Rolle hat volle Rechte über den Seed). - **Kompletter Login-Flow end-to-end mit echtem Postgres verifiziert:** Login mit `admin`/`abersicher` (Seed) → gültiges Token-Paar; `refresh` rotiert korrekt (neues Paar, alter Refresh-Token danach 401 bei Wiederverwendung); `GET /api/employees` mit frischem Access-Token → 200 (Administrator-Rolle hat volle Rechte über den Seed).
- **Session-Killswitch end-to-end verifiziert:** `GET /api/admin/sessions` liefert aktive Sessions; `POST /api/admin/sessions/revoke-all` → 204, danach liefert **derselbe, zuvor gültige Access-Token sofort 401** (nicht erst nach Ablauf) und der zugehörige Refresh-Token liefert bei `POST /api/auth/refresh` ebenfalls 401. Erneuter Login mit `admin`/`abersicher` funktioniert danach wieder normal. - **Session-Killswitch end-to-end verifiziert:** `GET /api/admin/sessions` liefert aktive Sessions; `POST /api/admin/sessions/revoke-all` → 204, danach liefert **derselbe, zuvor gültige Access-Token sofort 401** (nicht erst nach Ablauf) und der zugehörige Refresh-Token liefert bei `POST /api/auth/refresh` ebenfalls 401. Erneuter Login mit `admin`/`abersicher` funktioniert danach wieder normal.
- `DbSeeder.SeedBaseRolesAsync` gegen echte PostgreSQL-Instanz verifiziert: legt `Geschäftsführung`/`Disposition/Buchhaltung`/`Recruiting`/`Außendienst` mit der erwarteten Rechteanzahl an (54/33/10/0 Permissions), zweiter Lauf verändert nichts (idempotent pro Rollenname). **Hinweis:** Seit `AddPermissionScope` bekommt `Außendienst` neu `Employees.View`+`Contracts.View` (Scope `Own`) — die Zahl "0" für Außendienst ist damit veraltet (jetzt 2 erwartet), aber noch nicht erneut per echtem Seed-Lauf verifiziert (der Seed läuft nur bei leerer `Roles`-Tabelle bzw. pro fehlendem Rollennamen, nicht erneut gegen eine bereits befüllte Instanz). - `DbSeeder.SeedBaseRolesAsync` gegen echte PostgreSQL-Instanz verifiziert: legt `Geschäftsführung`/`Disposition/Buchhaltung`/`Recruiting`/`Außendienst` mit der erwarteten Rechteanzahl an (54/33/10/0 Permissions), zweiter Lauf verändert nichts (idempotent pro Rollenname). **Hinweis:** Seit `AddPermissionScope` bekommt `Außendienst` neu `Employees.View`+`Contracts.View` (Scope `Own`), seit den Absences-/TimeEntries-/EmployeeFacilityDistances-Erweiterungen zusätzlich `Absences.{Create,View,Edit}`, `TimeEntries.{Create,View,Edit}` und `EmployeeFacilityDistances.{Create,View,Edit}` (alle Scope `Own`) — die Zahl "0" für Außendienst ist damit veraltet (jetzt 11 erwartet), aber noch nicht erneut per echtem Seed-Lauf verifiziert (der Seed läuft nur bei leerer `Roles`-Tabelle bzw. pro fehlendem Rollennamen, nicht erneut gegen eine bereits befüllte Instanz).
## Konfigurierbare Auswahllisten ## Konfigurierbare Auswahllisten
@@ -231,7 +235,10 @@ Dropdown-Werte, die früher als hartcodierte Arrays im `omsorgapp`-Frontend lebt
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`: 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`:
- `BillingRate` (Verrechnungssatz, EUR/Std.), `NightSurchargePercent`/`SaturdaySurchargePercent`/`SundaySurchargePercent`/`HolidaySurchargePercent` (Zuschläge als **Prozent** auf den Verrechnungssatz, nicht als EUR-Betrag), `TravelCostRate` (Fahrtkosten als **Pauschale je Einsatz**, kein km-Modell), `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), `MinimumHours` (Mindeststunden je Einsatz), `BreakPolicy` (Pausenregelung, Freitext), `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`.
- `BillingInterval` wird wie `FacilityType`/`ContractType` gegen die admin-editierbare `ValueList` `"BillingInterval"` (Wöchentlich/Monatlich/Quartalsweise) validiert — siehe "Konfigurierbare Auswahllisten". - `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 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.
@@ -273,7 +280,7 @@ Drei Endpoints statt zwei (Absence hat Edit+Decide, TimeEntry hat Edit+Submit+De
## Offene Punkte ## Offene Punkte
- `Facility` hat jetzt volles Repository/Service/Controller (`FacilitiesController`, `GET/POST/PUT/DELETE /api/facilities`) nach dem Employee-Muster, inkl. `FacilityCreatedEvent`. Zusätzlich `FacilityContact` (FR-EIN-2, Ansprechpartner) als 1:n-Unterressource unter `GET/POST/PUT/DELETE /api/facilities/{facilityId}/contacts[/...]` (`FacilityContactsController`) und `FacilityQualificationRate` (FR-EIN-4, qualifikationsabhängige Preise) als 1:n-Unterressource unter `GET/POST/PUT/DELETE /api/facilities/{facilityId}/qualification-rates[/...]` (`FacilityQualificationRatesController`, siehe "Konditionen einer Einrichtung" oben) — beide bewusst kein eigener `ModuleType`, sondern über dieselben `Facilities`-Rechte gegated, da beides kein eigenständiges Core-Objekt ist. Löschen (`Employee`/`Facility`/`Contract`/`Order`/`FacilityContact`/`FacilityQualificationRate`) ist jetzt durchgängig Soft-Delete (`IsDeleted`/`DeletedAt`, `PermissionAction.Delete` je Modul, `DELETE`-Endpoint pro Controller) und über den `TrashController` (`api/trash/...`, `PermissionAction.Recover` je Modul, "Papierkorb"-Seite in `omsorgapp`) wiederherstellbar. - `Facility` hat jetzt volles Repository/Service/Controller (`FacilitiesController`, `GET/POST/PUT/DELETE /api/facilities`) nach dem Employee-Muster, inkl. `FacilityCreatedEvent`. Zusätzlich `FacilityContact` (FR-EIN-2, Ansprechpartner), `FacilityQualificationRate` (FR-EIN-4, qualifikationsabhängige Preise) und `EmployeeFacilityDistance` (FR-EIN-4, kilometerbasierte Fahrtkosten) als 1:n-Unterressourcen unter `GET/POST/PUT/DELETE /api/facilities/{facilityId}/contacts[/...]`, `.../qualification-rates[/...]` bzw. `.../employee-distances[/...]` (`FacilityContactsController`/`FacilityQualificationRatesController`/`EmployeeFacilityDistancesController`, siehe "Konditionen einer Einrichtung" oben) — alle drei bewusst kein eigener `ModuleType`, sondern über dieselben `Facilities`-Rechte gegated, da keines ein eigenständiges Core-Objekt ist. Löschen (`Employee`/`Facility`/`Contract`/`Order`/`FacilityContact`/`FacilityQualificationRate`/`EmployeeFacilityDistance`) ist jetzt durchgängig Soft-Delete (`IsDeleted`/`DeletedAt`, `PermissionAction.Delete` je Modul, `DELETE`-Endpoint pro Controller) und über den `TrashController` (`api/trash/...`, `PermissionAction.Recover` je Modul, "Papierkorb"-Seite in `omsorgapp`) wiederherstellbar.
- `Contract` hat jetzt ebenfalls volles Repository/Service/Controller (`ContractsController`, `GET/POST/PUT /api/contracts`, gegated über `[RequirePermission(ModuleType.Contracts, ...)]`) nach demselben Facility-Muster, inkl. `ContractCreatedEvent`. Deckt FR-MA-2 auf Backend-Seite ab: `WeeklyHours` (Arbeitszeit), `HourlyWage` (Stundenlohn), `AllowancesDescription` (Zuschläge, Freitext), `OvertimeRules` (Überstundenregelung, Freitext), `VacationDaysPerYear` (Urlaubsanspruch), `ProbationPeriodMonths` (Probezeit) — alle nullable, da ein Vertrag entweder einem Mitarbeiter oder einer Einrichtung zugeordnet ist (`EmployeeId`/`FacilityId`, mindestens eins muss gesetzt sein, per Controller-Validierung erzwungen) und nicht jeder Vertragstyp alle Felder braucht. `ContractConfiguration` hat jetzt (wie `Facility`) einen `HasQueryFilter(!IsDeleted)`. FR-MA-2 ist damit inkl. `omsorgapp`-UI abgeschlossen: "Verträge"-Tab in `EmployeeDetailPanel` (`ContractsList`/`ContractForm`/`Create-`/`EditContractDialog.jsx`), neue Verträge starten als "Entwurf", Statuswechsel nur im Bearbeiten-Formular, Löschen als Soft-Delete über den Papierkorb wiederherstellbar (siehe `omsorgapp/CLAUDE.md`). - `Contract` hat jetzt ebenfalls volles Repository/Service/Controller (`ContractsController`, `GET/POST/PUT /api/contracts`, gegated über `[RequirePermission(ModuleType.Contracts, ...)]`) nach demselben Facility-Muster, inkl. `ContractCreatedEvent`. Deckt FR-MA-2 auf Backend-Seite ab: `WeeklyHours` (Arbeitszeit), `HourlyWage` (Stundenlohn), `AllowancesDescription` (Zuschläge, Freitext), `OvertimeRules` (Überstundenregelung, Freitext), `VacationDaysPerYear` (Urlaubsanspruch), `ProbationPeriodMonths` (Probezeit) — alle nullable, da ein Vertrag entweder einem Mitarbeiter oder einer Einrichtung zugeordnet ist (`EmployeeId`/`FacilityId`, mindestens eins muss gesetzt sein, per Controller-Validierung erzwungen) und nicht jeder Vertragstyp alle Felder braucht. `ContractConfiguration` hat jetzt (wie `Facility`) einen `HasQueryFilter(!IsDeleted)`. FR-MA-2 ist damit inkl. `omsorgapp`-UI abgeschlossen: "Verträge"-Tab in `EmployeeDetailPanel` (`ContractsList`/`ContractForm`/`Create-`/`EditContractDialog.jsx`), neue Verträge starten als "Entwurf", Statuswechsel nur im Bearbeiten-Formular, Löschen als Soft-Delete über den Papierkorb wiederherstellbar (siehe `omsorgapp/CLAUDE.md`).
- `Order` hat jetzt ebenfalls volles Repository/Service/Controller (`OrdersController`, `GET/POST/PUT /api/orders`, gegated über `[RequirePermission(ModuleType.Orders, ...)]`) nach demselben Facility/Contract-Muster, inkl. `OrderCreatedEvent`. Deckt FR-EM-1 auf Backend-Seite ab: `FacilityContactId` (optionaler Ansprechpartner, gegen `FacilityId` cross-validiert — der Kontakt muss zur angegebenen Einrichtung gehören, sonst `400`), `ShiftType` (Schichtart, Freitext), `RequiredHeadcount` (Anzahl Mitarbeiter, mindestens 1), `Conditions` (Konditionen, Freitext), `Priority` (Priorität, Freitext). Der Auftragsstatus (FR-EM-2, `Order.StatusId`) ist Teil der generischen Auswahllisten — siehe "Konfigurierbare Auswahllisten" unten. `OrderConfiguration` hat jetzt (wie `Facility`/`Contract`) einen `HasQueryFilter(!IsDeleted)`. Kein `omsorgapp`-UI-Modul für Aufträge selbst in diesem Schritt (nur die Statuspflege über "Status-Verwaltung"). `TimeEntry` hat jetzt ebenfalls volles Repository/Service/Controller (FR-ZE-1/FR-ZE-2, siehe "Zeiterfassung" oben) — `Invoice` hat weiterhin nur Domain-Entität + DB-Konfiguration, nächster Schritt folgt demselben Muster (Repository-Interface in Application, Implementierung in Infrastructure, Service in Application, Controller in Api) und muss FR-ZE-3 (nur freigegebene Zeit fließt ein) auf Domain-/Application-Ebene erzwingen. - `Order` hat jetzt ebenfalls volles Repository/Service/Controller (`OrdersController`, `GET/POST/PUT /api/orders`, gegated über `[RequirePermission(ModuleType.Orders, ...)]`) nach demselben Facility/Contract-Muster, inkl. `OrderCreatedEvent`. Deckt FR-EM-1 auf Backend-Seite ab: `FacilityContactId` (optionaler Ansprechpartner, gegen `FacilityId` cross-validiert — der Kontakt muss zur angegebenen Einrichtung gehören, sonst `400`), `ShiftType` (Schichtart, Freitext), `RequiredHeadcount` (Anzahl Mitarbeiter, mindestens 1), `Conditions` (Konditionen, Freitext), `Priority` (Priorität, Freitext). Der Auftragsstatus (FR-EM-2, `Order.StatusId`) ist Teil der generischen Auswahllisten — siehe "Konfigurierbare Auswahllisten" unten. `OrderConfiguration` hat jetzt (wie `Facility`/`Contract`) einen `HasQueryFilter(!IsDeleted)`. Kein `omsorgapp`-UI-Modul für Aufträge selbst in diesem Schritt (nur die Statuspflege über "Status-Verwaltung"). `TimeEntry` hat jetzt ebenfalls volles Repository/Service/Controller (FR-ZE-1/FR-ZE-2, siehe "Zeiterfassung" oben) — `Invoice` hat weiterhin nur Domain-Entität + DB-Konfiguration, nächster Schritt folgt demselben Muster (Repository-Interface in Application, Implementierung in Infrastructure, Service in Application, Controller in Api) und muss FR-ZE-3 (nur freigegebene Zeit fließt ein) auf Domain-/Application-Ebene erzwingen.
- Dokumentenarchiv (FR-MA-3) hat jetzt volles Repository/Service/Controller (`DocumentsController`, `GET/POST /api/documents`, `PUT /{id}` für Metadaten, `GET /{id}/download`, `DELETE /{id}`) — siehe "Dokumentenarchiv" oben. `omsorgapp`-UI ("Dokumente"-Tab in der Personalakte) existiert jetzt ebenfalls, siehe `omsorgapp/CLAUDE.md`. Kein Außendienst-Selbstzugriff, kein physisches Löschen von Dateien beim Soft-Delete/Papierkorb (keine Hard-Purge-Stelle im System, die man konsistent mitziehen müsste). - Dokumentenarchiv (FR-MA-3) hat jetzt volles Repository/Service/Controller (`DocumentsController`, `GET/POST /api/documents`, `PUT /{id}` für Metadaten, `GET /{id}/download`, `DELETE /{id}`) — siehe "Dokumentenarchiv" oben. `omsorgapp`-UI ("Dokumente"-Tab in der Personalakte) existiert jetzt ebenfalls, siehe `omsorgapp/CLAUDE.md`. Kein Außendienst-Selbstzugriff, kein physisches Löschen von Dateien beim Soft-Delete/Papierkorb (keine Hard-Purge-Stelle im System, die man konsistent mitziehen müsste).
@@ -285,7 +292,7 @@ Drei Endpoints statt zwei (Absence hat Edit+Decide, TimeEntry hat Edit+Submit+De
Aus der Swagger/OpenAPI-JSON dieses Backends (`/swagger/v1/swagger.json`, nur im Development-Modus aktiv) werden mit `openapi-generator-cli` typisierte Clients generiert — `omsorgapp/api-client-ts/` (TypeScript, `typescript-fetch`-Template) und `omsorgWeb/mitarbeiter-app/api-client-php/` (PHP). Aus der Swagger/OpenAPI-JSON dieses Backends (`/swagger/v1/swagger.json`, nur im Development-Modus aktiv) werden mit `openapi-generator-cli` typisierte Clients generiert — `omsorgapp/api-client-ts/` (TypeScript, `typescript-fetch`-Template) und `omsorgWeb/mitarbeiter-app/api-client-php/` (PHP).
**Wichtig — beide generierten Clients sind im echten Datenpfad, nicht optional:** Alle Wrapper unter `omsorgapp/src/api/*Api.js` (`employeesApi.js`, `facilitiesApi.js`, `facilityContactsApi.js`, `facilityQualificationRatesApi.js`, `usersApi.js`, `rolesApi.js`, `valueListsApi.js`, `auditLogApi.js`, `authApi.js`, `absencesApi.js`, `timeEntriesApi.js`, ...) importieren die jeweilige `*Api`-Klasse aus dem generierten Paket `omsorgcore-client-ts` (`import { ... } from "omsorgcore-client-ts"`) und reichen Requests/Responses **ungeprüft typisiert** durch. `omsorgWeb/mitarbeiter-app/lib/omsorgCoreClient.php` nutzt seit den Absences-/Orders-/TimeEntries-Wrappern ebenfalls den generierten PHP-Client (`../api-client-php/`, `\OmsorgCoreClient\Api\...`) statt rohem cURL — beide Frontends müssen also nach einer Contract-Änderung neu generiert werden, nicht nur `omsorgapp`. **Wichtig — beide generierten Clients sind im echten Datenpfad, nicht optional:** Alle Wrapper unter `omsorgapp/src/api/*Api.js` (`employeesApi.js`, `facilitiesApi.js`, `facilityContactsApi.js`, `facilityQualificationRatesApi.js`, `employeeFacilityDistancesApi.js`, `usersApi.js`, `rolesApi.js`, `valueListsApi.js`, `auditLogApi.js`, `authApi.js`, `absencesApi.js`, `timeEntriesApi.js`, ...) importieren die jeweilige `*Api`-Klasse aus dem generierten Paket `omsorgcore-client-ts` (`import { ... } from "omsorgcore-client-ts"`) und reichen Requests/Responses **ungeprüft typisiert** durch. `omsorgWeb/mitarbeiter-app/lib/omsorgCoreClient.php` nutzt seit den Absences-/Orders-/TimeEntries-Wrappern ebenfalls den generierten PHP-Client (`../api-client-php/`, `\OmsorgCoreClient\Api\...`) statt rohem cURL — beide Frontends müssen also nach einer Contract-Änderung neu generiert werden, nicht nur `omsorgapp`.
**Verbindliche Regel: nach *jeder* Änderung an einem Controller oder DTO in `omsorgCore.Api/Contracts` müssen `omsorgapp/api-client-ts` **und** `omsorgWeb/mitarbeiter-app/api-client-php` neu generiert und neu gebaut werden — noch in demselben Change, nicht als Nachgang.** Wird das vergessen, gibt es **keinen Fehler, keine Exception, keine Warnung** — der generierte Client kennt das neue/geänderte Feld schlicht nicht und lässt es beim Serialisieren/Deserialisieren stillschweigend weg. Das Symptom in der UI: Speichern/Anlegen meldet Erfolg, aber das betroffene Feld kommt nie im Backend an bzw. taucht nie in der Antwort auf — schwer zu debuggen, weil weder Backend noch Frontend-Code einen sichtbaren Fehler werfen (siehe FR-EIN-1/Website-Vorfall, 2026-08-08). **Verbindliche Regel: nach *jeder* Änderung an einem Controller oder DTO in `omsorgCore.Api/Contracts` müssen `omsorgapp/api-client-ts` **und** `omsorgWeb/mitarbeiter-app/api-client-php` neu generiert und neu gebaut werden — noch in demselben Change, nicht als Nachgang.** Wird das vergessen, gibt es **keinen Fehler, keine Exception, keine Warnung** — der generierte Client kennt das neue/geänderte Feld schlicht nicht und lässt es beim Serialisieren/Deserialisieren stillschweigend weg. Das Symptom in der UI: Speichern/Anlegen meldet Erfolg, aber das betroffene Feld kommt nie im Backend an bzw. taucht nie in der Antwort auf — schwer zu debuggen, weil weder Backend noch Frontend-Code einen sichtbaren Fehler werfen (siehe FR-EIN-1/Website-Vorfall, 2026-08-08).
@@ -310,7 +317,7 @@ Verbindliche Regeln für das Datenmodell:
2. Beziehungen ausschließlich über Foreign Keys/IDs, keine redundante Texteingabe verwandter Daten. 2. Beziehungen ausschließlich über Foreign Keys/IDs, keine redundante Texteingabe verwandter Daten.
3. Stammdaten nur an einer Stelle — kein Feld, das auch in `omsorgWeb` oder `omsorgapp` unabhängig gepflegt wird, sobald die Migration dorthin begonnen hat. 3. Stammdaten nur an einer Stelle — kein Feld, das auch in `omsorgWeb` oder `omsorgapp` unabhängig gepflegt wird, sobald die Migration dorthin begonnen hat.
4. Änderungen an geschäftsrelevanten Daten müssen nachvollziehbar sein — `AuditableEntity` liefert `CreatedAt`/`UpdatedAt`; ein vollständiger Audit-Trail (wer hat was geändert) läuft automatisch über den `AuditSaveChangesInterceptor` (siehe "Audit-Log" oben), keine Handarbeit pro Entität nötig. 4. Änderungen an geschäftsrelevanten Daten müssen nachvollziehbar sein — `AuditableEntity` liefert `CreatedAt`/`UpdatedAt`; ein vollständiger Audit-Trail (wer hat was geändert) läuft automatisch über den `AuditSaveChangesInterceptor` (siehe "Audit-Log" oben), keine Handarbeit pro Entität nötig.
5. Kein Hard-Delete für sensible/geschäftsrelevante Daten — Soft-Delete/Archivierung. Umgesetzt für `Employee`/`Facility`/`Contract`/`Order`/`FacilityContact`/`FacilityQualificationRate`/`Absence`/`TimeEntry` (`IsDeleted`/`DeletedAt`, `DELETE`-Endpoints gegated über `PermissionAction.Delete`, Wiederherstellung über `TrashController`/`PermissionAction.Recover`); `Invoice` hat noch kein CRUD, daher hier noch nicht relevant. 5. Kein Hard-Delete für sensible/geschäftsrelevante Daten — Soft-Delete/Archivierung. Umgesetzt für `Employee`/`Facility`/`Contract`/`Order`/`FacilityContact`/`FacilityQualificationRate`/`EmployeeFacilityDistance`/`Absence`/`TimeEntry` (`IsDeleted`/`DeletedAt`, `DELETE`-Endpoints gegated über `PermissionAction.Delete`, Wiederherstellung über `TrashController`/`PermissionAction.Recover`); `Invoice` hat noch kein CRUD, daher hier noch nicht relevant.
6. Berechtigungsprüfung auf Daten- und Funktionsebene (siehe Rechtesystem oben und Rechtematrix in `REQUIREMENTS.md` Abschnitt 7). 6. Berechtigungsprüfung auf Daten- und Funktionsebene (siehe Rechtesystem oben und Rechtematrix in `REQUIREMENTS.md` Abschnitt 7).
7. Rechnungen entstehen ausschließlich aus freigegebener Zeiterfassung (FR-ZE-3/FR-RE-1) — muss bei Ausbau von `TimeEntry`/`Invoice` auf Domain-/Application-Ebene erzwungen werden, nicht nur als UI-Regel im Client. 7. Rechnungen entstehen ausschließlich aus freigegebener Zeiterfassung (FR-ZE-3/FR-RE-1) — muss bei Ausbau von `TimeEntry`/`Invoice` auf Domain-/Application-Ebene erzwungen werden, nicht nur als UI-Regel im Client.
@@ -0,0 +1,5 @@
namespace OmsorgCore.Api.Contracts;
public record CreateEmployeeFacilityDistanceRequest(
Guid EmployeeId,
decimal DistanceKm);
@@ -0,0 +1,5 @@
namespace OmsorgCore.Api.Contracts;
public record CreateMyFacilityDistanceRequest(
Guid FacilityId,
decimal DistanceKm);
@@ -0,0 +1,9 @@
namespace OmsorgCore.Api.Contracts;
public record EmployeeFacilityDistanceResponse(
Guid Id,
Guid FacilityId,
Guid EmployeeId,
string EmployeeFirstName,
string EmployeeLastName,
decimal DistanceKm);
@@ -0,0 +1,9 @@
namespace OmsorgCore.Api.Contracts;
/// <summary>
/// Minimale Einrichtungsauswahl für Selbstbedienungs-Formulare ohne Facilities-Recht (z. B.
/// MyFacilityDistancesController) — bewusst nur Id/Name, keine Konditionen/CRM-Daten.
/// </summary>
public record FacilityOptionResponse(
Guid Id,
string Name);
@@ -21,6 +21,8 @@ public record FacilityResponse(
decimal? SundaySurchargePercent, decimal? SundaySurchargePercent,
decimal? HolidaySurchargePercent, decimal? HolidaySurchargePercent,
decimal? TravelCostRate, decimal? TravelCostRate,
string TravelCostMode,
decimal? TravelCostPerKm,
decimal? MinimumHours, decimal? MinimumHours,
string? BreakPolicy, string? BreakPolicy,
string? BillingInterval, string? BillingInterval,
@@ -1,3 +1,8 @@
namespace OmsorgCore.Api.Contracts; namespace OmsorgCore.Api.Contracts;
public record LoginResponse(string AccessToken, DateTime ExpiresAt, bool MustChangePassword); // RefreshToken steht hier UND als HttpOnly-Cookie (siehe AuthController.SetRefreshTokenCookie) -
// zwei Konsumenten mit unterschiedlichem Transport: omsorgapp (Browser) ignoriert dieses Feld
// bewusst und verlässt sich nur auf die Cookie (XSS-sicher, siehe omsorgapp/src/api/authApi.js).
// omsorgWeb (PHP, server-seitiger Aufrufer ohne Browser-Cookie-Jar) MUSS den Wert hier lesen und
// selbst in der PHP-Session verwalten, siehe omsorgWeb/mitarbeiter-app/lib/auth.php.
public record LoginResponse(string AccessToken, string RefreshToken, DateTime ExpiresAt, bool MustChangePassword);
@@ -0,0 +1,5 @@
namespace OmsorgCore.Api.Contracts;
// Optional - nur für Aufrufer ohne Browser-Cookie-Jar (server-seitige API-Clients wie omsorgWeb),
// siehe AuthController.Logout. Ein Browser schickt keinen Body, die HttpOnly-Cookie reicht.
public record LogoutRequest(string? RefreshToken);
@@ -0,0 +1,7 @@
namespace OmsorgCore.Api.Contracts;
public record MyFacilityDistanceResponse(
Guid Id,
Guid FacilityId,
string FacilityName,
decimal DistanceKm);
@@ -0,0 +1,5 @@
namespace OmsorgCore.Api.Contracts;
// Optional - nur für Aufrufer ohne Browser-Cookie-Jar (server-seitige API-Clients wie omsorgWeb),
// siehe AuthController.Refresh. Ein Browser schickt keinen Body, die HttpOnly-Cookie reicht.
public record RefreshRequest(string? RefreshToken);
@@ -0,0 +1,8 @@
namespace OmsorgCore.Api.Contracts;
public record TrashEmployeeFacilityDistanceResponse(
Guid Id,
Guid FacilityId,
Guid EmployeeId,
decimal DistanceKm,
DateTime? DeletedAt);
@@ -0,0 +1,4 @@
namespace OmsorgCore.Api.Contracts;
public record UpdateEmployeeFacilityDistanceRequest(
decimal DistanceKm);
@@ -20,6 +20,8 @@ public record UpdateFacilityRequest(
decimal? SundaySurchargePercent, decimal? SundaySurchargePercent,
decimal? HolidaySurchargePercent, decimal? HolidaySurchargePercent,
decimal? TravelCostRate, decimal? TravelCostRate,
string TravelCostMode,
decimal? TravelCostPerKm,
decimal? MinimumHours, decimal? MinimumHours,
string? BreakPolicy, string? BreakPolicy,
string? BillingInterval, string? BillingInterval,
@@ -0,0 +1,4 @@
namespace OmsorgCore.Api.Contracts;
public record UpdateMyFacilityDistanceRequest(
decimal DistanceKm);
@@ -82,13 +82,26 @@ public class AuthController : ControllerBase
await _dispatcher.DispatchAsync(new AuditEvent(result.UserId, result.Username, ipAddress, "Login"), cancellationToken); await _dispatcher.DispatchAsync(new AuditEvent(result.UserId, result.Username, ipAddress, "Login"), cancellationToken);
SetRefreshTokenCookie(result.RefreshToken); SetRefreshTokenCookie(result.RefreshToken);
return Ok(new LoginResponse(result.Token, result.ExpiresAt.Value, result.MustChangePassword)); return Ok(new LoginResponse(result.Token, result.RefreshToken, result.ExpiresAt.Value, result.MustChangePassword));
}
// Zwei Transportwege für den vorgelegten Refresh-Token, in dieser Reihenfolge geprüft:
// 1. HttpOnly-Cookie (Browser, omsorgapp) - schickt bewusst keinen Body.
// 2. Body (server-seitige API-Clients ohne Cookie-Jar, z. B. omsorgWeb per PHP-cURL/Guzzle).
private string? ResolveRefreshToken(string? bodyRefreshToken)
{
if (Request.Cookies.TryGetValue(RefreshTokenCookieName, out var cookieToken) && !string.IsNullOrEmpty(cookieToken))
{
return cookieToken;
}
return string.IsNullOrEmpty(bodyRefreshToken) ? null : bodyRefreshToken;
} }
[HttpPost("refresh")] [HttpPost("refresh")]
public async Task<ActionResult<LoginResponse>> Refresh(CancellationToken cancellationToken) public async Task<ActionResult<LoginResponse>> Refresh(RefreshRequest? request, CancellationToken cancellationToken)
{ {
if (!Request.Cookies.TryGetValue(RefreshTokenCookieName, out var refreshToken) || string.IsNullOrEmpty(refreshToken)) var refreshToken = ResolveRefreshToken(request?.RefreshToken);
if (refreshToken is null)
{ {
return Unauthorized(); return Unauthorized();
} }
@@ -99,19 +112,28 @@ public class AuthController : ControllerBase
return Unauthorized(); return Unauthorized();
} }
// Cookie nur erneuern, wenn der Aufrufer auch eine Cookie vorgelegt hat - ein reiner
// API-Client (Body-basiert) soll keine Cookie untergeschoben bekommen, die er nie abfragt.
if (Request.Cookies.ContainsKey(RefreshTokenCookieName))
{
SetRefreshTokenCookie(result.RefreshToken); SetRefreshTokenCookie(result.RefreshToken);
return Ok(new LoginResponse(result.Token, result.ExpiresAt.Value, result.MustChangePassword)); }
return Ok(new LoginResponse(result.Token, result.RefreshToken, result.ExpiresAt.Value, result.MustChangePassword));
} }
[HttpPost("logout")] [HttpPost("logout")]
public async Task<IActionResult> Logout(CancellationToken cancellationToken) public async Task<IActionResult> Logout(LogoutRequest? request, CancellationToken cancellationToken)
{ {
if (Request.Cookies.TryGetValue(RefreshTokenCookieName, out var refreshToken) && !string.IsNullOrEmpty(refreshToken)) var refreshToken = ResolveRefreshToken(request?.RefreshToken);
if (refreshToken is not null)
{ {
await _authService.RevokeAsync(refreshToken, cancellationToken); await _authService.RevokeAsync(refreshToken, cancellationToken);
} }
if (Request.Cookies.ContainsKey(RefreshTokenCookieName))
{
Response.Cookies.Delete(RefreshTokenCookieName, new CookieOptions { Path = "/api/auth" }); Response.Cookies.Delete(RefreshTokenCookieName, new CookieOptions { Path = "/api/auth" });
}
await _dispatcher.DispatchAsync( await _dispatcher.DispatchAsync(
new AuditEvent(_currentUserService.UserId, _currentUserService.Username, _currentUserService.IpAddress, "Logout"), new AuditEvent(_currentUserService.UserId, _currentUserService.Username, _currentUserService.IpAddress, "Logout"),
cancellationToken); cancellationToken);
@@ -0,0 +1,137 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OmsorgCore.Api.Contracts;
using OmsorgCore.Api.Security;
using OmsorgCore.Application.Services;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Domain.Enums;
namespace OmsorgCore.Api.Controllers;
/// <summary>
/// Mitarbeiter-Einrichtung-Entfernungen (km) sind eine 1:n-Unterressource von Facility (FR-EIN-4,
/// Grundlage für Facility.TravelCostMode == "ProKilometer") — kein eigenständiges Core-Objekt, daher
/// unter /api/facilities/{facilityId}/employee-distances und mit den gleichen Facilities-Rechten
/// gegated statt einem eigenen ModuleType, analog zu FacilityQualificationRate.
/// </summary>
[ApiController]
[Authorize]
[Route("api/facilities/{facilityId:guid}/employee-distances")]
public class EmployeeFacilityDistancesController : ControllerBase
{
private readonly IFacilityService _facilityService;
private readonly IEmployeeService _employeeService;
private readonly IEmployeeFacilityDistanceService _employeeFacilityDistanceService;
public EmployeeFacilityDistancesController(
IFacilityService facilityService,
IEmployeeService employeeService,
IEmployeeFacilityDistanceService employeeFacilityDistanceService)
{
_facilityService = facilityService;
_employeeService = employeeService;
_employeeFacilityDistanceService = employeeFacilityDistanceService;
}
[HttpGet]
[RequirePermission(ModuleType.Facilities, PermissionAction.View)]
public async Task<ActionResult<IReadOnlyList<EmployeeFacilityDistanceResponse>>> GetAll(Guid facilityId, CancellationToken cancellationToken)
{
if (await _facilityService.GetByIdAsync(facilityId, cancellationToken) is null)
{
return NotFound();
}
var distances = await _employeeFacilityDistanceService.GetByFacilityIdAsync(facilityId, cancellationToken);
return Ok(distances.Select(d => ToResponse(d, d.Employee)).ToList());
}
[HttpPost]
[RequirePermission(ModuleType.Facilities, PermissionAction.Create)]
public async Task<ActionResult<EmployeeFacilityDistanceResponse>> Create(Guid facilityId, CreateEmployeeFacilityDistanceRequest request, CancellationToken cancellationToken)
{
if (await _facilityService.GetByIdAsync(facilityId, cancellationToken) is null)
{
return NotFound();
}
var employee = await _employeeService.GetByIdAsync(request.EmployeeId, cancellationToken);
if (employee is null)
{
return BadRequest("EmployeeId verweist auf keinen existierenden Mitarbeiter.");
}
if (request.DistanceKm < 0)
{
return BadRequest("DistanceKm darf nicht negativ sein.");
}
if (await _employeeFacilityDistanceService.GetByFacilityAndEmployeeAsync(facilityId, request.EmployeeId, cancellationToken) is not null)
{
return BadRequest("Für diesen Mitarbeiter existiert bereits eine Entfernung zu dieser Einrichtung.");
}
var distance = new EmployeeFacilityDistance
{
FacilityId = facilityId,
EmployeeId = request.EmployeeId,
DistanceKm = request.DistanceKm
};
var created = await _employeeFacilityDistanceService.CreateAsync(distance, cancellationToken);
return CreatedAtAction(nameof(GetAll), new { facilityId }, ToResponse(created, employee));
}
[HttpPut("{id:guid}")]
[RequirePermission(ModuleType.Facilities, PermissionAction.Edit)]
public async Task<ActionResult<EmployeeFacilityDistanceResponse>> Update(Guid facilityId, Guid id, UpdateEmployeeFacilityDistanceRequest request, CancellationToken cancellationToken)
{
var existing = await _employeeFacilityDistanceService.GetByIdAsync(id, cancellationToken);
if (existing is null || existing.FacilityId != facilityId)
{
return NotFound();
}
if (request.DistanceKm < 0)
{
return BadRequest("DistanceKm darf nicht negativ sein.");
}
var updates = new EmployeeFacilityDistance
{
DistanceKm = request.DistanceKm
};
var updated = await _employeeFacilityDistanceService.UpdateAsync(id, updates, cancellationToken);
if (updated is null)
{
return NotFound();
}
var employee = await _employeeService.GetByIdAsync(updated.EmployeeId, cancellationToken);
return Ok(ToResponse(updated, employee!));
}
[HttpDelete("{id:guid}")]
[RequirePermission(ModuleType.Facilities, PermissionAction.Delete)]
public async Task<IActionResult> Delete(Guid facilityId, Guid id, CancellationToken cancellationToken)
{
var existing = await _employeeFacilityDistanceService.GetByIdAsync(id, cancellationToken);
if (existing is null || existing.FacilityId != facilityId)
{
return NotFound();
}
var deleted = await _employeeFacilityDistanceService.DeleteAsync(id, cancellationToken);
return deleted ? NoContent() : NotFound();
}
private static EmployeeFacilityDistanceResponse ToResponse(EmployeeFacilityDistance distance, Employee employee)
=> new(
distance.Id,
distance.FacilityId,
distance.EmployeeId,
employee.FirstName,
employee.LastName,
distance.DistanceKm);
}
@@ -19,6 +19,7 @@ public class FacilitiesController : ControllerBase
private const string FacilityTypeListKey = "FacilityType"; private const string FacilityTypeListKey = "FacilityType";
private const string FollowUpPeriodsListKey = "FollowUpPeriods"; private const string FollowUpPeriodsListKey = "FollowUpPeriods";
private const string BillingIntervalListKey = "BillingInterval"; private const string BillingIntervalListKey = "BillingInterval";
private static readonly string[] TravelCostModes = { "Pauschale", "ProKilometer" };
private readonly IFacilityService _facilityService; private readonly IFacilityService _facilityService;
private readonly IValueListRepository _valueListRepository; private readonly IValueListRepository _valueListRepository;
@@ -222,6 +223,7 @@ public class FacilitiesController : ControllerBase
if (request.BillingRate is < 0 if (request.BillingRate is < 0
|| request.TravelCostRate is < 0 || request.TravelCostRate is < 0
|| request.TravelCostPerKm is < 0
|| request.MinimumHours is < 0 || request.MinimumHours is < 0
|| request.NightSurchargePercent is < 0 || request.NightSurchargePercent is < 0
|| request.SaturdaySurchargePercent is < 0 || request.SaturdaySurchargePercent is < 0
@@ -232,6 +234,11 @@ public class FacilitiesController : ControllerBase
return BadRequest("Konditionswerte dürfen nicht negativ sein."); 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 updates = new Facility var updates = new Facility
{ {
Name = request.Name, Name = request.Name,
@@ -243,6 +250,8 @@ public class FacilitiesController : ControllerBase
SundaySurchargePercent = request.SundaySurchargePercent, SundaySurchargePercent = request.SundaySurchargePercent,
HolidaySurchargePercent = request.HolidaySurchargePercent, HolidaySurchargePercent = request.HolidaySurchargePercent,
TravelCostRate = request.TravelCostRate, TravelCostRate = request.TravelCostRate,
TravelCostMode = request.TravelCostMode,
TravelCostPerKm = request.TravelCostPerKm,
MinimumHours = request.MinimumHours, MinimumHours = request.MinimumHours,
BreakPolicy = request.BreakPolicy, BreakPolicy = request.BreakPolicy,
BillingInterval = request.BillingInterval, BillingInterval = request.BillingInterval,
@@ -319,6 +328,8 @@ public class FacilitiesController : ControllerBase
facility.SundaySurchargePercent, facility.SundaySurchargePercent,
facility.HolidaySurchargePercent, facility.HolidaySurchargePercent,
facility.TravelCostRate, facility.TravelCostRate,
facility.TravelCostMode,
facility.TravelCostPerKm,
facility.MinimumHours, facility.MinimumHours,
facility.BreakPolicy, facility.BreakPolicy,
facility.BillingInterval, facility.BillingInterval,
@@ -0,0 +1,143 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using OmsorgCore.Api.Contracts;
using OmsorgCore.Api.Security;
using OmsorgCore.Application.Abstractions;
using OmsorgCore.Application.Services;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Domain.Enums;
namespace OmsorgCore.Api.Controllers;
/// <summary>
/// Selbstbedienungs-Endpoint für Außendienst-Mitarbeiter (OMSORG Connect,
/// `omsorgWeb/mitarbeiter-app`): eigene Fahrtstrecke (km) je Einrichtung pflegen, Grundlage für die
/// kilometerbasierte Fahrtkostenabrechnung (Facility.TravelCostMode == "ProKilometer", FR-EIN-4).
/// Bewusst ein eigener ModuleType (EmployeeFacilityDistances) statt Facilities-Rechten — der
/// Außendienst darf hier ausschließlich seine eigenen Datensätze sehen/anlegen/bearbeiten, nicht die
/// Konditionen/CRM-Daten einer Einrichtung. EmployeeId kommt IMMER serverseitig aus dem JWT-Claim,
/// nie vom Client (analog AbsencesController/TimeEntriesController).
/// </summary>
[ApiController]
[Authorize]
[Route("api/me/facility-distances")]
public class MyFacilityDistancesController : ControllerBase
{
private readonly IEmployeeFacilityDistanceService _employeeFacilityDistanceService;
private readonly IFacilityService _facilityService;
private readonly ICurrentUserService _currentUserService;
public MyFacilityDistancesController(
IEmployeeFacilityDistanceService employeeFacilityDistanceService,
IFacilityService facilityService,
ICurrentUserService currentUserService)
{
_employeeFacilityDistanceService = employeeFacilityDistanceService;
_facilityService = facilityService;
_currentUserService = currentUserService;
}
[HttpGet]
[RequirePermission(ModuleType.EmployeeFacilityDistances, PermissionAction.View)]
public async Task<ActionResult<IReadOnlyList<MyFacilityDistanceResponse>>> GetAll(CancellationToken cancellationToken)
{
var employeeId = RequireOwnEmployeeId();
if (employeeId is null)
{
return BadRequest("Dieser Benutzer ist mit keinem Mitarbeiter verknüpft.");
}
var facilities = (await _facilityService.GetAllAsync(cancellationToken)).ToDictionary(f => f.Id);
var all = new List<MyFacilityDistanceResponse>();
foreach (var facility in facilities.Values)
{
var distance = await _employeeFacilityDistanceService.GetByFacilityAndEmployeeAsync(facility.Id, employeeId.Value, cancellationToken);
if (distance is not null)
{
all.Add(new MyFacilityDistanceResponse(distance.Id, facility.Id, facility.Name, distance.DistanceKm));
}
}
return Ok(all);
}
[HttpGet("facilities")]
[RequirePermission(ModuleType.EmployeeFacilityDistances, PermissionAction.View)]
public async Task<ActionResult<IReadOnlyList<FacilityOptionResponse>>> GetFacilityOptions(CancellationToken cancellationToken)
{
var facilities = await _facilityService.GetAllAsync(cancellationToken);
return Ok(facilities.OrderBy(f => f.Name).Select(f => new FacilityOptionResponse(f.Id, f.Name)).ToList());
}
[HttpPost]
[RequirePermission(ModuleType.EmployeeFacilityDistances, PermissionAction.Create)]
public async Task<ActionResult<MyFacilityDistanceResponse>> Create(CreateMyFacilityDistanceRequest request, CancellationToken cancellationToken)
{
var employeeId = RequireOwnEmployeeId();
if (employeeId is null)
{
return BadRequest("Dieser Benutzer ist mit keinem Mitarbeiter verknüpft.");
}
var facility = await _facilityService.GetByIdAsync(request.FacilityId, cancellationToken);
if (facility is null)
{
return BadRequest("FacilityId verweist auf keine existierende Einrichtung.");
}
if (request.DistanceKm < 0)
{
return BadRequest("DistanceKm darf nicht negativ sein.");
}
if (await _employeeFacilityDistanceService.GetByFacilityAndEmployeeAsync(request.FacilityId, employeeId.Value, cancellationToken) is not null)
{
return BadRequest("Für diese Einrichtung existiert bereits eine eigene Entfernung.");
}
var distance = new EmployeeFacilityDistance
{
FacilityId = request.FacilityId,
EmployeeId = employeeId.Value,
DistanceKm = request.DistanceKm
};
var created = await _employeeFacilityDistanceService.CreateAsync(distance, cancellationToken);
return CreatedAtAction(nameof(GetAll), new MyFacilityDistanceResponse(created.Id, facility.Id, facility.Name, created.DistanceKm));
}
[HttpPut("{id:guid}")]
[RequirePermission(ModuleType.EmployeeFacilityDistances, PermissionAction.Edit)]
public async Task<ActionResult<MyFacilityDistanceResponse>> Update(Guid id, UpdateMyFacilityDistanceRequest request, CancellationToken cancellationToken)
{
var employeeId = RequireOwnEmployeeId();
if (employeeId is null)
{
return BadRequest("Dieser Benutzer ist mit keinem Mitarbeiter verknüpft.");
}
var existing = await _employeeFacilityDistanceService.GetByIdAsync(id, cancellationToken);
if (existing is null || existing.EmployeeId != employeeId.Value)
{
return NotFound();
}
if (request.DistanceKm < 0)
{
return BadRequest("DistanceKm darf nicht negativ sein.");
}
var updated = await _employeeFacilityDistanceService.UpdateAsync(id, new EmployeeFacilityDistance { DistanceKm = request.DistanceKm }, cancellationToken);
if (updated is null)
{
return NotFound();
}
var facility = await _facilityService.GetByIdAsync(updated.FacilityId, cancellationToken);
return Ok(new MyFacilityDistanceResponse(updated.Id, updated.FacilityId, facility?.Name ?? "", updated.DistanceKm));
}
private Guid? RequireOwnEmployeeId()
=> _currentUserService.EmployeeId is { } id && id != Guid.Empty ? id : null;
}
@@ -84,4 +84,22 @@ public class RolesController : ControllerBase
return NoContent(); return NoContent();
} }
[HttpDelete("{id:guid}")]
[RequirePermission(ModuleType.UserManagement, PermissionAction.Delete)]
public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
{
var result = await _roleService.DeleteAsync(id, cancellationToken);
if (!result.Success)
{
return result.FailureReason switch
{
DeleteRoleFailureReason.RoleNotFound => NotFound("Rolle nicht gefunden."),
DeleteRoleFailureReason.InUse => Conflict($"Diese Rolle ist noch {result.AssignedUserCount} Benutzer(n) zugewiesen und kann nicht gelöscht werden."),
_ => BadRequest()
};
}
return NoContent();
}
} }
@@ -26,6 +26,7 @@ public class TrashController : ControllerBase
private readonly IOrderService _orderService; private readonly IOrderService _orderService;
private readonly IFacilityContactService _facilityContactService; private readonly IFacilityContactService _facilityContactService;
private readonly IFacilityQualificationRateService _facilityQualificationRateService; private readonly IFacilityQualificationRateService _facilityQualificationRateService;
private readonly IEmployeeFacilityDistanceService _employeeFacilityDistanceService;
private readonly IAbsenceService _absenceService; private readonly IAbsenceService _absenceService;
private readonly ITimeEntryService _timeEntryService; private readonly ITimeEntryService _timeEntryService;
@@ -36,6 +37,7 @@ public class TrashController : ControllerBase
IOrderService orderService, IOrderService orderService,
IFacilityContactService facilityContactService, IFacilityContactService facilityContactService,
IFacilityQualificationRateService facilityQualificationRateService, IFacilityQualificationRateService facilityQualificationRateService,
IEmployeeFacilityDistanceService employeeFacilityDistanceService,
IAbsenceService absenceService, IAbsenceService absenceService,
ITimeEntryService timeEntryService) ITimeEntryService timeEntryService)
{ {
@@ -45,6 +47,7 @@ public class TrashController : ControllerBase
_orderService = orderService; _orderService = orderService;
_facilityContactService = facilityContactService; _facilityContactService = facilityContactService;
_facilityQualificationRateService = facilityQualificationRateService; _facilityQualificationRateService = facilityQualificationRateService;
_employeeFacilityDistanceService = employeeFacilityDistanceService;
_absenceService = absenceService; _absenceService = absenceService;
_timeEntryService = timeEntryService; _timeEntryService = timeEntryService;
} }
@@ -145,6 +148,22 @@ public class TrashController : ControllerBase
return restored ? NoContent() : NotFound(); return restored ? NoContent() : NotFound();
} }
[HttpGet("employee-facility-distances")]
[RequirePermission(ModuleType.Facilities, PermissionAction.Recover)]
public async Task<ActionResult<IReadOnlyList<TrashEmployeeFacilityDistanceResponse>>> GetDeletedEmployeeFacilityDistances([FromQuery] string? search, CancellationToken cancellationToken)
{
var distances = await _employeeFacilityDistanceService.GetDeletedAsync(search, cancellationToken);
return Ok(distances.Select(d => new TrashEmployeeFacilityDistanceResponse(d.Id, d.FacilityId, d.EmployeeId, d.DistanceKm, d.DeletedAt)).ToList());
}
[HttpPost("employee-facility-distances/{id:guid}/restore")]
[RequirePermission(ModuleType.Facilities, PermissionAction.Recover)]
public async Task<IActionResult> RestoreEmployeeFacilityDistance(Guid id, CancellationToken cancellationToken)
{
var restored = await _employeeFacilityDistanceService.RestoreAsync(id, cancellationToken);
return restored ? NoContent() : NotFound();
}
[HttpGet("absences")] [HttpGet("absences")]
[RequirePermission(ModuleType.Absences, PermissionAction.Recover)] [RequirePermission(ModuleType.Absences, PermissionAction.Recover)]
public async Task<ActionResult<IReadOnlyList<TrashAbsenceResponse>>> GetDeletedAbsences([FromQuery] string? search, CancellationToken cancellationToken) public async Task<ActionResult<IReadOnlyList<TrashAbsenceResponse>>> GetDeletedAbsences([FromQuery] string? search, CancellationToken cancellationToken)
@@ -0,0 +1,16 @@
using OmsorgCore.Domain.Entities;
namespace OmsorgCore.Application.Abstractions;
public interface IEmployeeFacilityDistanceRepository
{
Task<EmployeeFacilityDistance?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<EmployeeFacilityDistance>> GetByFacilityIdAsync(Guid facilityId, CancellationToken cancellationToken = default);
Task<EmployeeFacilityDistance?> GetByFacilityAndEmployeeAsync(Guid facilityId, Guid employeeId, CancellationToken cancellationToken = default);
Task AddAsync(EmployeeFacilityDistance distance, CancellationToken cancellationToken = default);
Task UpdateAsync(EmployeeFacilityDistance distance, CancellationToken cancellationToken = default);
Task<bool> SoftDeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<EmployeeFacilityDistance>> GetDeletedAsync(string? search, CancellationToken cancellationToken = default);
Task<bool> RestoreAsync(Guid id, CancellationToken cancellationToken = default);
Task SaveChangesAsync(CancellationToken cancellationToken = default);
}
@@ -18,5 +18,6 @@ public interface IRoleRepository
Task AddPermissionRangeAsync(IEnumerable<RolePermission> permissions, CancellationToken cancellationToken = default); Task AddPermissionRangeAsync(IEnumerable<RolePermission> permissions, CancellationToken cancellationToken = default);
Task<bool> ExistsByNameAsync(string name, CancellationToken cancellationToken = default); Task<bool> ExistsByNameAsync(string name, CancellationToken cancellationToken = default);
Task AddAsync(Role role, CancellationToken cancellationToken = default); Task AddAsync(Role role, CancellationToken cancellationToken = default);
Task RemoveAsync(Role role, CancellationToken cancellationToken = default);
Task SaveChangesAsync(CancellationToken cancellationToken = default); Task SaveChangesAsync(CancellationToken cancellationToken = default);
} }
@@ -14,6 +14,7 @@ public static class DependencyInjection
services.AddScoped<IFacilityService, FacilityService>(); services.AddScoped<IFacilityService, FacilityService>();
services.AddScoped<IFacilityContactService, FacilityContactService>(); services.AddScoped<IFacilityContactService, FacilityContactService>();
services.AddScoped<IFacilityQualificationRateService, FacilityQualificationRateService>(); services.AddScoped<IFacilityQualificationRateService, FacilityQualificationRateService>();
services.AddScoped<IEmployeeFacilityDistanceService, EmployeeFacilityDistanceService>();
services.AddScoped<IContractService, ContractService>(); services.AddScoped<IContractService, ContractService>();
services.AddScoped<IOrderService, OrderService>(); services.AddScoped<IOrderService, OrderService>();
services.AddScoped<IAbsenceService, AbsenceService>(); services.AddScoped<IAbsenceService, AbsenceService>();
@@ -0,0 +1,28 @@
namespace OmsorgCore.Application.Services;
public enum DeleteRoleFailureReason
{
RoleNotFound,
InUse
}
public class DeleteRoleResult
{
public bool Success { get; init; }
public DeleteRoleFailureReason? FailureReason { get; init; }
/// <summary>Nur gesetzt bei <see cref="DeleteRoleFailureReason.InUse"/> - Anzahl der Benutzer, die dieser Rolle noch zugewiesen sind.</summary>
public int AssignedUserCount { get; init; }
public static DeleteRoleResult Fail(DeleteRoleFailureReason reason, int assignedUserCount = 0) => new()
{
Success = false,
FailureReason = reason,
AssignedUserCount = assignedUserCount
};
public static DeleteRoleResult Ok() => new()
{
Success = true
};
}
@@ -0,0 +1,71 @@
using OmsorgCore.Application.Abstractions;
using OmsorgCore.Domain.Entities;
namespace OmsorgCore.Application.Services;
public class EmployeeFacilityDistanceService : IEmployeeFacilityDistanceService
{
private readonly IEmployeeFacilityDistanceRepository _employeeFacilityDistanceRepository;
public EmployeeFacilityDistanceService(IEmployeeFacilityDistanceRepository employeeFacilityDistanceRepository)
{
_employeeFacilityDistanceRepository = employeeFacilityDistanceRepository;
}
public Task<IReadOnlyList<EmployeeFacilityDistance>> GetByFacilityIdAsync(Guid facilityId, CancellationToken cancellationToken = default)
=> _employeeFacilityDistanceRepository.GetByFacilityIdAsync(facilityId, cancellationToken);
public Task<EmployeeFacilityDistance?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
=> _employeeFacilityDistanceRepository.GetByIdAsync(id, cancellationToken);
public Task<EmployeeFacilityDistance?> GetByFacilityAndEmployeeAsync(Guid facilityId, Guid employeeId, CancellationToken cancellationToken = default)
=> _employeeFacilityDistanceRepository.GetByFacilityAndEmployeeAsync(facilityId, employeeId, cancellationToken);
public async Task<EmployeeFacilityDistance> CreateAsync(EmployeeFacilityDistance distance, CancellationToken cancellationToken = default)
{
await _employeeFacilityDistanceRepository.AddAsync(distance, cancellationToken);
await _employeeFacilityDistanceRepository.SaveChangesAsync(cancellationToken);
return distance;
}
public async Task<EmployeeFacilityDistance?> UpdateAsync(Guid id, EmployeeFacilityDistance updates, CancellationToken cancellationToken = default)
{
var distance = await _employeeFacilityDistanceRepository.GetByIdAsync(id, cancellationToken);
if (distance is null)
{
return null;
}
distance.DistanceKm = updates.DistanceKm;
distance.UpdatedAt = DateTime.UtcNow;
await _employeeFacilityDistanceRepository.UpdateAsync(distance, cancellationToken);
await _employeeFacilityDistanceRepository.SaveChangesAsync(cancellationToken);
return distance;
}
public async Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
var deleted = await _employeeFacilityDistanceRepository.SoftDeleteAsync(id, cancellationToken);
if (deleted)
{
await _employeeFacilityDistanceRepository.SaveChangesAsync(cancellationToken);
}
return deleted;
}
public Task<IReadOnlyList<EmployeeFacilityDistance>> GetDeletedAsync(string? search, CancellationToken cancellationToken = default)
=> _employeeFacilityDistanceRepository.GetDeletedAsync(search, cancellationToken);
public async Task<bool> RestoreAsync(Guid id, CancellationToken cancellationToken = default)
{
var restored = await _employeeFacilityDistanceRepository.RestoreAsync(id, cancellationToken);
if (restored)
{
await _employeeFacilityDistanceRepository.SaveChangesAsync(cancellationToken);
}
return restored;
}
}
@@ -55,6 +55,19 @@ public class FacilityService : IFacilityService
facility.BillingCountry = updates.BillingCountry; facility.BillingCountry = updates.BillingCountry;
facility.CrmStatus = updates.CrmStatus; facility.CrmStatus = updates.CrmStatus;
facility.FollowUpDueDate = updates.FollowUpDueDate; facility.FollowUpDueDate = updates.FollowUpDueDate;
facility.BillingRate = updates.BillingRate;
facility.NightSurchargePercent = updates.NightSurchargePercent;
facility.SaturdaySurchargePercent = updates.SaturdaySurchargePercent;
facility.SundaySurchargePercent = updates.SundaySurchargePercent;
facility.HolidaySurchargePercent = updates.HolidaySurchargePercent;
facility.TravelCostRate = updates.TravelCostRate;
facility.TravelCostMode = updates.TravelCostMode;
facility.TravelCostPerKm = updates.TravelCostPerKm;
facility.MinimumHours = updates.MinimumHours;
facility.BreakPolicy = updates.BreakPolicy;
facility.BillingInterval = updates.BillingInterval;
facility.PaymentTermDays = updates.PaymentTermDays;
facility.IndividualAgreements = updates.IndividualAgreements;
facility.UpdatedAt = DateTime.UtcNow; facility.UpdatedAt = DateTime.UtcNow;
await _facilityRepository.UpdateAsync(facility, cancellationToken); await _facilityRepository.UpdateAsync(facility, cancellationToken);
@@ -0,0 +1,15 @@
using OmsorgCore.Domain.Entities;
namespace OmsorgCore.Application.Services;
public interface IEmployeeFacilityDistanceService
{
Task<IReadOnlyList<EmployeeFacilityDistance>> GetByFacilityIdAsync(Guid facilityId, CancellationToken cancellationToken = default);
Task<EmployeeFacilityDistance?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default);
Task<EmployeeFacilityDistance?> GetByFacilityAndEmployeeAsync(Guid facilityId, Guid employeeId, CancellationToken cancellationToken = default);
Task<EmployeeFacilityDistance> CreateAsync(EmployeeFacilityDistance distance, CancellationToken cancellationToken = default);
Task<EmployeeFacilityDistance?> UpdateAsync(Guid id, EmployeeFacilityDistance updates, CancellationToken cancellationToken = default);
Task<bool> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
Task<IReadOnlyList<EmployeeFacilityDistance>> GetDeletedAsync(string? search, CancellationToken cancellationToken = default);
Task<bool> RestoreAsync(Guid id, CancellationToken cancellationToken = default);
}
@@ -16,4 +16,7 @@ public interface IRoleService
Guid roleId, Guid roleId,
IReadOnlyList<(ModuleType Module, PermissionAction Action, PermissionScope Scope)> permissions, IReadOnlyList<(ModuleType Module, PermissionAction Action, PermissionScope Scope)> permissions,
CancellationToken cancellationToken = default); CancellationToken cancellationToken = default);
/// <summary>Löschschutz: schlägt fehl, solange mindestens ein User dieser Rolle zugewiesen ist (analog ValueListService.DeleteItemAsync).</summary>
Task<DeleteRoleResult> DeleteAsync(Guid id, CancellationToken cancellationToken = default);
} }
@@ -8,11 +8,13 @@ public class RoleService : IRoleService
{ {
private readonly IRoleRepository _roleRepository; private readonly IRoleRepository _roleRepository;
private readonly IPermissionService _permissionService; private readonly IPermissionService _permissionService;
private readonly IUserRepository _userRepository;
public RoleService(IRoleRepository roleRepository, IPermissionService permissionService) public RoleService(IRoleRepository roleRepository, IPermissionService permissionService, IUserRepository userRepository)
{ {
_roleRepository = roleRepository; _roleRepository = roleRepository;
_permissionService = permissionService; _permissionService = permissionService;
_userRepository = userRepository;
} }
public Task<IReadOnlyList<Role>> GetAllAsync(CancellationToken cancellationToken = default) public Task<IReadOnlyList<Role>> GetAllAsync(CancellationToken cancellationToken = default)
@@ -62,4 +64,23 @@ public class RoleService : IRoleService
await _permissionService.InvalidateRolePermissionsAsync(roleId, cancellationToken); await _permissionService.InvalidateRolePermissionsAsync(roleId, cancellationToken);
return UpdateRolePermissionsResult.Ok(); return UpdateRolePermissionsResult.Ok();
} }
public async Task<DeleteRoleResult> DeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
var role = await _roleRepository.GetByIdAsync(id, cancellationToken);
if (role is null)
{
return DeleteRoleResult.Fail(DeleteRoleFailureReason.RoleNotFound);
}
var assignedUserIds = await _userRepository.GetUserIdsByRoleAsync(id, cancellationToken);
if (assignedUserIds.Count > 0)
{
return DeleteRoleResult.Fail(DeleteRoleFailureReason.InUse, assignedUserIds.Count);
}
await _roleRepository.RemoveAsync(role, cancellationToken);
await _roleRepository.SaveChangesAsync(cancellationToken);
return DeleteRoleResult.Ok();
}
} }
@@ -0,0 +1,21 @@
using OmsorgCore.Domain.Common;
namespace OmsorgCore.Domain.Entities;
/// <summary>
/// Entfernung (km) zwischen einem Mitarbeiter und einer Einrichtung — Grundlage für die
/// kilometerbasierte Fahrtkostenabrechnung (<see cref="Facility.TravelCostMode"/> == "ProKilometer",
/// FR-EIN-4). Pro Mitarbeiter/Einrichtung individuell statt an Facility allein, da jeder Mitarbeiter
/// von einem anderen Wohnort anfährt. 1:n-Unterressource von Facility, kein eigenständiges Core-Objekt
/// — exakt nach dem Muster von <see cref="FacilityQualificationRate"/>.
/// </summary>
public class EmployeeFacilityDistance : AuditableEntity
{
public Guid FacilityId { get; set; }
public Facility Facility { get; set; } = null!;
public Guid EmployeeId { get; set; }
public Employee Employee { get; set; } = null!;
public decimal DistanceKm { get; set; }
}
@@ -33,7 +33,12 @@ public class Facility : AuditableEntity
public decimal? SaturdaySurchargePercent { get; set; } public decimal? SaturdaySurchargePercent { get; set; }
public decimal? SundaySurchargePercent { get; set; } public decimal? SundaySurchargePercent { get; set; }
public decimal? HolidaySurchargePercent { get; set; } public decimal? HolidaySurchargePercent { get; set; }
// TravelCostMode entscheidet, welches der beiden Felder die Rechnungserstellung (FR-RE-1,
// sobald umgesetzt) verwendet: "Pauschale" -> TravelCostRate (EUR je Einsatz), "ProKilometer"
// -> TravelCostPerKm (EUR/km) * EmployeeFacilityDistance.DistanceKm des jeweiligen Mitarbeiters.
public string TravelCostMode { get; set; } = "Pauschale";
public decimal? TravelCostRate { get; set; } public decimal? TravelCostRate { get; set; }
public decimal? TravelCostPerKm { get; set; }
public decimal? MinimumHours { get; set; } public decimal? MinimumHours { get; set; }
public string? BreakPolicy { get; set; } public string? BreakPolicy { get; set; }
public string? BillingInterval { get; set; } public string? BillingInterval { get; set; }
@@ -18,5 +18,6 @@ public enum ModuleType
Documents, Documents,
Users, Users,
Configuration, Configuration,
Absences Absences,
EmployeeFacilityDistances
} }
@@ -35,6 +35,7 @@ public static class DependencyInjection
services.AddScoped<IFacilityRepository, FacilityRepository>(); services.AddScoped<IFacilityRepository, FacilityRepository>();
services.AddScoped<IFacilityContactRepository, FacilityContactRepository>(); services.AddScoped<IFacilityContactRepository, FacilityContactRepository>();
services.AddScoped<IFacilityQualificationRateRepository, FacilityQualificationRateRepository>(); services.AddScoped<IFacilityQualificationRateRepository, FacilityQualificationRateRepository>();
services.AddScoped<IEmployeeFacilityDistanceRepository, EmployeeFacilityDistanceRepository>();
services.AddScoped<IContractRepository, ContractRepository>(); services.AddScoped<IContractRepository, ContractRepository>();
services.AddScoped<IOrderRepository, OrderRepository>(); services.AddScoped<IOrderRepository, OrderRepository>();
services.AddScoped<IAbsenceRepository, AbsenceRepository>(); services.AddScoped<IAbsenceRepository, AbsenceRepository>();
@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using OmsorgCore.Domain.Entities;
namespace OmsorgCore.Infrastructure.Persistence.Configurations;
public class EmployeeFacilityDistanceConfiguration : IEntityTypeConfiguration<EmployeeFacilityDistance>
{
public void Configure(EntityTypeBuilder<EmployeeFacilityDistance> builder)
{
builder.ToTable("employee_facility_distances");
builder.HasKey(d => d.Id);
builder.Property(d => d.DistanceKm).IsRequired().HasColumnType("decimal(10,2)");
builder.HasOne(d => d.Facility)
.WithMany()
.HasForeignKey(d => d.FacilityId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasOne(d => d.Employee)
.WithMany()
.HasForeignKey(d => d.EmployeeId)
.OnDelete(DeleteBehavior.Restrict);
// Soft-gelöschte Distanzen sind für alle normalen Queries unsichtbar.
builder.HasQueryFilter(d => !d.IsDeleted);
}
}
@@ -29,6 +29,8 @@ public class FacilityConfiguration : IEntityTypeConfiguration<Facility>
builder.Property(f => f.SundaySurchargePercent).HasColumnType("decimal(5,2)"); builder.Property(f => f.SundaySurchargePercent).HasColumnType("decimal(5,2)");
builder.Property(f => f.HolidaySurchargePercent).HasColumnType("decimal(5,2)"); builder.Property(f => f.HolidaySurchargePercent).HasColumnType("decimal(5,2)");
builder.Property(f => f.TravelCostRate).HasColumnType("decimal(10,2)"); 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.MinimumHours).HasColumnType("decimal(5,2)"); builder.Property(f => f.MinimumHours).HasColumnType("decimal(5,2)");
builder.Property(f => f.BreakPolicy).HasMaxLength(1000); builder.Property(f => f.BreakPolicy).HasMaxLength(1000);
builder.Property(f => f.BillingInterval).HasMaxLength(50); builder.Property(f => f.BillingInterval).HasMaxLength(50);
@@ -69,7 +69,11 @@ public static class DbSeeder
/// User.EmployeeId. Ebenso Absences.{Create,View,Edit} mit PermissionScope.Own (FR-CON-1/FR-EM-3): /// User.EmployeeId. Ebenso Absences.{Create,View,Edit} mit PermissionScope.Own (FR-CON-1/FR-EM-3):
/// der Außendienst stellt eigene Abwesenheits-/Urlaubs-/Krankmeldungsanträge über /// der Außendienst stellt eigene Abwesenheits-/Urlaubs-/Krankmeldungsanträge über
/// `omsorgWeb/mitarbeiter-app` gegen dieses Backend, Genehmigen/Ablehnen bleibt Büro-Rollen /// `omsorgWeb/mitarbeiter-app` gegen dieses Backend, Genehmigen/Ablehnen bleibt Büro-Rollen
/// vorbehalten (kein Approve-Recht hier). "Geschäftsführung" bekommt AuditLog automatisch mit /// vorbehalten (kein Approve-Recht hier). Ebenso EmployeeFacilityDistances.{Create,View,Edit} mit
/// PermissionScope.Own (FR-EIN-4, kilometerbasierte Fahrtkostenabrechnung, siehe
/// MyFacilityDistancesController) - der Außendienst pflegt seine eigenen Fahrtstrecken je
/// Einrichtung selbst über `omsorgWeb/mitarbeiter-app`, hat aber KEIN Facilities-Recht und sieht
/// daher keine Konditionen/CRM-Daten einer Einrichtung, nur deren Namen zur Auswahl. "Geschäftsführung" bekommt AuditLog automatisch mit
/// (iteriert alle ModuleType-Werte generisch, /// (iteriert alle ModuleType-Werte generisch,
/// siehe unten) - alle anderen Rollen listen ihre Module explizit auf und bekommen AuditLog dadurch /// siehe unten) - alle anderen Rollen listen ihre Module explizit auf und bekommen AuditLog dadurch
/// bewusst NICHT (Audit-Log ist per Default nur für Geschäftsführung sichtbar). /// bewusst NICHT (Audit-Log ist per Default nur für Geschäftsführung sichtbar).
@@ -104,7 +108,8 @@ public static class DbSeeder
(ModuleType.Employees, new[] { PermissionAction.View }, PermissionScope.Own), (ModuleType.Employees, new[] { PermissionAction.View }, PermissionScope.Own),
(ModuleType.Contracts, new[] { PermissionAction.View }, PermissionScope.Own), (ModuleType.Contracts, new[] { PermissionAction.View }, PermissionScope.Own),
(ModuleType.Absences, new[] { PermissionAction.Create, PermissionAction.View, PermissionAction.Edit }, PermissionScope.Own), (ModuleType.Absences, new[] { PermissionAction.Create, PermissionAction.View, PermissionAction.Edit }, PermissionScope.Own),
(ModuleType.TimeEntries, new[] { PermissionAction.Create, PermissionAction.View, PermissionAction.Edit }, PermissionScope.Own) (ModuleType.TimeEntries, new[] { PermissionAction.Create, PermissionAction.View, PermissionAction.Edit }, PermissionScope.Own),
(ModuleType.EmployeeFacilityDistances, new[] { PermissionAction.Create, PermissionAction.View, PermissionAction.Edit }, PermissionScope.Own)
}, cancellationToken); }, cancellationToken);
} }
@@ -0,0 +1,84 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace OmsorgCore.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class AddTravelCostModeAndEmployeeFacilityDistances : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "TravelCostMode",
table: "facilities",
type: "character varying(20)",
maxLength: 20,
nullable: false,
defaultValue: "Pauschale");
migrationBuilder.AddColumn<decimal>(
name: "TravelCostPerKm",
table: "facilities",
type: "numeric(10,2)",
nullable: true);
migrationBuilder.CreateTable(
name: "employee_facility_distances",
columns: table => new
{
Id = table.Column<Guid>(type: "uuid", nullable: false),
FacilityId = table.Column<Guid>(type: "uuid", nullable: false),
EmployeeId = table.Column<Guid>(type: "uuid", nullable: false),
DistanceKm = table.Column<decimal>(type: "numeric(10,2)", nullable: false),
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
IsDeleted = table.Column<bool>(type: "boolean", nullable: false),
DeletedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_employee_facility_distances", x => x.Id);
table.ForeignKey(
name: "FK_employee_facility_distances_employees_EmployeeId",
column: x => x.EmployeeId,
principalTable: "employees",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_employee_facility_distances_facilities_FacilityId",
column: x => x.FacilityId,
principalTable: "facilities",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateIndex(
name: "IX_employee_facility_distances_EmployeeId",
table: "employee_facility_distances",
column: "EmployeeId");
migrationBuilder.CreateIndex(
name: "IX_employee_facility_distances_FacilityId",
table: "employee_facility_distances",
column: "FacilityId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "employee_facility_distances");
migrationBuilder.DropColumn(
name: "TravelCostMode",
table: "facilities");
migrationBuilder.DropColumn(
name: "TravelCostPerKm",
table: "facilities");
}
}
}
@@ -361,6 +361,42 @@ namespace OmsorgCore.Infrastructure.Persistence.Migrations
b.ToTable("employees", (string)null); b.ToTable("employees", (string)null);
}); });
modelBuilder.Entity("OmsorgCore.Domain.Entities.EmployeeFacilityDistance", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("uuid");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<DateTime?>("DeletedAt")
.HasColumnType("timestamp with time zone");
b.Property<decimal>("DistanceKm")
.HasColumnType("decimal(10,2)");
b.Property<Guid>("EmployeeId")
.HasColumnType("uuid");
b.Property<Guid>("FacilityId")
.HasColumnType("uuid");
b.Property<bool>("IsDeleted")
.HasColumnType("boolean");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.HasKey("Id");
b.HasIndex("EmployeeId");
b.HasIndex("FacilityId");
b.ToTable("employee_facility_distances", (string)null);
});
modelBuilder.Entity("OmsorgCore.Domain.Entities.Facility", b => modelBuilder.Entity("OmsorgCore.Domain.Entities.Facility", b =>
{ {
b.Property<Guid>("Id") b.Property<Guid>("Id")
@@ -458,6 +494,14 @@ namespace OmsorgCore.Infrastructure.Persistence.Migrations
b.Property<decimal?>("SundaySurchargePercent") b.Property<decimal?>("SundaySurchargePercent")
.HasColumnType("decimal(5,2)"); .HasColumnType("decimal(5,2)");
b.Property<string>("TravelCostMode")
.IsRequired()
.HasMaxLength(20)
.HasColumnType("character varying(20)");
b.Property<decimal?>("TravelCostPerKm")
.HasColumnType("decimal(10,2)");
b.Property<decimal?>("TravelCostRate") b.Property<decimal?>("TravelCostRate")
.HasColumnType("decimal(10,2)"); .HasColumnType("decimal(10,2)");
@@ -1121,6 +1165,25 @@ namespace OmsorgCore.Infrastructure.Persistence.Migrations
b.Navigation("UploadedByUser"); b.Navigation("UploadedByUser");
}); });
modelBuilder.Entity("OmsorgCore.Domain.Entities.EmployeeFacilityDistance", b =>
{
b.HasOne("OmsorgCore.Domain.Entities.Employee", "Employee")
.WithMany()
.HasForeignKey("EmployeeId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.HasOne("OmsorgCore.Domain.Entities.Facility", "Facility")
.WithMany()
.HasForeignKey("FacilityId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Employee");
b.Navigation("Facility");
});
modelBuilder.Entity("OmsorgCore.Domain.Entities.FacilityContact", b => modelBuilder.Entity("OmsorgCore.Domain.Entities.FacilityContact", b =>
{ {
b.HasOne("OmsorgCore.Domain.Entities.Facility", "Facility") b.HasOne("OmsorgCore.Domain.Entities.Facility", "Facility")
@@ -13,6 +13,7 @@ public class OmsorgCoreDbContext : DbContext
public DbSet<Facility> Facilities => Set<Facility>(); public DbSet<Facility> Facilities => Set<Facility>();
public DbSet<FacilityContact> FacilityContacts => Set<FacilityContact>(); public DbSet<FacilityContact> FacilityContacts => Set<FacilityContact>();
public DbSet<FacilityQualificationRate> FacilityQualificationRates => Set<FacilityQualificationRate>(); public DbSet<FacilityQualificationRate> FacilityQualificationRates => Set<FacilityQualificationRate>();
public DbSet<EmployeeFacilityDistance> EmployeeFacilityDistances => Set<EmployeeFacilityDistance>();
public DbSet<Contract> Contracts => Set<Contract>(); public DbSet<Contract> Contracts => Set<Contract>();
public DbSet<Order> Orders => Set<Order>(); public DbSet<Order> Orders => Set<Order>();
public DbSet<Absence> Absences => Set<Absence>(); public DbSet<Absence> Absences => Set<Absence>();
@@ -0,0 +1,82 @@
using Microsoft.EntityFrameworkCore;
using OmsorgCore.Application.Abstractions;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Infrastructure.Persistence;
namespace OmsorgCore.Infrastructure.Repositories;
public class EmployeeFacilityDistanceRepository : IEmployeeFacilityDistanceRepository
{
private readonly OmsorgCoreDbContext _db;
public EmployeeFacilityDistanceRepository(OmsorgCoreDbContext db)
{
_db = db;
}
public Task<EmployeeFacilityDistance?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
=> _db.EmployeeFacilityDistances.FirstOrDefaultAsync(d => d.Id == id, cancellationToken);
public async Task<IReadOnlyList<EmployeeFacilityDistance>> GetByFacilityIdAsync(Guid facilityId, CancellationToken cancellationToken = default)
=> await _db.EmployeeFacilityDistances
.AsNoTracking()
.Include(d => d.Employee)
.Where(d => d.FacilityId == facilityId)
.OrderBy(d => d.Employee.LastName)
.ThenBy(d => d.Employee.FirstName)
.ToListAsync(cancellationToken);
public Task<EmployeeFacilityDistance?> GetByFacilityAndEmployeeAsync(Guid facilityId, Guid employeeId, CancellationToken cancellationToken = default)
=> _db.EmployeeFacilityDistances.FirstOrDefaultAsync(d => d.FacilityId == facilityId && d.EmployeeId == employeeId, cancellationToken);
public async Task AddAsync(EmployeeFacilityDistance distance, CancellationToken cancellationToken = default)
=> await _db.EmployeeFacilityDistances.AddAsync(distance, cancellationToken);
public Task UpdateAsync(EmployeeFacilityDistance distance, CancellationToken cancellationToken = default)
{
_db.EmployeeFacilityDistances.Update(distance);
return Task.CompletedTask;
}
public async Task<bool> SoftDeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
var distance = await _db.EmployeeFacilityDistances.FirstOrDefaultAsync(d => d.Id == id, cancellationToken);
if (distance is null)
{
return false;
}
distance.IsDeleted = true;
distance.DeletedAt = DateTime.UtcNow;
return true;
}
public async Task<IReadOnlyList<EmployeeFacilityDistance>> GetDeletedAsync(string? search, CancellationToken cancellationToken = default)
{
var query = _db.EmployeeFacilityDistances.IgnoreQueryFilters().AsNoTracking().Include(d => d.Employee).Where(d => d.IsDeleted);
if (!string.IsNullOrWhiteSpace(search))
{
var pattern = $"%{search.Trim()}%";
query = query.Where(d => EF.Functions.ILike(d.Employee.FirstName, pattern) || EF.Functions.ILike(d.Employee.LastName, pattern));
}
return await query.OrderByDescending(d => d.DeletedAt).ToListAsync(cancellationToken);
}
public async Task<bool> RestoreAsync(Guid id, CancellationToken cancellationToken = default)
{
var distance = await _db.EmployeeFacilityDistances.IgnoreQueryFilters().FirstOrDefaultAsync(d => d.Id == id && d.IsDeleted, cancellationToken);
if (distance is null)
{
return false;
}
distance.IsDeleted = false;
distance.DeletedAt = null;
return true;
}
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
=> _db.SaveChangesAsync(cancellationToken);
}
@@ -34,6 +34,12 @@ public class RoleRepository : IRoleRepository
public async Task AddAsync(Role role, CancellationToken cancellationToken = default) public async Task AddAsync(Role role, CancellationToken cancellationToken = default)
=> await _db.Roles.AddAsync(role, cancellationToken); => await _db.Roles.AddAsync(role, cancellationToken);
public Task RemoveAsync(Role role, CancellationToken cancellationToken = default)
{
_db.Roles.Remove(role);
return Task.CompletedTask;
}
public Task SaveChangesAsync(CancellationToken cancellationToken = default) public Task SaveChangesAsync(CancellationToken cancellationToken = default)
=> _db.SaveChangesAsync(cancellationToken); => _db.SaveChangesAsync(cancellationToken);
} }
@@ -0,0 +1,120 @@
using OmsorgCore.Application.Services;
using OmsorgCore.Domain.Entities;
using OmsorgCore.Tests.TestDoubles;
namespace OmsorgCore.Tests.Services;
public class EmployeeFacilityDistanceServiceTests
{
[Fact]
public async Task CreateAsync_PersistsAllFields()
{
var repository = new FakeEmployeeFacilityDistanceRepository();
var sut = new EmployeeFacilityDistanceService(repository);
var facilityId = Guid.NewGuid();
var employeeId = Guid.NewGuid();
var distance = new EmployeeFacilityDistance
{
FacilityId = facilityId,
EmployeeId = employeeId,
DistanceKm = 12.5m
};
var created = await sut.CreateAsync(distance);
Assert.Equal(facilityId, created.FacilityId);
Assert.Equal(employeeId, created.EmployeeId);
Assert.Equal(12.5m, created.DistanceKm);
}
[Fact]
public async Task GetByFacilityIdAsync_ReturnsOnlyDistancesOfThatFacility()
{
var repository = new FakeEmployeeFacilityDistanceRepository();
var sut = new EmployeeFacilityDistanceService(repository);
var facilityIdA = Guid.NewGuid();
var facilityIdB = Guid.NewGuid();
await sut.CreateAsync(new EmployeeFacilityDistance { FacilityId = facilityIdA, EmployeeId = Guid.NewGuid(), DistanceKm = 5 });
await sut.CreateAsync(new EmployeeFacilityDistance { FacilityId = facilityIdA, EmployeeId = Guid.NewGuid(), DistanceKm = 8 });
await sut.CreateAsync(new EmployeeFacilityDistance { FacilityId = facilityIdB, EmployeeId = Guid.NewGuid(), DistanceKm = 3 });
var distancesForA = await sut.GetByFacilityIdAsync(facilityIdA);
Assert.Equal(2, distancesForA.Count);
Assert.All(distancesForA, d => Assert.Equal(facilityIdA, d.FacilityId));
}
[Fact]
public async Task GetByFacilityAndEmployeeAsync_FindsExistingPair()
{
var repository = new FakeEmployeeFacilityDistanceRepository();
var sut = new EmployeeFacilityDistanceService(repository);
var facilityId = Guid.NewGuid();
var employeeId = Guid.NewGuid();
await sut.CreateAsync(new EmployeeFacilityDistance { FacilityId = facilityId, EmployeeId = employeeId, DistanceKm = 7 });
var found = await sut.GetByFacilityAndEmployeeAsync(facilityId, employeeId);
var notFound = await sut.GetByFacilityAndEmployeeAsync(facilityId, Guid.NewGuid());
Assert.NotNull(found);
Assert.Equal(7, found!.DistanceKm);
Assert.Null(notFound);
}
[Fact]
public async Task UpdateAsync_UpdatesDistanceKm_AndReturnsUpdatedEntity()
{
var repository = new FakeEmployeeFacilityDistanceRepository();
var sut = new EmployeeFacilityDistanceService(repository);
var created = await sut.CreateAsync(new EmployeeFacilityDistance { FacilityId = Guid.NewGuid(), EmployeeId = Guid.NewGuid(), DistanceKm = 5 });
var updated = await sut.UpdateAsync(created.Id, new EmployeeFacilityDistance { DistanceKm = 9.5m });
Assert.NotNull(updated);
Assert.Equal(9.5m, updated!.DistanceKm);
Assert.NotNull(updated.UpdatedAt);
}
[Fact]
public async Task UpdateAsync_UnknownId_ReturnsNull()
{
var repository = new FakeEmployeeFacilityDistanceRepository();
var sut = new EmployeeFacilityDistanceService(repository);
var updated = await sut.UpdateAsync(Guid.NewGuid(), new EmployeeFacilityDistance { DistanceKm = 1 });
Assert.Null(updated);
}
[Fact]
public async Task DeleteAsync_ThenGetDeletedAsync_ReturnsSoftDeletedDistance()
{
var repository = new FakeEmployeeFacilityDistanceRepository();
var sut = new EmployeeFacilityDistanceService(repository);
var created = await sut.CreateAsync(new EmployeeFacilityDistance { FacilityId = Guid.NewGuid(), EmployeeId = Guid.NewGuid(), DistanceKm = 5 });
var deleted = await sut.DeleteAsync(created.Id);
var deletedList = await sut.GetDeletedAsync(null);
Assert.True(deleted);
Assert.Contains(deletedList, d => d.Id == created.Id);
}
[Fact]
public async Task RestoreAsync_UndoesSoftDelete()
{
var repository = new FakeEmployeeFacilityDistanceRepository();
var sut = new EmployeeFacilityDistanceService(repository);
var created = await sut.CreateAsync(new EmployeeFacilityDistance { FacilityId = Guid.NewGuid(), EmployeeId = Guid.NewGuid(), DistanceKm = 5 });
await sut.DeleteAsync(created.Id);
var restored = await sut.RestoreAsync(created.Id);
var deletedList = await sut.GetDeletedAsync(null);
Assert.True(restored);
Assert.DoesNotContain(deletedList, d => d.Id == created.Id);
}
}
@@ -84,6 +84,50 @@ public class FacilityServiceTests
Assert.NotNull(updated.UpdatedAt); Assert.NotNull(updated.UpdatedAt);
} }
[Fact]
public async Task UpdateAsync_PersistsKonditionenFields()
{
var repository = new FakeFacilityRepository();
var sut = new FacilityService(repository);
var created = await sut.CreateAsync(new Facility { Name = "Pflegeheim Musterstadt" });
var updates = new Facility
{
Name = "Pflegeheim Musterstadt",
CrmStatus = "Kunde",
BillingRate = 32.5m,
NightSurchargePercent = 25,
SaturdaySurchargePercent = 15,
SundaySurchargePercent = 20,
HolidaySurchargePercent = 50,
TravelCostMode = "ProKilometer",
TravelCostRate = 10,
TravelCostPerKm = 0.42m,
MinimumHours = 4,
BreakPolicy = "30 Min. ab 6 Std.",
BillingInterval = "Monatlich",
PaymentTermDays = 14,
IndividualAgreements = "Sonderkonditionen Wochenende"
};
var updated = await sut.UpdateAsync(created.Id, updates);
Assert.NotNull(updated);
Assert.Equal(32.5m, updated!.BillingRate);
Assert.Equal(25, updated.NightSurchargePercent);
Assert.Equal(15, updated.SaturdaySurchargePercent);
Assert.Equal(20, updated.SundaySurchargePercent);
Assert.Equal(50, updated.HolidaySurchargePercent);
Assert.Equal("ProKilometer", updated.TravelCostMode);
Assert.Equal(10, updated.TravelCostRate);
Assert.Equal(0.42m, updated.TravelCostPerKm);
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);
}
[Fact] [Fact]
public async Task UpdateAsync_UnknownId_ReturnsNull() public async Task UpdateAsync_UnknownId_ReturnsNull()
{ {
@@ -15,7 +15,7 @@ public class RoleServiceTests
public async Task CreateAsync_WithNewName_CreatesRole() public async Task CreateAsync_WithNewName_CreatesRole()
{ {
var roles = new FakeRoleRepository(); var roles = new FakeRoleRepository();
var sut = new RoleService(roles, CreatePermissionService()); var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository());
var result = await sut.CreateAsync("Aussendienst"); var result = await sut.CreateAsync("Aussendienst");
@@ -29,7 +29,7 @@ public class RoleServiceTests
public async Task CreateAsync_WithExistingName_Fails() public async Task CreateAsync_WithExistingName_Fails()
{ {
var roles = new FakeRoleRepository(new Role { Name = "Aussendienst" }); var roles = new FakeRoleRepository(new Role { Name = "Aussendienst" });
var sut = new RoleService(roles, CreatePermissionService()); var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository());
var result = await sut.CreateAsync("Aussendienst"); var result = await sut.CreateAsync("Aussendienst");
@@ -40,7 +40,7 @@ public class RoleServiceTests
[Fact] [Fact]
public async Task CreateAsync_WithEmptyName_Fails() public async Task CreateAsync_WithEmptyName_Fails()
{ {
var sut = new RoleService(new FakeRoleRepository(), CreatePermissionService()); var sut = new RoleService(new FakeRoleRepository(), CreatePermissionService(), new FakeUserRepository());
var result = await sut.CreateAsync(" "); var result = await sut.CreateAsync(" ");
@@ -54,7 +54,7 @@ public class RoleServiceTests
var role = new Role { Name = "Disposition" }; var role = new Role { Name = "Disposition" };
role.RolePermissions.Add(new RolePermission { RoleId = role.Id, Role = role, Module = ModuleType.Employees, Action = PermissionAction.View }); role.RolePermissions.Add(new RolePermission { RoleId = role.Id, Role = role, Module = ModuleType.Employees, Action = PermissionAction.View });
var roles = new FakeRoleRepository(role); var roles = new FakeRoleRepository(role);
var sut = new RoleService(roles, CreatePermissionService()); var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository());
var result = await sut.UpdatePermissionsAsync(role.Id, new[] { (ModuleType.Invoices, PermissionAction.Edit, PermissionScope.All) }); var result = await sut.UpdatePermissionsAsync(role.Id, new[] { (ModuleType.Invoices, PermissionAction.Edit, PermissionScope.All) });
@@ -71,7 +71,7 @@ public class RoleServiceTests
{ {
var role = new Role { Name = "Außendienst" }; var role = new Role { Name = "Außendienst" };
var roles = new FakeRoleRepository(role); var roles = new FakeRoleRepository(role);
var sut = new RoleService(roles, CreatePermissionService()); var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository());
var result = await sut.UpdatePermissionsAsync(role.Id, new[] { (ModuleType.Employees, PermissionAction.View, PermissionScope.Own) }); var result = await sut.UpdatePermissionsAsync(role.Id, new[] { (ModuleType.Employees, PermissionAction.View, PermissionScope.Own) });
@@ -84,11 +84,51 @@ public class RoleServiceTests
[Fact] [Fact]
public async Task UpdatePermissionsAsync_UnknownRole_Fails() public async Task UpdatePermissionsAsync_UnknownRole_Fails()
{ {
var sut = new RoleService(new FakeRoleRepository(), CreatePermissionService()); var sut = new RoleService(new FakeRoleRepository(), CreatePermissionService(), new FakeUserRepository());
var result = await sut.UpdatePermissionsAsync(Guid.NewGuid(), new[] { (ModuleType.Employees, PermissionAction.View, PermissionScope.All) }); var result = await sut.UpdatePermissionsAsync(Guid.NewGuid(), new[] { (ModuleType.Employees, PermissionAction.View, PermissionScope.All) });
Assert.False(result.Success); Assert.False(result.Success);
Assert.Equal(UpdateRolePermissionsFailureReason.RoleNotFound, result.FailureReason); Assert.Equal(UpdateRolePermissionsFailureReason.RoleNotFound, result.FailureReason);
} }
[Fact]
public async Task DeleteAsync_UnassignedRole_DeletesIt()
{
var role = new Role { Name = "Recruiting" };
var roles = new FakeRoleRepository(role);
var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository());
var result = await sut.DeleteAsync(role.Id);
Assert.True(result.Success);
Assert.Empty(await roles.GetAllAsync());
}
[Fact]
public async Task DeleteAsync_RoleStillAssignedToUser_Fails()
{
var role = new Role { Name = "Recruiting" };
var roles = new FakeRoleRepository(role);
var user = new User { Username = "sascha", RoleId = role.Id };
var sut = new RoleService(roles, CreatePermissionService(), new FakeUserRepository(user));
var result = await sut.DeleteAsync(role.Id);
Assert.False(result.Success);
Assert.Equal(DeleteRoleFailureReason.InUse, result.FailureReason);
Assert.Equal(1, result.AssignedUserCount);
Assert.Single(await roles.GetAllAsync());
}
[Fact]
public async Task DeleteAsync_UnknownRole_Fails()
{
var sut = new RoleService(new FakeRoleRepository(), CreatePermissionService(), new FakeUserRepository());
var result = await sut.DeleteAsync(Guid.NewGuid());
Assert.False(result.Success);
Assert.Equal(DeleteRoleFailureReason.RoleNotFound, result.FailureReason);
}
} }
@@ -386,6 +386,63 @@ public class FakeFacilityQualificationRateRepository : IFacilityQualificationRat
=> Task.CompletedTask; => Task.CompletedTask;
} }
public class FakeEmployeeFacilityDistanceRepository : IEmployeeFacilityDistanceRepository
{
private readonly List<EmployeeFacilityDistance> _distances = new();
public Task<EmployeeFacilityDistance?> GetByIdAsync(Guid id, CancellationToken cancellationToken = default)
=> Task.FromResult(_distances.FirstOrDefault(d => d.Id == id));
public Task<IReadOnlyList<EmployeeFacilityDistance>> GetByFacilityIdAsync(Guid facilityId, CancellationToken cancellationToken = default)
=> Task.FromResult<IReadOnlyList<EmployeeFacilityDistance>>(
_distances.Where(d => d.FacilityId == facilityId).ToList());
public Task<EmployeeFacilityDistance?> GetByFacilityAndEmployeeAsync(Guid facilityId, Guid employeeId, CancellationToken cancellationToken = default)
=> Task.FromResult(_distances.FirstOrDefault(d => d.FacilityId == facilityId && d.EmployeeId == employeeId));
public Task AddAsync(EmployeeFacilityDistance distance, CancellationToken cancellationToken = default)
{
_distances.Add(distance);
return Task.CompletedTask;
}
public Task UpdateAsync(EmployeeFacilityDistance distance, CancellationToken cancellationToken = default)
=> Task.CompletedTask;
public Task<bool> SoftDeleteAsync(Guid id, CancellationToken cancellationToken = default)
{
var distance = _distances.FirstOrDefault(d => d.Id == id);
if (distance is null)
{
return Task.FromResult(false);
}
distance.IsDeleted = true;
distance.DeletedAt = DateTime.UtcNow;
return Task.FromResult(true);
}
public Task<IReadOnlyList<EmployeeFacilityDistance>> GetDeletedAsync(string? search, CancellationToken cancellationToken = default)
=> Task.FromResult<IReadOnlyList<EmployeeFacilityDistance>>(
_distances.Where(d => d.IsDeleted).OrderByDescending(d => d.DeletedAt).ToList());
public Task<bool> RestoreAsync(Guid id, CancellationToken cancellationToken = default)
{
var distance = _distances.FirstOrDefault(d => d.Id == id && d.IsDeleted);
if (distance is null)
{
return Task.FromResult(false);
}
distance.IsDeleted = false;
distance.DeletedAt = null;
return Task.FromResult(true);
}
public Task SaveChangesAsync(CancellationToken cancellationToken = default)
=> Task.CompletedTask;
}
public class FakeContractRepository : IContractRepository public class FakeContractRepository : IContractRepository
{ {
private readonly List<Contract> _contracts = new(); private readonly List<Contract> _contracts = new();
@@ -505,6 +562,12 @@ public class FakeRoleRepository : IRoleRepository
return Task.CompletedTask; return Task.CompletedTask;
} }
public Task RemoveAsync(Role role, CancellationToken cancellationToken = default)
{
_roles.RemoveAll(r => r.Id == role.Id);
return Task.CompletedTask;
}
public Task AddPermissionRangeAsync(IEnumerable<RolePermission> permissions, CancellationToken cancellationToken = default) public Task AddPermissionRangeAsync(IEnumerable<RolePermission> permissions, CancellationToken cancellationToken = default)
{ {
foreach (var permission in permissions) foreach (var permission in permissions)
+3 -1
View File
@@ -9,7 +9,7 @@ This is the OMSORG website plus **OMSORG Connect**, an internal employee web app
**OMSORG Connect wird gerade komplett neu aufgebaut.** `mitarbeiter-app-legacy/` ist die alte, produktiv gelaufene Version — dient nur noch als Referenz/Vorlage, wird nicht mehr weiterentwickelt. Der Rest dieser Datei beschreibt `mitarbeiter-app-legacy/`, nicht den Neuaufbau. Die aktive Entwicklung findet in `mitarbeiter-app/` statt (aktuell: Login-Flow + eigenes Passwort ändern/zurücksetzen gegen `omsorgCore`, siehe unten "Neuaufbau"). Auth in der Legacy-Version läuft **nicht mehr** über lokales bcrypt/Session-Lockout wie unten in "Entry point" beschrieben — das ist bereits auf omsorgCore-JWT-Auth umgestellt (`lib/omsorgCoreClient.php`, `lib/auth.php`), lokales MySQL dient dort nur noch als Read-Cache für Profildaten. **OMSORG Connect wird gerade komplett neu aufgebaut.** `mitarbeiter-app-legacy/` ist die alte, produktiv gelaufene Version — dient nur noch als Referenz/Vorlage, wird nicht mehr weiterentwickelt. Der Rest dieser Datei beschreibt `mitarbeiter-app-legacy/`, nicht den Neuaufbau. Die aktive Entwicklung findet in `mitarbeiter-app/` statt (aktuell: Login-Flow + eigenes Passwort ändern/zurücksetzen gegen `omsorgCore`, siehe unten "Neuaufbau"). Auth in der Legacy-Version läuft **nicht mehr** über lokales bcrypt/Session-Lockout wie unten in "Entry point" beschrieben — das ist bereits auf omsorgCore-JWT-Auth umgestellt (`lib/omsorgCoreClient.php`, `lib/auth.php`), lokales MySQL dient dort nur noch als Read-Cache für Profildaten.
### Neuaufbau (`mitarbeiter-app/`) ### Neuaufbau (`mitarbeiter-app/`)
Frischer, minimaler PHP-Flow gegen `omsorgCore` — kein Framework, gleiches Deployment-Modell wie die Legacy-App. Login/Passwort, das Abwesenheits-/Urlaubs-/Krankmeldungsformular und die strukturierte Zeiterfassung pro Schicht (siehe unten) sind umgesetzt; weiterhin bewusst (noch) ohne: Dienstplan, Downloads, Admin-Oberfläche, PWA-Assets, MySQL. Admin-/Mitarbeiterverwaltung gehört nicht hierher, sondern exklusiv zu OMSORG Desktop (`omsorgapp`) — Connect zeigt/bearbeitet ausschließlich Daten des eingeloggten Nutzers selbst. Frischer, minimaler PHP-Flow gegen `omsorgCore` — kein Framework, gleiches Deployment-Modell wie die Legacy-App. Login/Passwort, das Abwesenheits-/Urlaubs-/Krankmeldungsformular, die strukturierte Zeiterfassung pro Schicht und die eigene Fahrtstrecken-Pflege (siehe unten) sind umgesetzt; weiterhin bewusst (noch) ohne: Dienstplan, Downloads, Admin-Oberfläche, PWA-Assets, MySQL. Admin-/Mitarbeiterverwaltung gehört nicht hierher, sondern exklusiv zu OMSORG Desktop (`omsorgapp`) — Connect zeigt/bearbeitet ausschließlich Daten des eingeloggten Nutzers selbst.
**Abwesenheits-/Urlaubs-/Krankmeldungsanträge (FR-CON-1, `pages/urlaubsantrag.php`):** einzige Seite in diesem Neuaufbau mit echtem Formular + eigener Datenliste. Formular (Art/Zeitraum/Grund/Vertretung/Nachricht) postet inline auf sich selbst (kein separates `actions/*.php` wie in der Legacy-App) über `omsorgcore_absences_create()` (`lib/omsorgCoreClient.php`) gegen `POST /api/absences` in `omsorgCore` — schickt bewusst **keine** `employeeId` mit, das Backend löst den eingeloggten Mitarbeiter serverseitig über den JWT-Claim auf (`AbsenceService.CreateAsync`, siehe `omsorgCore/CLAUDE.md`). Darunter die eigene Antragshistorie über `omsorgcore_absences_list()` (Own-Scope filtert automatisch serverseitig, kein `employeeId`-Parameter nötig). Die Art-Dropdown-Optionen kommen über den neuen generischen `omsorgcore_value_list_items($config, $token, $key)`-Wrapper (erste Nicht-Auth-Verwendung des generierten PHP-Clients hier) aus `GET /api/value-lists/AbsenceType/items`, nicht hartcodiert. Genehmigen/Ablehnen passiert ausschließlich in `omsorgapp` (`AbsencesPage`) — Connect selbst hat keine Entscheidungs-UI, nur Anlegen/Bearbeiten + eigenen Status einsehen. Zweispaltiges Layout (`.split-layout` in `app.css`, Liste links/Formular rechts, bricht unter 860px auf eine Spalte um) statt gestapelter Karten. **Abwesenheits-/Urlaubs-/Krankmeldungsanträge (FR-CON-1, `pages/urlaubsantrag.php`):** einzige Seite in diesem Neuaufbau mit echtem Formular + eigener Datenliste. Formular (Art/Zeitraum/Grund/Vertretung/Nachricht) postet inline auf sich selbst (kein separates `actions/*.php` wie in der Legacy-App) über `omsorgcore_absences_create()` (`lib/omsorgCoreClient.php`) gegen `POST /api/absences` in `omsorgCore` — schickt bewusst **keine** `employeeId` mit, das Backend löst den eingeloggten Mitarbeiter serverseitig über den JWT-Claim auf (`AbsenceService.CreateAsync`, siehe `omsorgCore/CLAUDE.md`). Darunter die eigene Antragshistorie über `omsorgcore_absences_list()` (Own-Scope filtert automatisch serverseitig, kein `employeeId`-Parameter nötig). Die Art-Dropdown-Optionen kommen über den neuen generischen `omsorgcore_value_list_items($config, $token, $key)`-Wrapper (erste Nicht-Auth-Verwendung des generierten PHP-Clients hier) aus `GET /api/value-lists/AbsenceType/items`, nicht hartcodiert. Genehmigen/Ablehnen passiert ausschließlich in `omsorgapp` (`AbsencesPage`) — Connect selbst hat keine Entscheidungs-UI, nur Anlegen/Bearbeiten + eigenen Status einsehen. Zweispaltiges Layout (`.split-layout` in `app.css`, Liste links/Formular rechts, bricht unter 860px auf eine Spalte um) statt gestapelter Karten.
@@ -17,6 +17,8 @@ Frischer, minimaler PHP-Flow gegen `omsorgCore` — kein Framework, gleiches Dep
**Zeiterfassung (FR-ZE-1/FR-ZE-2, `pages/stundenerfassung.php`):** 1:1 nach dem Muster von `urlaubsantrag.php` (Liste links/Formular rechts, inline-POST auf sich selbst, `mode=create|edit` als Hidden-Field), aber mit drei statt zwei möglichen Aktionen, weil `TimeEntryStatus` eine echte Mehrstufen-Pipeline statt einer binären Entscheidung ist (siehe `omsorgCore/CLAUDE.md` "Zeiterfassung"): Anlegen (`omsorgcore_time_entries_create()` gegen `POST /api/time-entries`, ohne `employeeId`), Bearbeiten solange `isEditableByOwner` (`omsorgcore_time_entries_update()` gegen `PUT /api/time-entries/{id}`, ohne `statusId`) und zusätzlich ein dritter `mode=submit`-Zweig im selben POST-Handler (`omsorgcore_time_entries_submit()` gegen `POST /api/time-entries/{id}/submit`, kein Payload) für den "Einreichen"-Button. Der "Einreichen"-Button erscheint nur bei Einträgen, deren `statusId` in der Menge der Selbst-Einreichungs-Kanten liegt (`omsorgcore_value_list_transitions($config, $token, 'TimeEntryStatus')`, gefiltert auf `requiresApproval === false`) — bewusst **nicht** dasselbe Kriterium wie für den "Bearbeiten"-Link (`isEditableByOwner` allein reicht hier nicht, weil auch der bereits eingereichte, aber noch nicht geprüfte Status `isEditableByOwner=true` trägt, aber keine ausgehende Selbst-Einreichungs-Kante mehr hat). Auftrags-Dropdown über `omsorgcore_orders_list()` (`GET /api/orders`) — zeigt mangels Mitarbeiter-Zuweisung auf `Order` (FR-EM-3 offen) bewusst alle aktiven Aufträge, nicht nur zugewiesene. Genehmigen/Prüfen/Freigeben passiert ausschließlich in `omsorgapp` (`TimeEntriesPage`) — Connect selbst hat keine Entscheidungs-UI. **Zeiterfassung (FR-ZE-1/FR-ZE-2, `pages/stundenerfassung.php`):** 1:1 nach dem Muster von `urlaubsantrag.php` (Liste links/Formular rechts, inline-POST auf sich selbst, `mode=create|edit` als Hidden-Field), aber mit drei statt zwei möglichen Aktionen, weil `TimeEntryStatus` eine echte Mehrstufen-Pipeline statt einer binären Entscheidung ist (siehe `omsorgCore/CLAUDE.md` "Zeiterfassung"): Anlegen (`omsorgcore_time_entries_create()` gegen `POST /api/time-entries`, ohne `employeeId`), Bearbeiten solange `isEditableByOwner` (`omsorgcore_time_entries_update()` gegen `PUT /api/time-entries/{id}`, ohne `statusId`) und zusätzlich ein dritter `mode=submit`-Zweig im selben POST-Handler (`omsorgcore_time_entries_submit()` gegen `POST /api/time-entries/{id}/submit`, kein Payload) für den "Einreichen"-Button. Der "Einreichen"-Button erscheint nur bei Einträgen, deren `statusId` in der Menge der Selbst-Einreichungs-Kanten liegt (`omsorgcore_value_list_transitions($config, $token, 'TimeEntryStatus')`, gefiltert auf `requiresApproval === false`) — bewusst **nicht** dasselbe Kriterium wie für den "Bearbeiten"-Link (`isEditableByOwner` allein reicht hier nicht, weil auch der bereits eingereichte, aber noch nicht geprüfte Status `isEditableByOwner=true` trägt, aber keine ausgehende Selbst-Einreichungs-Kante mehr hat). Auftrags-Dropdown über `omsorgcore_orders_list()` (`GET /api/orders`) — zeigt mangels Mitarbeiter-Zuweisung auf `Order` (FR-EM-3 offen) bewusst alle aktiven Aufträge, nicht nur zugewiesene. Genehmigen/Prüfen/Freigeben passiert ausschließlich in `omsorgapp` (`TimeEntriesPage`) — Connect selbst hat keine Entscheidungs-UI.
**Fahrtstrecken (FR-EIN-4, `pages/fahrtstrecken.php`):** Selbstbedienungsseite, mit der ein Mitarbeiter seine eigene Entfernung (km, einfache Strecke) je Einrichtung pflegt — Grundlage für die kilometerbasierte Fahrtkostenabrechnung, wenn eine Einrichtung in `omsorgapp` auf `TravelCostMode = "ProKilometer"` gestellt ist (siehe `omsorgCore/CLAUDE.md`, "Konditionen einer Einrichtung"). 1:1 nach dem `urlaubsantrag.php`-Muster (Liste links/Formular rechts, `mode=create|edit`, Post-Redirect-Get bei Update), aber gegen einen eigenen, engeren Endpunkt statt der vollen Facilities-API: `omsorgcore_my_facility_distances_list/create/update()` (`lib/omsorgCoreClient.php`) gegen `/api/me/facility-distances` (`MyFacilityDistancesController`) — schickt bewusst **keine** `employeeId` mit, das Backend löst den eingeloggten Mitarbeiter serverseitig auf, exakt wie bei Abwesenheiten/Zeiterfassung. Die Einrichtungsauswahl im Anlegen-Formular kommt über `omsorgcore_my_facility_distances_facility_options()` gegen `/api/me/facility-distances/facilities` — liefert bewusst nur `id`/`name`, keine Konditionen/CRM-Daten, weil der Außendienst kein `Facilities`-Recht hat (eigener `ModuleType.EmployeeFacilityDistances`, siehe `omsorgCore/CLAUDE.md`, "Rechtesystem"). Das Dropdown zeigt nur Einrichtungen, für die noch keine eigene Distanz existiert (Duplikate serverseitig ohnehin abgelehnt); Bearbeiten ändert nur die km-Zahl, die Einrichtung selbst ist danach fix. Kein `DELETE` hier — Löschen bleibt Büro-Aufgabe in `omsorgapp`.
**Rechte im Client (`has_permission()`, `lib/auth.php`):** serverseitig ist jeder `omsorgCore`-Endpunkt ohnehin über `[RequirePermission]` gegated (siehe `omsorgCore/CLAUDE.md`, "Rechtesystem") — `has_permission(string $module, string $action): bool` ist nur die UI-Seite davon, analog zu `hasPermission()` in `omsorgapp/src/app/AuthContext.jsx`, liest `$_SESSION['omsorgcore_profile']['permissions']` (aus `GET /api/auth/me`, `PermissionDto[] { module, action, scope }`). `lib/layout.php` blendet den "Urlaub & Abwesenheit"-Tab aus, wenn `!has_permission('Absences','View')`; `pages/urlaubsantrag.php` leitet ohne dieses Recht direkt auf `dashboard.php` um (kein "leere Seite ohne Erklärung"-Fall) und blendet zusätzlich separat das Formular aus, wenn `!has_permission('Absences','Create')` (z. B. für eine Rolle mit `View`, aber ohne `Create`) — beide Prüfungen sind rein kosmetisch, ein direkt gepostetes Formular ohne UI wird serverseitig trotzdem mit `403` abgelehnt, wird hier aber zusätzlich mit einer klaren deutschen Fehlermeldung statt eines stillen Fehlschlags abgefangen. Neue Connect-Seiten mit einem Rechte-Bezug sollten `has_permission()` nach demselben Muster nutzen, statt ungegated jedem eingeloggten Nutzer alles zu zeigen. **Rechte im Client (`has_permission()`, `lib/auth.php`):** serverseitig ist jeder `omsorgCore`-Endpunkt ohnehin über `[RequirePermission]` gegated (siehe `omsorgCore/CLAUDE.md`, "Rechtesystem") — `has_permission(string $module, string $action): bool` ist nur die UI-Seite davon, analog zu `hasPermission()` in `omsorgapp/src/app/AuthContext.jsx`, liest `$_SESSION['omsorgcore_profile']['permissions']` (aus `GET /api/auth/me`, `PermissionDto[] { module, action, scope }`). `lib/layout.php` blendet den "Urlaub & Abwesenheit"-Tab aus, wenn `!has_permission('Absences','View')`; `pages/urlaubsantrag.php` leitet ohne dieses Recht direkt auf `dashboard.php` um (kein "leere Seite ohne Erklärung"-Fall) und blendet zusätzlich separat das Formular aus, wenn `!has_permission('Absences','Create')` (z. B. für eine Rolle mit `View`, aber ohne `Create`) — beide Prüfungen sind rein kosmetisch, ein direkt gepostetes Formular ohne UI wird serverseitig trotzdem mit `403` abgelehnt, wird hier aber zusätzlich mit einer klaren deutschen Fehlermeldung statt eines stillen Fehlschlags abgefangen. Neue Connect-Seiten mit einem Rechte-Bezug sollten `has_permission()` nach demselben Muster nutzen, statt ungegated jedem eingeloggten Nutzer alles zu zeigen.
**Passwort ändern/vergessen:** `pages/settings.php` (nach Login, `require_login()`) und `pages/forgot-password.php` (vor Login, 3-stufig: Code anfordern → verifizieren → neues Passwort setzen), beide über die dafür in `lib/omsorgCoreClient.php` ergänzten `omsorgcore_change_password`/`omsorgcore_forgot_password_*`-Wrapper gegen dieselben `omsorgCore`-Endpunkte wie in `mitarbeiter-app-legacy`. Die Mindestlänge kommt nicht hartcodiert, sondern über `omsorgcore_password_policy()` (`GET /api/auth/password-policy`, siehe `omsorgCore/CLAUDE.md` Abschnitt "Passwort-Mindestlänge") — sowohl für das `minlength`-Attribut der Formularfelder als auch für die serverseitige Vorab-Fehlermeldung; die eigentliche Durchsetzung passiert im Backend. Nach erfolgreichem Anlegen/Admin-Reset eines Accounts (`mustChangePassword`-Flag aus der Login-Response, siehe `lib/auth.php`) leitet `pages/dashboard.php` erzwungen zu `settings.php` weiter. `forgot-password.php` geht bei Schritt "request" bewusst **immer** zu Schritt "verify" weiter, unabhängig davon, ob der Username existiert (kein Enumeration-Rückschluss, siehe `omsorgCore/CLAUDE.md` "Passwort-Reset/E-Mail-Versand") — **außer** der Status ist `"email_unavailable"` (E-Mail-Versand aktuell gestört, z. B. SMTP down): dann bleibt die Seite auf Schritt "request" und zeigt eine klare Fehlermeldung, statt den Nutzer auf eine Code-Eingabe warten zu lassen, die nie ankommt. **Passwort ändern/vergessen:** `pages/settings.php` (nach Login, `require_login()`) und `pages/forgot-password.php` (vor Login, 3-stufig: Code anfordern → verifizieren → neues Passwort setzen), beide über die dafür in `lib/omsorgCoreClient.php` ergänzten `omsorgcore_change_password`/`omsorgcore_forgot_password_*`-Wrapper gegen dieselben `omsorgCore`-Endpunkte wie in `mitarbeiter-app-legacy`. Die Mindestlänge kommt nicht hartcodiert, sondern über `omsorgcore_password_policy()` (`GET /api/auth/password-policy`, siehe `omsorgCore/CLAUDE.md` Abschnitt "Passwort-Mindestlänge") — sowohl für das `minlength`-Attribut der Formularfelder als auch für die serverseitige Vorab-Fehlermeldung; die eigentliche Durchsetzung passiert im Backend. Nach erfolgreichem Anlegen/Admin-Reset eines Accounts (`mustChangePassword`-Flag aus der Login-Response, siehe `lib/auth.php`) leitet `pages/dashboard.php` erzwungen zu `settings.php` weiter. `forgot-password.php` geht bei Schritt "request" bewusst **immer** zu Schritt "verify" weiter, unabhängig davon, ob der Username existiert (kein Enumeration-Rückschluss, siehe `omsorgCore/CLAUDE.md` "Passwort-Reset/E-Mail-Versand") — **außer** der Status ist `"email_unavailable"` (E-Mail-Versand aktuell gestört, z. B. SMTP down): dann bleibt die Seite auf Schritt "request" und zeigt eine klare Fehlermeldung, statt den Nutzer auf eine Code-Eingabe warten zu lassen, die nie ankommt.
+16 -2
View File
@@ -14,12 +14,26 @@ RUN apt-get update \
COPY omsorgWeb/docker/allow-htaccess.conf /etc/apache2/conf-available/allow-htaccess.conf COPY omsorgWeb/docker/allow-htaccess.conf /etc/apache2/conf-available/allow-htaccess.conf
RUN a2enconf allow-htaccess RUN a2enconf allow-htaccess
# Ersetzt die mitgelieferte Default-vhost (identisch, nur mit einer zusätzlichen Rewrite-Regel,
# die den TLS-terminierenden Host-nginx davor erkennt - sonst redirected die root-.htaccess
# endlos, siehe Datei-Kommentar. Muss im <VirtualHost>-Block selbst stehen, ein conf-enabled-Drop-in
# außerhalb davon wird nicht mit der .htaccess desselben Pfads zusammengeführt.)
COPY omsorgWeb/docker/000-default.conf /etc/apache2/sites-enabled/000-default.conf
COPY omsorgWeb/ /var/www/html/ COPY omsorgWeb/ /var/www/html/
COPY .htaccess /var/www/html/.htaccess COPY .htaccess /var/www/html/.htaccess
RUN chmod +x /var/www/html/docker/docker-entrypoint.sh \ RUN chmod +x /var/www/html/docker/docker-entrypoint.sh \
# Schreibbare Verzeichnisse für Datei-Uploads zur Laufzeit (siehe docker-compose.yml-Volumes) - # Schreibbare Verzeichnisse für Datei-Uploads zur Laufzeit (siehe docker-compose.yml-Volumes).
# Besitzer auf den Apache-User setzen, damit PHP dort schreiben kann. # Existieren im Repo NICHT zwingend (Root-.gitignore schließt sie komplett aus - lokal hatten
# sie bei mir Testdateien, ein frischer CI-Checkout hat sie gar nicht) - deshalb erst anlegen,
# dann Besitzer auf den Apache-User setzen, damit PHP dort schreiben kann.
&& mkdir -p \
/var/www/html/mitarbeiter-app-legacy/uploads \
/var/www/html/mitarbeiter-app-legacy/downloads \
/var/www/html/mitarbeiter-app-legacy/fortbildung-materials \
/var/www/html/mitarbeiter-app-legacy/assets/avatars \
/var/www/html/mitarbeiter-app-legacy/data \
&& chown -R www-data:www-data \ && chown -R www-data:www-data \
/var/www/html/mitarbeiter-app-legacy/uploads \ /var/www/html/mitarbeiter-app-legacy/uploads \
/var/www/html/mitarbeiter-app-legacy/downloads \ /var/www/html/mitarbeiter-app-legacy/downloads \
+22
View File
@@ -0,0 +1,22 @@
<VirtualHost *:80>
ServerName omsorgweb
ServerAdmin webmaster@localhost
DocumentRoot /var/www/html
# Der Host-nginx terminiert TLS und proxied per HTTP an diesen Container (siehe
# docker-compose.yml) - ohne diese Regel weiß die root-.htaccess
# (`RewriteCond %{HTTPS} !=on` -> Redirect auf https://) nie, dass die ursprüngliche Anfrage
# HTTPS war, und redirected endlos (führt zu "Firefox kann nicht verbinden - Seite leitet
# falsch weiter" bzw. ERR_TOO_MANY_REDIRECTS). Per mod_rewrite-Trace verifiziert: das MUSS
# innerhalb dieses <VirtualHost>-Blocks stehen - weder eine Regel in conf-enabled/*.conf
# außerhalb jedes VirtualHost, noch eine in einem <Directory>-Block wird mit dem
# per-Directory-Regelsatz der .htaccess desselben Pfads zusammengeführt (beides per Trace
# ausprobiert, keins hat gewirkt - Apache vererbt Rewrite-Regeln standardmäßig nicht über
# Kontextgrenzen hinweg, siehe RewriteOptions Inherit in der mod_rewrite-Doku).
RewriteEngine On
RewriteCond "%{HTTP:X-Forwarded-Proto}" "=https"
RewriteRule ^ - [E=HTTPS:on]
ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined
</VirtualHost>
@@ -1,7 +1,12 @@
# ref: https://github.com/github/gitignore/blob/master/Composer.gitignore # ref: https://github.com/github/gitignore/blob/master/Composer.gitignore
composer.phar composer.phar
/vendor/
# 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).
# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control # 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 # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
@@ -10,11 +10,13 @@ docs/Api/AuditLogApi.md
docs/Api/AuthApi.md docs/Api/AuthApi.md
docs/Api/ContractsApi.md docs/Api/ContractsApi.md
docs/Api/DocumentsApi.md docs/Api/DocumentsApi.md
docs/Api/EmployeeFacilityDistancesApi.md
docs/Api/EmployeesApi.md docs/Api/EmployeesApi.md
docs/Api/FacilitiesApi.md docs/Api/FacilitiesApi.md
docs/Api/FacilityContactsApi.md docs/Api/FacilityContactsApi.md
docs/Api/FacilityQualificationRatesApi.md docs/Api/FacilityQualificationRatesApi.md
docs/Api/HealthApi.md docs/Api/HealthApi.md
docs/Api/MyFacilityDistancesApi.md
docs/Api/OrdersApi.md docs/Api/OrdersApi.md
docs/Api/RolesApi.md docs/Api/RolesApi.md
docs/Api/TimeEntriesApi.md docs/Api/TimeEntriesApi.md
@@ -33,19 +35,23 @@ docs/Model/ContractResponse.md
docs/Model/ContractResponsePagedResponse.md docs/Model/ContractResponsePagedResponse.md
docs/Model/CreateAbsenceRequest.md docs/Model/CreateAbsenceRequest.md
docs/Model/CreateContractRequest.md docs/Model/CreateContractRequest.md
docs/Model/CreateEmployeeFacilityDistanceRequest.md
docs/Model/CreateEmployeeRequest.md docs/Model/CreateEmployeeRequest.md
docs/Model/CreateFacilityContactRequest.md docs/Model/CreateFacilityContactRequest.md
docs/Model/CreateFacilityQualificationRateRequest.md docs/Model/CreateFacilityQualificationRateRequest.md
docs/Model/CreateFacilityRequest.md docs/Model/CreateFacilityRequest.md
docs/Model/CreateMyFacilityDistanceRequest.md
docs/Model/CreateOrderRequest.md docs/Model/CreateOrderRequest.md
docs/Model/CreateRoleRequest.md docs/Model/CreateRoleRequest.md
docs/Model/CreateTimeEntryRequest.md docs/Model/CreateTimeEntryRequest.md
docs/Model/CreateUserRequest.md docs/Model/CreateUserRequest.md
docs/Model/CreateValueListItemRequest.md docs/Model/CreateValueListItemRequest.md
docs/Model/DocumentResponse.md docs/Model/DocumentResponse.md
docs/Model/EmployeeFacilityDistanceResponse.md
docs/Model/EmployeeResponse.md docs/Model/EmployeeResponse.md
docs/Model/EmployeeResponsePagedResponse.md docs/Model/EmployeeResponsePagedResponse.md
docs/Model/FacilityContactResponse.md docs/Model/FacilityContactResponse.md
docs/Model/FacilityOptionResponse.md
docs/Model/FacilityQualificationRateResponse.md docs/Model/FacilityQualificationRateResponse.md
docs/Model/FacilityResponse.md docs/Model/FacilityResponse.md
docs/Model/FacilityResponsePagedResponse.md docs/Model/FacilityResponsePagedResponse.md
@@ -59,6 +65,7 @@ docs/Model/LoginResponse.md
docs/Model/LogoutRequest.md docs/Model/LogoutRequest.md
docs/Model/MeResponse.md docs/Model/MeResponse.md
docs/Model/ModuleType.md docs/Model/ModuleType.md
docs/Model/MyFacilityDistanceResponse.md
docs/Model/OrderResponse.md docs/Model/OrderResponse.md
docs/Model/OrderResponsePagedResponse.md docs/Model/OrderResponsePagedResponse.md
docs/Model/PasswordPolicyResponse.md docs/Model/PasswordPolicyResponse.md
@@ -78,6 +85,7 @@ docs/Model/TimeEntryResponse.md
docs/Model/TimeEntryResponsePagedResponse.md docs/Model/TimeEntryResponsePagedResponse.md
docs/Model/TrashAbsenceResponse.md docs/Model/TrashAbsenceResponse.md
docs/Model/TrashContractResponse.md docs/Model/TrashContractResponse.md
docs/Model/TrashEmployeeFacilityDistanceResponse.md
docs/Model/TrashEmployeeResponse.md docs/Model/TrashEmployeeResponse.md
docs/Model/TrashFacilityContactResponse.md docs/Model/TrashFacilityContactResponse.md
docs/Model/TrashFacilityQualificationRateResponse.md docs/Model/TrashFacilityQualificationRateResponse.md
@@ -87,10 +95,12 @@ docs/Model/TrashTimeEntryResponse.md
docs/Model/UpdateAbsenceRequest.md docs/Model/UpdateAbsenceRequest.md
docs/Model/UpdateContractRequest.md docs/Model/UpdateContractRequest.md
docs/Model/UpdateDocumentRequest.md docs/Model/UpdateDocumentRequest.md
docs/Model/UpdateEmployeeFacilityDistanceRequest.md
docs/Model/UpdateEmployeeRequest.md docs/Model/UpdateEmployeeRequest.md
docs/Model/UpdateFacilityContactRequest.md docs/Model/UpdateFacilityContactRequest.md
docs/Model/UpdateFacilityQualificationRateRequest.md docs/Model/UpdateFacilityQualificationRateRequest.md
docs/Model/UpdateFacilityRequest.md docs/Model/UpdateFacilityRequest.md
docs/Model/UpdateMyFacilityDistanceRequest.md
docs/Model/UpdateOrderRequest.md docs/Model/UpdateOrderRequest.md
docs/Model/UpdateRolePermissionsRequest.md docs/Model/UpdateRolePermissionsRequest.md
docs/Model/UpdateTimeEntryRequest.md docs/Model/UpdateTimeEntryRequest.md
@@ -111,11 +121,13 @@ lib/Api/AuditLogApi.php
lib/Api/AuthApi.php lib/Api/AuthApi.php
lib/Api/ContractsApi.php lib/Api/ContractsApi.php
lib/Api/DocumentsApi.php lib/Api/DocumentsApi.php
lib/Api/EmployeeFacilityDistancesApi.php
lib/Api/EmployeesApi.php lib/Api/EmployeesApi.php
lib/Api/FacilitiesApi.php lib/Api/FacilitiesApi.php
lib/Api/FacilityContactsApi.php lib/Api/FacilityContactsApi.php
lib/Api/FacilityQualificationRatesApi.php lib/Api/FacilityQualificationRatesApi.php
lib/Api/HealthApi.php lib/Api/HealthApi.php
lib/Api/MyFacilityDistancesApi.php
lib/Api/OrdersApi.php lib/Api/OrdersApi.php
lib/Api/RolesApi.php lib/Api/RolesApi.php
lib/Api/TimeEntriesApi.php lib/Api/TimeEntriesApi.php
@@ -138,19 +150,23 @@ lib/Model/ContractResponse.php
lib/Model/ContractResponsePagedResponse.php lib/Model/ContractResponsePagedResponse.php
lib/Model/CreateAbsenceRequest.php lib/Model/CreateAbsenceRequest.php
lib/Model/CreateContractRequest.php lib/Model/CreateContractRequest.php
lib/Model/CreateEmployeeFacilityDistanceRequest.php
lib/Model/CreateEmployeeRequest.php lib/Model/CreateEmployeeRequest.php
lib/Model/CreateFacilityContactRequest.php lib/Model/CreateFacilityContactRequest.php
lib/Model/CreateFacilityQualificationRateRequest.php lib/Model/CreateFacilityQualificationRateRequest.php
lib/Model/CreateFacilityRequest.php lib/Model/CreateFacilityRequest.php
lib/Model/CreateMyFacilityDistanceRequest.php
lib/Model/CreateOrderRequest.php lib/Model/CreateOrderRequest.php
lib/Model/CreateRoleRequest.php lib/Model/CreateRoleRequest.php
lib/Model/CreateTimeEntryRequest.php lib/Model/CreateTimeEntryRequest.php
lib/Model/CreateUserRequest.php lib/Model/CreateUserRequest.php
lib/Model/CreateValueListItemRequest.php lib/Model/CreateValueListItemRequest.php
lib/Model/DocumentResponse.php lib/Model/DocumentResponse.php
lib/Model/EmployeeFacilityDistanceResponse.php
lib/Model/EmployeeResponse.php lib/Model/EmployeeResponse.php
lib/Model/EmployeeResponsePagedResponse.php lib/Model/EmployeeResponsePagedResponse.php
lib/Model/FacilityContactResponse.php lib/Model/FacilityContactResponse.php
lib/Model/FacilityOptionResponse.php
lib/Model/FacilityQualificationRateResponse.php lib/Model/FacilityQualificationRateResponse.php
lib/Model/FacilityResponse.php lib/Model/FacilityResponse.php
lib/Model/FacilityResponsePagedResponse.php lib/Model/FacilityResponsePagedResponse.php
@@ -165,6 +181,7 @@ lib/Model/LogoutRequest.php
lib/Model/MeResponse.php lib/Model/MeResponse.php
lib/Model/ModelInterface.php lib/Model/ModelInterface.php
lib/Model/ModuleType.php lib/Model/ModuleType.php
lib/Model/MyFacilityDistanceResponse.php
lib/Model/OrderResponse.php lib/Model/OrderResponse.php
lib/Model/OrderResponsePagedResponse.php lib/Model/OrderResponsePagedResponse.php
lib/Model/PasswordPolicyResponse.php lib/Model/PasswordPolicyResponse.php
@@ -184,6 +201,7 @@ lib/Model/TimeEntryResponse.php
lib/Model/TimeEntryResponsePagedResponse.php lib/Model/TimeEntryResponsePagedResponse.php
lib/Model/TrashAbsenceResponse.php lib/Model/TrashAbsenceResponse.php
lib/Model/TrashContractResponse.php lib/Model/TrashContractResponse.php
lib/Model/TrashEmployeeFacilityDistanceResponse.php
lib/Model/TrashEmployeeResponse.php lib/Model/TrashEmployeeResponse.php
lib/Model/TrashFacilityContactResponse.php lib/Model/TrashFacilityContactResponse.php
lib/Model/TrashFacilityQualificationRateResponse.php lib/Model/TrashFacilityQualificationRateResponse.php
@@ -193,10 +211,12 @@ lib/Model/TrashTimeEntryResponse.php
lib/Model/UpdateAbsenceRequest.php lib/Model/UpdateAbsenceRequest.php
lib/Model/UpdateContractRequest.php lib/Model/UpdateContractRequest.php
lib/Model/UpdateDocumentRequest.php lib/Model/UpdateDocumentRequest.php
lib/Model/UpdateEmployeeFacilityDistanceRequest.php
lib/Model/UpdateEmployeeRequest.php lib/Model/UpdateEmployeeRequest.php
lib/Model/UpdateFacilityContactRequest.php lib/Model/UpdateFacilityContactRequest.php
lib/Model/UpdateFacilityQualificationRateRequest.php lib/Model/UpdateFacilityQualificationRateRequest.php
lib/Model/UpdateFacilityRequest.php lib/Model/UpdateFacilityRequest.php
lib/Model/UpdateMyFacilityDistanceRequest.php
lib/Model/UpdateOrderRequest.php lib/Model/UpdateOrderRequest.php
lib/Model/UpdateRolePermissionsRequest.php lib/Model/UpdateRolePermissionsRequest.php
lib/Model/UpdateTimeEntryRequest.php lib/Model/UpdateTimeEntryRequest.php
@@ -211,3 +231,13 @@ lib/Model/ValueListTransitionResponse.php
lib/Model/ValueListUsageResponse.php lib/Model/ValueListUsageResponse.php
lib/ObjectSerializer.php lib/ObjectSerializer.php
phpunit.xml.dist 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
@@ -110,6 +110,10 @@ Class | Method | HTTP request | Description
*DocumentsApi* | [**apiDocumentsIdDownloadGet**](docs/Api/DocumentsApi.md#apidocumentsiddownloadget) | **GET** /api/documents/{id}/download | *DocumentsApi* | [**apiDocumentsIdDownloadGet**](docs/Api/DocumentsApi.md#apidocumentsiddownloadget) | **GET** /api/documents/{id}/download |
*DocumentsApi* | [**apiDocumentsIdPut**](docs/Api/DocumentsApi.md#apidocumentsidput) | **PUT** /api/documents/{id} | *DocumentsApi* | [**apiDocumentsIdPut**](docs/Api/DocumentsApi.md#apidocumentsidput) | **PUT** /api/documents/{id} |
*DocumentsApi* | [**apiDocumentsPost**](docs/Api/DocumentsApi.md#apidocumentspost) | **POST** /api/documents | *DocumentsApi* | [**apiDocumentsPost**](docs/Api/DocumentsApi.md#apidocumentspost) | **POST** /api/documents |
*EmployeeFacilityDistancesApi* | [**apiFacilitiesFacilityIdEmployeeDistancesGet**](docs/Api/EmployeeFacilityDistancesApi.md#apifacilitiesfacilityidemployeedistancesget) | **GET** /api/facilities/{facilityId}/employee-distances |
*EmployeeFacilityDistancesApi* | [**apiFacilitiesFacilityIdEmployeeDistancesIdDelete**](docs/Api/EmployeeFacilityDistancesApi.md#apifacilitiesfacilityidemployeedistancesiddelete) | **DELETE** /api/facilities/{facilityId}/employee-distances/{id} |
*EmployeeFacilityDistancesApi* | [**apiFacilitiesFacilityIdEmployeeDistancesIdPut**](docs/Api/EmployeeFacilityDistancesApi.md#apifacilitiesfacilityidemployeedistancesidput) | **PUT** /api/facilities/{facilityId}/employee-distances/{id} |
*EmployeeFacilityDistancesApi* | [**apiFacilitiesFacilityIdEmployeeDistancesPost**](docs/Api/EmployeeFacilityDistancesApi.md#apifacilitiesfacilityidemployeedistancespost) | **POST** /api/facilities/{facilityId}/employee-distances |
*EmployeesApi* | [**apiEmployeesGet**](docs/Api/EmployeesApi.md#apiemployeesget) | **GET** /api/employees | *EmployeesApi* | [**apiEmployeesGet**](docs/Api/EmployeesApi.md#apiemployeesget) | **GET** /api/employees |
*EmployeesApi* | [**apiEmployeesIdDelete**](docs/Api/EmployeesApi.md#apiemployeesiddelete) | **DELETE** /api/employees/{id} | *EmployeesApi* | [**apiEmployeesIdDelete**](docs/Api/EmployeesApi.md#apiemployeesiddelete) | **DELETE** /api/employees/{id} |
*EmployeesApi* | [**apiEmployeesIdGet**](docs/Api/EmployeesApi.md#apiemployeesidget) | **GET** /api/employees/{id} | *EmployeesApi* | [**apiEmployeesIdGet**](docs/Api/EmployeesApi.md#apiemployeesidget) | **GET** /api/employees/{id} |
@@ -129,6 +133,10 @@ Class | Method | HTTP request | Description
*FacilityQualificationRatesApi* | [**apiFacilitiesFacilityIdQualificationRatesIdPut**](docs/Api/FacilityQualificationRatesApi.md#apifacilitiesfacilityidqualificationratesidput) | **PUT** /api/facilities/{facilityId}/qualification-rates/{id} | *FacilityQualificationRatesApi* | [**apiFacilitiesFacilityIdQualificationRatesIdPut**](docs/Api/FacilityQualificationRatesApi.md#apifacilitiesfacilityidqualificationratesidput) | **PUT** /api/facilities/{facilityId}/qualification-rates/{id} |
*FacilityQualificationRatesApi* | [**apiFacilitiesFacilityIdQualificationRatesPost**](docs/Api/FacilityQualificationRatesApi.md#apifacilitiesfacilityidqualificationratespost) | **POST** /api/facilities/{facilityId}/qualification-rates | *FacilityQualificationRatesApi* | [**apiFacilitiesFacilityIdQualificationRatesPost**](docs/Api/FacilityQualificationRatesApi.md#apifacilitiesfacilityidqualificationratespost) | **POST** /api/facilities/{facilityId}/qualification-rates |
*HealthApi* | [**apiHealthGet**](docs/Api/HealthApi.md#apihealthget) | **GET** /api/health | *HealthApi* | [**apiHealthGet**](docs/Api/HealthApi.md#apihealthget) | **GET** /api/health |
*MyFacilityDistancesApi* | [**apiMeFacilityDistancesFacilitiesGet**](docs/Api/MyFacilityDistancesApi.md#apimefacilitydistancesfacilitiesget) | **GET** /api/me/facility-distances/facilities |
*MyFacilityDistancesApi* | [**apiMeFacilityDistancesGet**](docs/Api/MyFacilityDistancesApi.md#apimefacilitydistancesget) | **GET** /api/me/facility-distances |
*MyFacilityDistancesApi* | [**apiMeFacilityDistancesIdPut**](docs/Api/MyFacilityDistancesApi.md#apimefacilitydistancesidput) | **PUT** /api/me/facility-distances/{id} |
*MyFacilityDistancesApi* | [**apiMeFacilityDistancesPost**](docs/Api/MyFacilityDistancesApi.md#apimefacilitydistancespost) | **POST** /api/me/facility-distances |
*OrdersApi* | [**apiOrdersGet**](docs/Api/OrdersApi.md#apiordersget) | **GET** /api/orders | *OrdersApi* | [**apiOrdersGet**](docs/Api/OrdersApi.md#apiordersget) | **GET** /api/orders |
*OrdersApi* | [**apiOrdersIdDelete**](docs/Api/OrdersApi.md#apiordersiddelete) | **DELETE** /api/orders/{id} | *OrdersApi* | [**apiOrdersIdDelete**](docs/Api/OrdersApi.md#apiordersiddelete) | **DELETE** /api/orders/{id} |
*OrdersApi* | [**apiOrdersIdGet**](docs/Api/OrdersApi.md#apiordersidget) | **GET** /api/orders/{id} | *OrdersApi* | [**apiOrdersIdGet**](docs/Api/OrdersApi.md#apiordersidget) | **GET** /api/orders/{id} |
@@ -149,6 +157,8 @@ Class | Method | HTTP request | Description
*TrashApi* | [**apiTrashAbsencesIdRestorePost**](docs/Api/TrashApi.md#apitrashabsencesidrestorepost) | **POST** /api/trash/absences/{id}/restore | *TrashApi* | [**apiTrashAbsencesIdRestorePost**](docs/Api/TrashApi.md#apitrashabsencesidrestorepost) | **POST** /api/trash/absences/{id}/restore |
*TrashApi* | [**apiTrashContractsGet**](docs/Api/TrashApi.md#apitrashcontractsget) | **GET** /api/trash/contracts | *TrashApi* | [**apiTrashContractsGet**](docs/Api/TrashApi.md#apitrashcontractsget) | **GET** /api/trash/contracts |
*TrashApi* | [**apiTrashContractsIdRestorePost**](docs/Api/TrashApi.md#apitrashcontractsidrestorepost) | **POST** /api/trash/contracts/{id}/restore | *TrashApi* | [**apiTrashContractsIdRestorePost**](docs/Api/TrashApi.md#apitrashcontractsidrestorepost) | **POST** /api/trash/contracts/{id}/restore |
*TrashApi* | [**apiTrashEmployeeFacilityDistancesGet**](docs/Api/TrashApi.md#apitrashemployeefacilitydistancesget) | **GET** /api/trash/employee-facility-distances |
*TrashApi* | [**apiTrashEmployeeFacilityDistancesIdRestorePost**](docs/Api/TrashApi.md#apitrashemployeefacilitydistancesidrestorepost) | **POST** /api/trash/employee-facility-distances/{id}/restore |
*TrashApi* | [**apiTrashEmployeesGet**](docs/Api/TrashApi.md#apitrashemployeesget) | **GET** /api/trash/employees | *TrashApi* | [**apiTrashEmployeesGet**](docs/Api/TrashApi.md#apitrashemployeesget) | **GET** /api/trash/employees |
*TrashApi* | [**apiTrashEmployeesIdRestorePost**](docs/Api/TrashApi.md#apitrashemployeesidrestorepost) | **POST** /api/trash/employees/{id}/restore | *TrashApi* | [**apiTrashEmployeesIdRestorePost**](docs/Api/TrashApi.md#apitrashemployeesidrestorepost) | **POST** /api/trash/employees/{id}/restore |
*TrashApi* | [**apiTrashFacilitiesGet**](docs/Api/TrashApi.md#apitrashfacilitiesget) | **GET** /api/trash/facilities | *TrashApi* | [**apiTrashFacilitiesGet**](docs/Api/TrashApi.md#apitrashfacilitiesget) | **GET** /api/trash/facilities |
@@ -191,19 +201,23 @@ Class | Method | HTTP request | Description
- [ContractResponsePagedResponse](docs/Model/ContractResponsePagedResponse.md) - [ContractResponsePagedResponse](docs/Model/ContractResponsePagedResponse.md)
- [CreateAbsenceRequest](docs/Model/CreateAbsenceRequest.md) - [CreateAbsenceRequest](docs/Model/CreateAbsenceRequest.md)
- [CreateContractRequest](docs/Model/CreateContractRequest.md) - [CreateContractRequest](docs/Model/CreateContractRequest.md)
- [CreateEmployeeFacilityDistanceRequest](docs/Model/CreateEmployeeFacilityDistanceRequest.md)
- [CreateEmployeeRequest](docs/Model/CreateEmployeeRequest.md) - [CreateEmployeeRequest](docs/Model/CreateEmployeeRequest.md)
- [CreateFacilityContactRequest](docs/Model/CreateFacilityContactRequest.md) - [CreateFacilityContactRequest](docs/Model/CreateFacilityContactRequest.md)
- [CreateFacilityQualificationRateRequest](docs/Model/CreateFacilityQualificationRateRequest.md) - [CreateFacilityQualificationRateRequest](docs/Model/CreateFacilityQualificationRateRequest.md)
- [CreateFacilityRequest](docs/Model/CreateFacilityRequest.md) - [CreateFacilityRequest](docs/Model/CreateFacilityRequest.md)
- [CreateMyFacilityDistanceRequest](docs/Model/CreateMyFacilityDistanceRequest.md)
- [CreateOrderRequest](docs/Model/CreateOrderRequest.md) - [CreateOrderRequest](docs/Model/CreateOrderRequest.md)
- [CreateRoleRequest](docs/Model/CreateRoleRequest.md) - [CreateRoleRequest](docs/Model/CreateRoleRequest.md)
- [CreateTimeEntryRequest](docs/Model/CreateTimeEntryRequest.md) - [CreateTimeEntryRequest](docs/Model/CreateTimeEntryRequest.md)
- [CreateUserRequest](docs/Model/CreateUserRequest.md) - [CreateUserRequest](docs/Model/CreateUserRequest.md)
- [CreateValueListItemRequest](docs/Model/CreateValueListItemRequest.md) - [CreateValueListItemRequest](docs/Model/CreateValueListItemRequest.md)
- [DocumentResponse](docs/Model/DocumentResponse.md) - [DocumentResponse](docs/Model/DocumentResponse.md)
- [EmployeeFacilityDistanceResponse](docs/Model/EmployeeFacilityDistanceResponse.md)
- [EmployeeResponse](docs/Model/EmployeeResponse.md) - [EmployeeResponse](docs/Model/EmployeeResponse.md)
- [EmployeeResponsePagedResponse](docs/Model/EmployeeResponsePagedResponse.md) - [EmployeeResponsePagedResponse](docs/Model/EmployeeResponsePagedResponse.md)
- [FacilityContactResponse](docs/Model/FacilityContactResponse.md) - [FacilityContactResponse](docs/Model/FacilityContactResponse.md)
- [FacilityOptionResponse](docs/Model/FacilityOptionResponse.md)
- [FacilityQualificationRateResponse](docs/Model/FacilityQualificationRateResponse.md) - [FacilityQualificationRateResponse](docs/Model/FacilityQualificationRateResponse.md)
- [FacilityResponse](docs/Model/FacilityResponse.md) - [FacilityResponse](docs/Model/FacilityResponse.md)
- [FacilityResponsePagedResponse](docs/Model/FacilityResponsePagedResponse.md) - [FacilityResponsePagedResponse](docs/Model/FacilityResponsePagedResponse.md)
@@ -217,6 +231,7 @@ Class | Method | HTTP request | Description
- [LogoutRequest](docs/Model/LogoutRequest.md) - [LogoutRequest](docs/Model/LogoutRequest.md)
- [MeResponse](docs/Model/MeResponse.md) - [MeResponse](docs/Model/MeResponse.md)
- [ModuleType](docs/Model/ModuleType.md) - [ModuleType](docs/Model/ModuleType.md)
- [MyFacilityDistanceResponse](docs/Model/MyFacilityDistanceResponse.md)
- [OrderResponse](docs/Model/OrderResponse.md) - [OrderResponse](docs/Model/OrderResponse.md)
- [OrderResponsePagedResponse](docs/Model/OrderResponsePagedResponse.md) - [OrderResponsePagedResponse](docs/Model/OrderResponsePagedResponse.md)
- [PasswordPolicyResponse](docs/Model/PasswordPolicyResponse.md) - [PasswordPolicyResponse](docs/Model/PasswordPolicyResponse.md)
@@ -236,6 +251,7 @@ Class | Method | HTTP request | Description
- [TimeEntryResponsePagedResponse](docs/Model/TimeEntryResponsePagedResponse.md) - [TimeEntryResponsePagedResponse](docs/Model/TimeEntryResponsePagedResponse.md)
- [TrashAbsenceResponse](docs/Model/TrashAbsenceResponse.md) - [TrashAbsenceResponse](docs/Model/TrashAbsenceResponse.md)
- [TrashContractResponse](docs/Model/TrashContractResponse.md) - [TrashContractResponse](docs/Model/TrashContractResponse.md)
- [TrashEmployeeFacilityDistanceResponse](docs/Model/TrashEmployeeFacilityDistanceResponse.md)
- [TrashEmployeeResponse](docs/Model/TrashEmployeeResponse.md) - [TrashEmployeeResponse](docs/Model/TrashEmployeeResponse.md)
- [TrashFacilityContactResponse](docs/Model/TrashFacilityContactResponse.md) - [TrashFacilityContactResponse](docs/Model/TrashFacilityContactResponse.md)
- [TrashFacilityQualificationRateResponse](docs/Model/TrashFacilityQualificationRateResponse.md) - [TrashFacilityQualificationRateResponse](docs/Model/TrashFacilityQualificationRateResponse.md)
@@ -245,10 +261,12 @@ Class | Method | HTTP request | Description
- [UpdateAbsenceRequest](docs/Model/UpdateAbsenceRequest.md) - [UpdateAbsenceRequest](docs/Model/UpdateAbsenceRequest.md)
- [UpdateContractRequest](docs/Model/UpdateContractRequest.md) - [UpdateContractRequest](docs/Model/UpdateContractRequest.md)
- [UpdateDocumentRequest](docs/Model/UpdateDocumentRequest.md) - [UpdateDocumentRequest](docs/Model/UpdateDocumentRequest.md)
- [UpdateEmployeeFacilityDistanceRequest](docs/Model/UpdateEmployeeFacilityDistanceRequest.md)
- [UpdateEmployeeRequest](docs/Model/UpdateEmployeeRequest.md) - [UpdateEmployeeRequest](docs/Model/UpdateEmployeeRequest.md)
- [UpdateFacilityContactRequest](docs/Model/UpdateFacilityContactRequest.md) - [UpdateFacilityContactRequest](docs/Model/UpdateFacilityContactRequest.md)
- [UpdateFacilityQualificationRateRequest](docs/Model/UpdateFacilityQualificationRateRequest.md) - [UpdateFacilityQualificationRateRequest](docs/Model/UpdateFacilityQualificationRateRequest.md)
- [UpdateFacilityRequest](docs/Model/UpdateFacilityRequest.md) - [UpdateFacilityRequest](docs/Model/UpdateFacilityRequest.md)
- [UpdateMyFacilityDistanceRequest](docs/Model/UpdateMyFacilityDistanceRequest.md)
- [UpdateOrderRequest](docs/Model/UpdateOrderRequest.md) - [UpdateOrderRequest](docs/Model/UpdateOrderRequest.md)
- [UpdateRolePermissionsRequest](docs/Model/UpdateRolePermissionsRequest.md) - [UpdateRolePermissionsRequest](docs/Model/UpdateRolePermissionsRequest.md)
- [UpdateTimeEntryRequest](docs/Model/UpdateTimeEntryRequest.md) - [UpdateTimeEntryRequest](docs/Model/UpdateTimeEntryRequest.md)
@@ -0,0 +1,250 @@
# OmsorgCoreClient\EmployeeFacilityDistancesApi
All URIs are relative to http://localhost, except if the operation defines another base path.
| Method | HTTP request | Description |
| ------------- | ------------- | ------------- |
| [**apiFacilitiesFacilityIdEmployeeDistancesGet()**](EmployeeFacilityDistancesApi.md#apiFacilitiesFacilityIdEmployeeDistancesGet) | **GET** /api/facilities/{facilityId}/employee-distances | |
| [**apiFacilitiesFacilityIdEmployeeDistancesIdDelete()**](EmployeeFacilityDistancesApi.md#apiFacilitiesFacilityIdEmployeeDistancesIdDelete) | **DELETE** /api/facilities/{facilityId}/employee-distances/{id} | |
| [**apiFacilitiesFacilityIdEmployeeDistancesIdPut()**](EmployeeFacilityDistancesApi.md#apiFacilitiesFacilityIdEmployeeDistancesIdPut) | **PUT** /api/facilities/{facilityId}/employee-distances/{id} | |
| [**apiFacilitiesFacilityIdEmployeeDistancesPost()**](EmployeeFacilityDistancesApi.md#apiFacilitiesFacilityIdEmployeeDistancesPost) | **POST** /api/facilities/{facilityId}/employee-distances | |
## `apiFacilitiesFacilityIdEmployeeDistancesGet()`
```php
apiFacilitiesFacilityIdEmployeeDistancesGet($facility_id): \OmsorgCoreClient\Model\EmployeeFacilityDistanceResponse[]
```
### 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\EmployeeFacilityDistancesApi(
// 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
);
$facility_id = 'facility_id_example'; // string
try {
$result = $apiInstance->apiFacilitiesFacilityIdEmployeeDistancesGet($facility_id);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling EmployeeFacilityDistancesApi->apiFacilitiesFacilityIdEmployeeDistancesGet: ', $e->getMessage(), PHP_EOL;
}
```
### Parameters
| Name | Type | Description | Notes |
| ------------- | ------------- | ------------- | ------------- |
| **facility_id** | **string**| | |
### Return type
[**\OmsorgCoreClient\Model\EmployeeFacilityDistanceResponse[]**](../Model/EmployeeFacilityDistanceResponse.md)
### Authorization
[Bearer](../../README.md#Bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: `text/plain`, `application/json`, `text/json`
[[Back to top]](#) [[Back to API list]](../../README.md#endpoints)
[[Back to Model list]](../../README.md#models)
[[Back to README]](../../README.md)
## `apiFacilitiesFacilityIdEmployeeDistancesIdDelete()`
```php
apiFacilitiesFacilityIdEmployeeDistancesIdDelete($facility_id, $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\EmployeeFacilityDistancesApi(
// 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
);
$facility_id = 'facility_id_example'; // string
$id = 'id_example'; // string
try {
$apiInstance->apiFacilitiesFacilityIdEmployeeDistancesIdDelete($facility_id, $id);
} catch (Exception $e) {
echo 'Exception when calling EmployeeFacilityDistancesApi->apiFacilitiesFacilityIdEmployeeDistancesIdDelete: ', $e->getMessage(), PHP_EOL;
}
```
### Parameters
| Name | Type | Description | Notes |
| ------------- | ------------- | ------------- | ------------- |
| **facility_id** | **string**| | |
| **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)
## `apiFacilitiesFacilityIdEmployeeDistancesIdPut()`
```php
apiFacilitiesFacilityIdEmployeeDistancesIdPut($facility_id, $id, $update_employee_facility_distance_request): \OmsorgCoreClient\Model\EmployeeFacilityDistanceResponse
```
### 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\EmployeeFacilityDistancesApi(
// 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
);
$facility_id = 'facility_id_example'; // string
$id = 'id_example'; // string
$update_employee_facility_distance_request = new \OmsorgCoreClient\Model\UpdateEmployeeFacilityDistanceRequest(); // \OmsorgCoreClient\Model\UpdateEmployeeFacilityDistanceRequest
try {
$result = $apiInstance->apiFacilitiesFacilityIdEmployeeDistancesIdPut($facility_id, $id, $update_employee_facility_distance_request);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling EmployeeFacilityDistancesApi->apiFacilitiesFacilityIdEmployeeDistancesIdPut: ', $e->getMessage(), PHP_EOL;
}
```
### Parameters
| Name | Type | Description | Notes |
| ------------- | ------------- | ------------- | ------------- |
| **facility_id** | **string**| | |
| **id** | **string**| | |
| **update_employee_facility_distance_request** | [**\OmsorgCoreClient\Model\UpdateEmployeeFacilityDistanceRequest**](../Model/UpdateEmployeeFacilityDistanceRequest.md)| | [optional] |
### Return type
[**\OmsorgCoreClient\Model\EmployeeFacilityDistanceResponse**](../Model/EmployeeFacilityDistanceResponse.md)
### Authorization
[Bearer](../../README.md#Bearer)
### HTTP request headers
- **Content-Type**: `application/json`, `text/json`, `application/*+json`
- **Accept**: `text/plain`, `application/json`, `text/json`
[[Back to top]](#) [[Back to API list]](../../README.md#endpoints)
[[Back to Model list]](../../README.md#models)
[[Back to README]](../../README.md)
## `apiFacilitiesFacilityIdEmployeeDistancesPost()`
```php
apiFacilitiesFacilityIdEmployeeDistancesPost($facility_id, $create_employee_facility_distance_request): \OmsorgCoreClient\Model\EmployeeFacilityDistanceResponse
```
### 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\EmployeeFacilityDistancesApi(
// 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
);
$facility_id = 'facility_id_example'; // string
$create_employee_facility_distance_request = new \OmsorgCoreClient\Model\CreateEmployeeFacilityDistanceRequest(); // \OmsorgCoreClient\Model\CreateEmployeeFacilityDistanceRequest
try {
$result = $apiInstance->apiFacilitiesFacilityIdEmployeeDistancesPost($facility_id, $create_employee_facility_distance_request);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling EmployeeFacilityDistancesApi->apiFacilitiesFacilityIdEmployeeDistancesPost: ', $e->getMessage(), PHP_EOL;
}
```
### Parameters
| Name | Type | Description | Notes |
| ------------- | ------------- | ------------- | ------------- |
| **facility_id** | **string**| | |
| **create_employee_facility_distance_request** | [**\OmsorgCoreClient\Model\CreateEmployeeFacilityDistanceRequest**](../Model/CreateEmployeeFacilityDistanceRequest.md)| | [optional] |
### Return type
[**\OmsorgCoreClient\Model\EmployeeFacilityDistanceResponse**](../Model/EmployeeFacilityDistanceResponse.md)
### Authorization
[Bearer](../../README.md#Bearer)
### HTTP request headers
- **Content-Type**: `application/json`, `text/json`, `application/*+json`
- **Accept**: `text/plain`, `application/json`, `text/json`
[[Back to top]](#) [[Back to API list]](../../README.md#endpoints)
[[Back to Model list]](../../README.md#models)
[[Back to README]](../../README.md)
@@ -0,0 +1,239 @@
# OmsorgCoreClient\MyFacilityDistancesApi
All URIs are relative to http://localhost, except if the operation defines another base path.
| Method | HTTP request | Description |
| ------------- | ------------- | ------------- |
| [**apiMeFacilityDistancesFacilitiesGet()**](MyFacilityDistancesApi.md#apiMeFacilityDistancesFacilitiesGet) | **GET** /api/me/facility-distances/facilities | |
| [**apiMeFacilityDistancesGet()**](MyFacilityDistancesApi.md#apiMeFacilityDistancesGet) | **GET** /api/me/facility-distances | |
| [**apiMeFacilityDistancesIdPut()**](MyFacilityDistancesApi.md#apiMeFacilityDistancesIdPut) | **PUT** /api/me/facility-distances/{id} | |
| [**apiMeFacilityDistancesPost()**](MyFacilityDistancesApi.md#apiMeFacilityDistancesPost) | **POST** /api/me/facility-distances | |
## `apiMeFacilityDistancesFacilitiesGet()`
```php
apiMeFacilityDistancesFacilitiesGet(): \OmsorgCoreClient\Model\FacilityOptionResponse[]
```
### 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\MyFacilityDistancesApi(
// 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
);
try {
$result = $apiInstance->apiMeFacilityDistancesFacilitiesGet();
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling MyFacilityDistancesApi->apiMeFacilityDistancesFacilitiesGet: ', $e->getMessage(), PHP_EOL;
}
```
### Parameters
This endpoint does not need any parameter.
### Return type
[**\OmsorgCoreClient\Model\FacilityOptionResponse[]**](../Model/FacilityOptionResponse.md)
### Authorization
[Bearer](../../README.md#Bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: `text/plain`, `application/json`, `text/json`
[[Back to top]](#) [[Back to API list]](../../README.md#endpoints)
[[Back to Model list]](../../README.md#models)
[[Back to README]](../../README.md)
## `apiMeFacilityDistancesGet()`
```php
apiMeFacilityDistancesGet(): \OmsorgCoreClient\Model\MyFacilityDistanceResponse[]
```
### 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\MyFacilityDistancesApi(
// 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
);
try {
$result = $apiInstance->apiMeFacilityDistancesGet();
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling MyFacilityDistancesApi->apiMeFacilityDistancesGet: ', $e->getMessage(), PHP_EOL;
}
```
### Parameters
This endpoint does not need any parameter.
### Return type
[**\OmsorgCoreClient\Model\MyFacilityDistanceResponse[]**](../Model/MyFacilityDistanceResponse.md)
### Authorization
[Bearer](../../README.md#Bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: `text/plain`, `application/json`, `text/json`
[[Back to top]](#) [[Back to API list]](../../README.md#endpoints)
[[Back to Model list]](../../README.md#models)
[[Back to README]](../../README.md)
## `apiMeFacilityDistancesIdPut()`
```php
apiMeFacilityDistancesIdPut($id, $update_my_facility_distance_request): \OmsorgCoreClient\Model\MyFacilityDistanceResponse
```
### 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\MyFacilityDistancesApi(
// 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
$update_my_facility_distance_request = new \OmsorgCoreClient\Model\UpdateMyFacilityDistanceRequest(); // \OmsorgCoreClient\Model\UpdateMyFacilityDistanceRequest
try {
$result = $apiInstance->apiMeFacilityDistancesIdPut($id, $update_my_facility_distance_request);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling MyFacilityDistancesApi->apiMeFacilityDistancesIdPut: ', $e->getMessage(), PHP_EOL;
}
```
### Parameters
| Name | Type | Description | Notes |
| ------------- | ------------- | ------------- | ------------- |
| **id** | **string**| | |
| **update_my_facility_distance_request** | [**\OmsorgCoreClient\Model\UpdateMyFacilityDistanceRequest**](../Model/UpdateMyFacilityDistanceRequest.md)| | [optional] |
### Return type
[**\OmsorgCoreClient\Model\MyFacilityDistanceResponse**](../Model/MyFacilityDistanceResponse.md)
### Authorization
[Bearer](../../README.md#Bearer)
### HTTP request headers
- **Content-Type**: `application/json`, `text/json`, `application/*+json`
- **Accept**: `text/plain`, `application/json`, `text/json`
[[Back to top]](#) [[Back to API list]](../../README.md#endpoints)
[[Back to Model list]](../../README.md#models)
[[Back to README]](../../README.md)
## `apiMeFacilityDistancesPost()`
```php
apiMeFacilityDistancesPost($create_my_facility_distance_request): \OmsorgCoreClient\Model\MyFacilityDistanceResponse
```
### 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\MyFacilityDistancesApi(
// 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
);
$create_my_facility_distance_request = new \OmsorgCoreClient\Model\CreateMyFacilityDistanceRequest(); // \OmsorgCoreClient\Model\CreateMyFacilityDistanceRequest
try {
$result = $apiInstance->apiMeFacilityDistancesPost($create_my_facility_distance_request);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling MyFacilityDistancesApi->apiMeFacilityDistancesPost: ', $e->getMessage(), PHP_EOL;
}
```
### Parameters
| Name | Type | Description | Notes |
| ------------- | ------------- | ------------- | ------------- |
| **create_my_facility_distance_request** | [**\OmsorgCoreClient\Model\CreateMyFacilityDistanceRequest**](../Model/CreateMyFacilityDistanceRequest.md)| | [optional] |
### Return type
[**\OmsorgCoreClient\Model\MyFacilityDistanceResponse**](../Model/MyFacilityDistanceResponse.md)
### Authorization
[Bearer](../../README.md#Bearer)
### HTTP request headers
- **Content-Type**: `application/json`, `text/json`, `application/*+json`
- **Accept**: `text/plain`, `application/json`, `text/json`
[[Back to top]](#) [[Back to API list]](../../README.md#endpoints)
[[Back to Model list]](../../README.md#models)
[[Back to README]](../../README.md)
@@ -8,6 +8,8 @@ All URIs are relative to http://localhost, except if the operation defines anoth
| [**apiTrashAbsencesIdRestorePost()**](TrashApi.md#apiTrashAbsencesIdRestorePost) | **POST** /api/trash/absences/{id}/restore | | | [**apiTrashAbsencesIdRestorePost()**](TrashApi.md#apiTrashAbsencesIdRestorePost) | **POST** /api/trash/absences/{id}/restore | |
| [**apiTrashContractsGet()**](TrashApi.md#apiTrashContractsGet) | **GET** /api/trash/contracts | | | [**apiTrashContractsGet()**](TrashApi.md#apiTrashContractsGet) | **GET** /api/trash/contracts | |
| [**apiTrashContractsIdRestorePost()**](TrashApi.md#apiTrashContractsIdRestorePost) | **POST** /api/trash/contracts/{id}/restore | | | [**apiTrashContractsIdRestorePost()**](TrashApi.md#apiTrashContractsIdRestorePost) | **POST** /api/trash/contracts/{id}/restore | |
| [**apiTrashEmployeeFacilityDistancesGet()**](TrashApi.md#apiTrashEmployeeFacilityDistancesGet) | **GET** /api/trash/employee-facility-distances | |
| [**apiTrashEmployeeFacilityDistancesIdRestorePost()**](TrashApi.md#apiTrashEmployeeFacilityDistancesIdRestorePost) | **POST** /api/trash/employee-facility-distances/{id}/restore | |
| [**apiTrashEmployeesGet()**](TrashApi.md#apiTrashEmployeesGet) | **GET** /api/trash/employees | | | [**apiTrashEmployeesGet()**](TrashApi.md#apiTrashEmployeesGet) | **GET** /api/trash/employees | |
| [**apiTrashEmployeesIdRestorePost()**](TrashApi.md#apiTrashEmployeesIdRestorePost) | **POST** /api/trash/employees/{id}/restore | | | [**apiTrashEmployeesIdRestorePost()**](TrashApi.md#apiTrashEmployeesIdRestorePost) | **POST** /api/trash/employees/{id}/restore | |
| [**apiTrashFacilitiesGet()**](TrashApi.md#apiTrashFacilitiesGet) | **GET** /api/trash/facilities | | | [**apiTrashFacilitiesGet()**](TrashApi.md#apiTrashFacilitiesGet) | **GET** /api/trash/facilities | |
@@ -252,6 +254,121 @@ void (empty response body)
[[Back to Model list]](../../README.md#models) [[Back to Model list]](../../README.md#models)
[[Back to README]](../../README.md) [[Back to README]](../../README.md)
## `apiTrashEmployeeFacilityDistancesGet()`
```php
apiTrashEmployeeFacilityDistancesGet($search): \OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]
```
### 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\TrashApi(
// 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
);
$search = 'search_example'; // string
try {
$result = $apiInstance->apiTrashEmployeeFacilityDistancesGet($search);
print_r($result);
} catch (Exception $e) {
echo 'Exception when calling TrashApi->apiTrashEmployeeFacilityDistancesGet: ', $e->getMessage(), PHP_EOL;
}
```
### Parameters
| Name | Type | Description | Notes |
| ------------- | ------------- | ------------- | ------------- |
| **search** | **string**| | [optional] |
### Return type
[**\OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]**](../Model/TrashEmployeeFacilityDistanceResponse.md)
### Authorization
[Bearer](../../README.md#Bearer)
### HTTP request headers
- **Content-Type**: Not defined
- **Accept**: `text/plain`, `application/json`, `text/json`
[[Back to top]](#) [[Back to API list]](../../README.md#endpoints)
[[Back to Model list]](../../README.md#models)
[[Back to README]](../../README.md)
## `apiTrashEmployeeFacilityDistancesIdRestorePost()`
```php
apiTrashEmployeeFacilityDistancesIdRestorePost($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\TrashApi(
// 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->apiTrashEmployeeFacilityDistancesIdRestorePost($id);
} catch (Exception $e) {
echo 'Exception when calling TrashApi->apiTrashEmployeeFacilityDistancesIdRestorePost: ', $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)
## `apiTrashEmployeesGet()` ## `apiTrashEmployeesGet()`
```php ```php
@@ -0,0 +1,10 @@
# # CreateEmployeeFacilityDistanceRequest
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**employee_id** | **string** | | [optional]
**distance_km** | **float** | | [optional]
[[Back to Model list]](../../README.md#models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md)
@@ -0,0 +1,10 @@
# # CreateMyFacilityDistanceRequest
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**facility_id** | **string** | | [optional]
**distance_km** | **float** | | [optional]
[[Back to Model list]](../../README.md#models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md)
@@ -0,0 +1,14 @@
# # EmployeeFacilityDistanceResponse
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **string** | | [optional]
**facility_id** | **string** | | [optional]
**employee_id** | **string** | | [optional]
**employee_first_name** | **string** | | [optional]
**employee_last_name** | **string** | | [optional]
**distance_km** | **float** | | [optional]
[[Back to Model list]](../../README.md#models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md)
@@ -0,0 +1,10 @@
# # FacilityOptionResponse
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **string** | | [optional]
**name** | **string** | | [optional]
[[Back to Model list]](../../README.md#models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md)
@@ -24,6 +24,8 @@ Name | Type | Description | Notes
**sunday_surcharge_percent** | **float** | | [optional] **sunday_surcharge_percent** | **float** | | [optional]
**holiday_surcharge_percent** | **float** | | [optional] **holiday_surcharge_percent** | **float** | | [optional]
**travel_cost_rate** | **float** | | [optional] **travel_cost_rate** | **float** | | [optional]
**travel_cost_mode** | **string** | | [optional]
**travel_cost_per_km** | **float** | | [optional]
**minimum_hours** | **float** | | [optional] **minimum_hours** | **float** | | [optional]
**break_policy** | **string** | | [optional] **break_policy** | **string** | | [optional]
**billing_interval** | **string** | | [optional] **billing_interval** | **string** | | [optional]
@@ -0,0 +1,12 @@
# # MyFacilityDistanceResponse
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **string** | | [optional]
**facility_id** | **string** | | [optional]
**facility_name** | **string** | | [optional]
**distance_km** | **float** | | [optional]
[[Back to Model list]](../../README.md#models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md)
@@ -0,0 +1,13 @@
# # TrashEmployeeFacilityDistanceResponse
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **string** | | [optional]
**facility_id** | **string** | | [optional]
**employee_id** | **string** | | [optional]
**distance_km** | **float** | | [optional]
**deleted_at** | **\DateTime** | | [optional]
[[Back to Model list]](../../README.md#models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md)
@@ -0,0 +1,9 @@
# # UpdateEmployeeFacilityDistanceRequest
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**distance_km** | **float** | | [optional]
[[Back to Model list]](../../README.md#models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md)
@@ -23,6 +23,8 @@ Name | Type | Description | Notes
**sunday_surcharge_percent** | **float** | | [optional] **sunday_surcharge_percent** | **float** | | [optional]
**holiday_surcharge_percent** | **float** | | [optional] **holiday_surcharge_percent** | **float** | | [optional]
**travel_cost_rate** | **float** | | [optional] **travel_cost_rate** | **float** | | [optional]
**travel_cost_mode** | **string** | | [optional]
**travel_cost_per_km** | **float** | | [optional]
**minimum_hours** | **float** | | [optional] **minimum_hours** | **float** | | [optional]
**break_policy** | **string** | | [optional] **break_policy** | **string** | | [optional]
**billing_interval** | **string** | | [optional] **billing_interval** | **string** | | [optional]
@@ -0,0 +1,9 @@
# # UpdateMyFacilityDistanceRequest
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**distance_km** | **float** | | [optional]
[[Back to Model list]](../../README.md#models) [[Back to API list]](../../README.md#endpoints) [[Back to README]](../../README.md)
File diff suppressed because it is too large Load Diff
@@ -86,6 +86,12 @@ class TrashApi
'apiTrashContractsIdRestorePost' => [ 'apiTrashContractsIdRestorePost' => [
'application/json', 'application/json',
], ],
'apiTrashEmployeeFacilityDistancesGet' => [
'application/json',
],
'apiTrashEmployeeFacilityDistancesIdRestorePost' => [
'application/json',
],
'apiTrashEmployeesGet' => [ 'apiTrashEmployeesGet' => [
'application/json', 'application/json',
], ],
@@ -1049,6 +1055,479 @@ class TrashApi
// 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(
'POST',
$operationHost . $resourcePath . ($query ? "?{$query}" : ''),
$headers,
$httpBody
);
}
/**
* Operation apiTrashEmployeeFacilityDistancesGet
*
* @param string|null $search search (optional)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesGet'] 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 \OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]
*/
public function apiTrashEmployeeFacilityDistancesGet($search = null, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesGet'][0])
{
list($response) = $this->apiTrashEmployeeFacilityDistancesGetWithHttpInfo($search, $contentType);
return $response;
}
/**
* Operation apiTrashEmployeeFacilityDistancesGetWithHttpInfo
*
* @param string|null $search (optional)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesGet'] 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 \OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[], HTTP status code, HTTP response headers (array of strings)
*/
public function apiTrashEmployeeFacilityDistancesGetWithHttpInfo($search = null, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesGet'][0])
{
$request = $this->apiTrashEmployeeFacilityDistancesGetRequest($search, $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();
switch($statusCode) {
case 200:
return $this->handleResponseWithDataType(
'\OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]',
$request,
$response,
);
}
if ($statusCode < 200 || $statusCode > 299) {
throw new ApiException(
sprintf(
'[%d] Error connecting to the API (%s)',
$statusCode,
(string) $request->getUri()
),
$statusCode,
$response->getHeaders(),
(string) $response->getBody()
);
}
return $this->handleResponseWithDataType(
'\OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]',
$request,
$response,
);
} catch (ApiException $e) {
switch ($e->getCode()) {
case 200:
$data = ObjectSerializer::deserialize(
$e->getResponseBody(),
'\OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]',
$e->getResponseHeaders()
);
$e->setResponseObject($data);
throw $e;
}
throw $e;
}
}
/**
* Operation apiTrashEmployeeFacilityDistancesGetAsync
*
* @param string|null $search (optional)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesGet'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Promise\PromiseInterface
*/
public function apiTrashEmployeeFacilityDistancesGetAsync($search = null, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesGet'][0])
{
return $this->apiTrashEmployeeFacilityDistancesGetAsyncWithHttpInfo($search, $contentType)
->then(
function ($response) {
return $response[0];
}
);
}
/**
* Operation apiTrashEmployeeFacilityDistancesGetAsyncWithHttpInfo
*
* @param string|null $search (optional)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesGet'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Promise\PromiseInterface
*/
public function apiTrashEmployeeFacilityDistancesGetAsyncWithHttpInfo($search = null, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesGet'][0])
{
$returnType = '\OmsorgCoreClient\Model\TrashEmployeeFacilityDistanceResponse[]';
$request = $this->apiTrashEmployeeFacilityDistancesGetRequest($search, $contentType);
return $this->client
->sendAsync($request, $this->createHttpClientOption())
->then(
function ($response) use ($returnType) {
if ($returnType === '\SplFileObject') {
$content = $response->getBody(); //stream goes to serializer
} else {
$content = (string) $response->getBody();
if ($returnType !== 'string') {
$content = json_decode($content);
}
}
return [
ObjectSerializer::deserialize($content, $returnType, []),
$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 'apiTrashEmployeeFacilityDistancesGet'
*
* @param string|null $search (optional)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesGet'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Psr7\Request
*/
public function apiTrashEmployeeFacilityDistancesGetRequest($search = null, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesGet'][0])
{
$resourcePath = '/api/trash/employee-facility-distances';
$formParams = [];
$queryParams = [];
$headerParams = [];
$httpBody = '';
$multipart = false;
// query params
$queryParams = array_merge($queryParams, ObjectSerializer::toQueryValue(
$search,
'search', // param base name
'string', // openApiType
'form', // style
true, // explode
false // required
) ?? []);
$headers = $this->headerSelector->selectHeaders(
['text/plain', 'application/json', 'text/json', ],
$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(
'GET',
$operationHost . $resourcePath . ($query ? "?{$query}" : ''),
$headers,
$httpBody
);
}
/**
* Operation apiTrashEmployeeFacilityDistancesIdRestorePost
*
* @param string $id id (required)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'] 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 apiTrashEmployeeFacilityDistancesIdRestorePost($id, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'][0])
{
$this->apiTrashEmployeeFacilityDistancesIdRestorePostWithHttpInfo($id, $contentType);
}
/**
* Operation apiTrashEmployeeFacilityDistancesIdRestorePostWithHttpInfo
*
* @param string $id (required)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'] 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 apiTrashEmployeeFacilityDistancesIdRestorePostWithHttpInfo($id, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'][0])
{
$request = $this->apiTrashEmployeeFacilityDistancesIdRestorePostRequest($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 apiTrashEmployeeFacilityDistancesIdRestorePostAsync
*
* @param string $id (required)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Promise\PromiseInterface
*/
public function apiTrashEmployeeFacilityDistancesIdRestorePostAsync($id, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'][0])
{
return $this->apiTrashEmployeeFacilityDistancesIdRestorePostAsyncWithHttpInfo($id, $contentType)
->then(
function ($response) {
return $response[0];
}
);
}
/**
* Operation apiTrashEmployeeFacilityDistancesIdRestorePostAsyncWithHttpInfo
*
* @param string $id (required)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Promise\PromiseInterface
*/
public function apiTrashEmployeeFacilityDistancesIdRestorePostAsyncWithHttpInfo($id, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'][0])
{
$returnType = '';
$request = $this->apiTrashEmployeeFacilityDistancesIdRestorePostRequest($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 'apiTrashEmployeeFacilityDistancesIdRestorePost'
*
* @param string $id (required)
* @param string $contentType The value for the Content-Type header. Check self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'] to see the possible values for this operation
*
* @throws \InvalidArgumentException
* @return \GuzzleHttp\Psr7\Request
*/
public function apiTrashEmployeeFacilityDistancesIdRestorePostRequest($id, string $contentType = self::contentTypes['apiTrashEmployeeFacilityDistancesIdRestorePost'][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 apiTrashEmployeeFacilityDistancesIdRestorePost'
);
}
$resourcePath = '/api/trash/employee-facility-distances/{id}/restore';
$formParams = [];
$queryParams = [];
$headerParams = [];
$httpBody = '';
$multipart = false;
// path params // path params
if ($id !== null) { if ($id !== null) {
$resourcePath = str_replace( $resourcePath = str_replace(
@@ -0,0 +1,443 @@
<?php
/**
* CreateEmployeeFacilityDistanceRequest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
namespace OmsorgCoreClient\Model;
use \ArrayAccess;
use \OmsorgCoreClient\ObjectSerializer;
/**
* CreateEmployeeFacilityDistanceRequest Class Doc Comment
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
* @implements \ArrayAccess<string, mixed>
*/
class CreateEmployeeFacilityDistanceRequest implements ModelInterface, ArrayAccess, \JsonSerializable
{
public const DISCRIMINATOR = null;
/**
* The original name of the model.
*
* @var string
*/
protected static $openAPIModelName = 'CreateEmployeeFacilityDistanceRequest';
/**
* Array of property to type mappings. Used for (de)serialization
*
* @var string[]
*/
protected static $openAPITypes = [
'employee_id' => 'string',
'distance_km' => 'float'
];
/**
* Array of property to format mappings. Used for (de)serialization
*
* @var string[]
* @phpstan-var array<string, string|null>
* @psalm-var array<string, string|null>
*/
protected static $openAPIFormats = [
'employee_id' => 'uuid',
'distance_km' => 'double'
];
/**
* Array of nullable properties. Used for (de)serialization
*
* @var boolean[]
*/
protected static array $openAPINullables = [
'employee_id' => false,
'distance_km' => false
];
/**
* If a nullable field gets set to null, insert it here
*
* @var boolean[]
*/
protected array $openAPINullablesSetToNull = [];
/**
* Array of property to type mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPITypes()
{
return self::$openAPITypes;
}
/**
* Array of property to format mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPIFormats()
{
return self::$openAPIFormats;
}
/**
* Array of nullable properties
*
* @return array
*/
protected static function openAPINullables(): array
{
return self::$openAPINullables;
}
/**
* Array of nullable field names deliberately set to null
*
* @return boolean[]
*/
private function getOpenAPINullablesSetToNull(): array
{
return $this->openAPINullablesSetToNull;
}
/**
* Setter - Array of nullable field names deliberately set to null
*
* @param boolean[] $openAPINullablesSetToNull
*/
private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void
{
$this->openAPINullablesSetToNull = $openAPINullablesSetToNull;
}
/**
* Checks if a property is nullable
*
* @param string $property
* @return bool
*/
public static function isNullable(string $property): bool
{
return self::openAPINullables()[$property] ?? false;
}
/**
* Checks if a nullable property is set to null.
*
* @param string $property
* @return bool
*/
public function isNullableSetToNull(string $property): bool
{
return in_array($property, $this->getOpenAPINullablesSetToNull(), true);
}
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @var string[]
*/
protected static $attributeMap = [
'employee_id' => 'employeeId',
'distance_km' => 'distanceKm'
];
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @var string[]
*/
protected static $setters = [
'employee_id' => 'setEmployeeId',
'distance_km' => 'setDistanceKm'
];
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @var string[]
*/
protected static $getters = [
'employee_id' => 'getEmployeeId',
'distance_km' => 'getDistanceKm'
];
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @return array
*/
public static function attributeMap()
{
return self::$attributeMap;
}
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @return array
*/
public static function setters()
{
return self::$setters;
}
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @return array
*/
public static function getters()
{
return self::$getters;
}
/**
* The original name of the model.
*
* @return string
*/
public function getModelName()
{
return self::$openAPIModelName;
}
/**
* Associative array for storing property values
*
* @var mixed[]
*/
protected $container = [];
/**
* Constructor
*
* @param mixed[]|null $data Associated array of property values
* initializing the model
*/
public function __construct(?array $data = null)
{
$this->setIfExists('employee_id', $data ?? [], null);
$this->setIfExists('distance_km', $data ?? [], null);
}
/**
* Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName
* is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the
* $this->openAPINullablesSetToNull array
*
* @param string $variableName
* @param array $fields
* @param mixed $defaultValue
*/
private function setIfExists(string $variableName, array $fields, $defaultValue): void
{
if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) {
$this->openAPINullablesSetToNull[] = $variableName;
}
$this->container[$variableName] = $fields[$variableName] ?? $defaultValue;
}
/**
* Show all the invalid properties with reasons.
*
* @return array invalid properties with reasons
*/
public function listInvalidProperties()
{
$invalidProperties = [];
return $invalidProperties;
}
/**
* Validate all the properties in the model
* return true if all passed
*
* @return bool True if all properties are valid
*/
public function valid()
{
return count($this->listInvalidProperties()) === 0;
}
/**
* Gets employee_id
*
* @return string|null
*/
public function getEmployeeId()
{
return $this->container['employee_id'];
}
/**
* Sets employee_id
*
* @param string|null $employee_id employee_id
*
* @return self
*/
public function setEmployeeId($employee_id)
{
if (is_null($employee_id)) {
throw new \InvalidArgumentException('non-nullable employee_id cannot be null');
}
$this->container['employee_id'] = $employee_id;
return $this;
}
/**
* Gets distance_km
*
* @return float|null
*/
public function getDistanceKm()
{
return $this->container['distance_km'];
}
/**
* Sets distance_km
*
* @param float|null $distance_km distance_km
*
* @return self
*/
public function setDistanceKm($distance_km)
{
if (is_null($distance_km)) {
throw new \InvalidArgumentException('non-nullable distance_km cannot be null');
}
$this->container['distance_km'] = $distance_km;
return $this;
}
/**
* Returns true if offset exists. False otherwise.
*
* @param integer $offset Offset
*
* @return boolean
*/
public function offsetExists($offset): bool
{
return isset($this->container[$offset]);
}
/**
* Gets offset.
*
* @param integer $offset Offset
*
* @return mixed|null
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->container[$offset] ?? null;
}
/**
* Sets value based on offset.
*
* @param int|null $offset Offset
* @param mixed $value Value to be set
*
* @return void
*/
public function offsetSet($offset, $value): void
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
/**
* Unsets offset.
*
* @param integer $offset Offset
*
* @return void
*/
public function offsetUnset($offset): void
{
unset($this->container[$offset]);
}
/**
* Serializes the object to a value that can be serialized natively by json_encode().
* @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed Returns data which can be serialized by json_encode(), which is a value
* of any type other than a resource.
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return ObjectSerializer::sanitizeForSerialization($this);
}
/**
* Gets the string presentation of the object
*
* @return string
*/
public function __toString()
{
return json_encode(
ObjectSerializer::sanitizeForSerialization($this),
JSON_PRETTY_PRINT
);
}
/**
* Gets a header-safe presentation of the object
*
* @return string
*/
public function toHeaderValue()
{
return json_encode(ObjectSerializer::sanitizeForSerialization($this));
}
}
@@ -0,0 +1,443 @@
<?php
/**
* CreateMyFacilityDistanceRequest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
namespace OmsorgCoreClient\Model;
use \ArrayAccess;
use \OmsorgCoreClient\ObjectSerializer;
/**
* CreateMyFacilityDistanceRequest Class Doc Comment
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
* @implements \ArrayAccess<string, mixed>
*/
class CreateMyFacilityDistanceRequest implements ModelInterface, ArrayAccess, \JsonSerializable
{
public const DISCRIMINATOR = null;
/**
* The original name of the model.
*
* @var string
*/
protected static $openAPIModelName = 'CreateMyFacilityDistanceRequest';
/**
* Array of property to type mappings. Used for (de)serialization
*
* @var string[]
*/
protected static $openAPITypes = [
'facility_id' => 'string',
'distance_km' => 'float'
];
/**
* Array of property to format mappings. Used for (de)serialization
*
* @var string[]
* @phpstan-var array<string, string|null>
* @psalm-var array<string, string|null>
*/
protected static $openAPIFormats = [
'facility_id' => 'uuid',
'distance_km' => 'double'
];
/**
* Array of nullable properties. Used for (de)serialization
*
* @var boolean[]
*/
protected static array $openAPINullables = [
'facility_id' => false,
'distance_km' => false
];
/**
* If a nullable field gets set to null, insert it here
*
* @var boolean[]
*/
protected array $openAPINullablesSetToNull = [];
/**
* Array of property to type mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPITypes()
{
return self::$openAPITypes;
}
/**
* Array of property to format mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPIFormats()
{
return self::$openAPIFormats;
}
/**
* Array of nullable properties
*
* @return array
*/
protected static function openAPINullables(): array
{
return self::$openAPINullables;
}
/**
* Array of nullable field names deliberately set to null
*
* @return boolean[]
*/
private function getOpenAPINullablesSetToNull(): array
{
return $this->openAPINullablesSetToNull;
}
/**
* Setter - Array of nullable field names deliberately set to null
*
* @param boolean[] $openAPINullablesSetToNull
*/
private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void
{
$this->openAPINullablesSetToNull = $openAPINullablesSetToNull;
}
/**
* Checks if a property is nullable
*
* @param string $property
* @return bool
*/
public static function isNullable(string $property): bool
{
return self::openAPINullables()[$property] ?? false;
}
/**
* Checks if a nullable property is set to null.
*
* @param string $property
* @return bool
*/
public function isNullableSetToNull(string $property): bool
{
return in_array($property, $this->getOpenAPINullablesSetToNull(), true);
}
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @var string[]
*/
protected static $attributeMap = [
'facility_id' => 'facilityId',
'distance_km' => 'distanceKm'
];
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @var string[]
*/
protected static $setters = [
'facility_id' => 'setFacilityId',
'distance_km' => 'setDistanceKm'
];
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @var string[]
*/
protected static $getters = [
'facility_id' => 'getFacilityId',
'distance_km' => 'getDistanceKm'
];
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @return array
*/
public static function attributeMap()
{
return self::$attributeMap;
}
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @return array
*/
public static function setters()
{
return self::$setters;
}
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @return array
*/
public static function getters()
{
return self::$getters;
}
/**
* The original name of the model.
*
* @return string
*/
public function getModelName()
{
return self::$openAPIModelName;
}
/**
* Associative array for storing property values
*
* @var mixed[]
*/
protected $container = [];
/**
* Constructor
*
* @param mixed[]|null $data Associated array of property values
* initializing the model
*/
public function __construct(?array $data = null)
{
$this->setIfExists('facility_id', $data ?? [], null);
$this->setIfExists('distance_km', $data ?? [], null);
}
/**
* Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName
* is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the
* $this->openAPINullablesSetToNull array
*
* @param string $variableName
* @param array $fields
* @param mixed $defaultValue
*/
private function setIfExists(string $variableName, array $fields, $defaultValue): void
{
if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) {
$this->openAPINullablesSetToNull[] = $variableName;
}
$this->container[$variableName] = $fields[$variableName] ?? $defaultValue;
}
/**
* Show all the invalid properties with reasons.
*
* @return array invalid properties with reasons
*/
public function listInvalidProperties()
{
$invalidProperties = [];
return $invalidProperties;
}
/**
* Validate all the properties in the model
* return true if all passed
*
* @return bool True if all properties are valid
*/
public function valid()
{
return count($this->listInvalidProperties()) === 0;
}
/**
* Gets facility_id
*
* @return string|null
*/
public function getFacilityId()
{
return $this->container['facility_id'];
}
/**
* Sets facility_id
*
* @param string|null $facility_id facility_id
*
* @return self
*/
public function setFacilityId($facility_id)
{
if (is_null($facility_id)) {
throw new \InvalidArgumentException('non-nullable facility_id cannot be null');
}
$this->container['facility_id'] = $facility_id;
return $this;
}
/**
* Gets distance_km
*
* @return float|null
*/
public function getDistanceKm()
{
return $this->container['distance_km'];
}
/**
* Sets distance_km
*
* @param float|null $distance_km distance_km
*
* @return self
*/
public function setDistanceKm($distance_km)
{
if (is_null($distance_km)) {
throw new \InvalidArgumentException('non-nullable distance_km cannot be null');
}
$this->container['distance_km'] = $distance_km;
return $this;
}
/**
* Returns true if offset exists. False otherwise.
*
* @param integer $offset Offset
*
* @return boolean
*/
public function offsetExists($offset): bool
{
return isset($this->container[$offset]);
}
/**
* Gets offset.
*
* @param integer $offset Offset
*
* @return mixed|null
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->container[$offset] ?? null;
}
/**
* Sets value based on offset.
*
* @param int|null $offset Offset
* @param mixed $value Value to be set
*
* @return void
*/
public function offsetSet($offset, $value): void
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
/**
* Unsets offset.
*
* @param integer $offset Offset
*
* @return void
*/
public function offsetUnset($offset): void
{
unset($this->container[$offset]);
}
/**
* Serializes the object to a value that can be serialized natively by json_encode().
* @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed Returns data which can be serialized by json_encode(), which is a value
* of any type other than a resource.
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return ObjectSerializer::sanitizeForSerialization($this);
}
/**
* Gets the string presentation of the object
*
* @return string
*/
public function __toString()
{
return json_encode(
ObjectSerializer::sanitizeForSerialization($this),
JSON_PRETTY_PRINT
);
}
/**
* Gets a header-safe presentation of the object
*
* @return string
*/
public function toHeaderValue()
{
return json_encode(ObjectSerializer::sanitizeForSerialization($this));
}
}
@@ -0,0 +1,593 @@
<?php
/**
* EmployeeFacilityDistanceResponse
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
namespace OmsorgCoreClient\Model;
use \ArrayAccess;
use \OmsorgCoreClient\ObjectSerializer;
/**
* EmployeeFacilityDistanceResponse Class Doc Comment
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
* @implements \ArrayAccess<string, mixed>
*/
class EmployeeFacilityDistanceResponse implements ModelInterface, ArrayAccess, \JsonSerializable
{
public const DISCRIMINATOR = null;
/**
* The original name of the model.
*
* @var string
*/
protected static $openAPIModelName = 'EmployeeFacilityDistanceResponse';
/**
* Array of property to type mappings. Used for (de)serialization
*
* @var string[]
*/
protected static $openAPITypes = [
'id' => 'string',
'facility_id' => 'string',
'employee_id' => 'string',
'employee_first_name' => 'string',
'employee_last_name' => 'string',
'distance_km' => 'float'
];
/**
* Array of property to format mappings. Used for (de)serialization
*
* @var string[]
* @phpstan-var array<string, string|null>
* @psalm-var array<string, string|null>
*/
protected static $openAPIFormats = [
'id' => 'uuid',
'facility_id' => 'uuid',
'employee_id' => 'uuid',
'employee_first_name' => null,
'employee_last_name' => null,
'distance_km' => 'double'
];
/**
* Array of nullable properties. Used for (de)serialization
*
* @var boolean[]
*/
protected static array $openAPINullables = [
'id' => false,
'facility_id' => false,
'employee_id' => false,
'employee_first_name' => true,
'employee_last_name' => true,
'distance_km' => false
];
/**
* If a nullable field gets set to null, insert it here
*
* @var boolean[]
*/
protected array $openAPINullablesSetToNull = [];
/**
* Array of property to type mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPITypes()
{
return self::$openAPITypes;
}
/**
* Array of property to format mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPIFormats()
{
return self::$openAPIFormats;
}
/**
* Array of nullable properties
*
* @return array
*/
protected static function openAPINullables(): array
{
return self::$openAPINullables;
}
/**
* Array of nullable field names deliberately set to null
*
* @return boolean[]
*/
private function getOpenAPINullablesSetToNull(): array
{
return $this->openAPINullablesSetToNull;
}
/**
* Setter - Array of nullable field names deliberately set to null
*
* @param boolean[] $openAPINullablesSetToNull
*/
private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void
{
$this->openAPINullablesSetToNull = $openAPINullablesSetToNull;
}
/**
* Checks if a property is nullable
*
* @param string $property
* @return bool
*/
public static function isNullable(string $property): bool
{
return self::openAPINullables()[$property] ?? false;
}
/**
* Checks if a nullable property is set to null.
*
* @param string $property
* @return bool
*/
public function isNullableSetToNull(string $property): bool
{
return in_array($property, $this->getOpenAPINullablesSetToNull(), true);
}
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @var string[]
*/
protected static $attributeMap = [
'id' => 'id',
'facility_id' => 'facilityId',
'employee_id' => 'employeeId',
'employee_first_name' => 'employeeFirstName',
'employee_last_name' => 'employeeLastName',
'distance_km' => 'distanceKm'
];
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @var string[]
*/
protected static $setters = [
'id' => 'setId',
'facility_id' => 'setFacilityId',
'employee_id' => 'setEmployeeId',
'employee_first_name' => 'setEmployeeFirstName',
'employee_last_name' => 'setEmployeeLastName',
'distance_km' => 'setDistanceKm'
];
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @var string[]
*/
protected static $getters = [
'id' => 'getId',
'facility_id' => 'getFacilityId',
'employee_id' => 'getEmployeeId',
'employee_first_name' => 'getEmployeeFirstName',
'employee_last_name' => 'getEmployeeLastName',
'distance_km' => 'getDistanceKm'
];
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @return array
*/
public static function attributeMap()
{
return self::$attributeMap;
}
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @return array
*/
public static function setters()
{
return self::$setters;
}
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @return array
*/
public static function getters()
{
return self::$getters;
}
/**
* The original name of the model.
*
* @return string
*/
public function getModelName()
{
return self::$openAPIModelName;
}
/**
* Associative array for storing property values
*
* @var mixed[]
*/
protected $container = [];
/**
* Constructor
*
* @param mixed[]|null $data Associated array of property values
* initializing the model
*/
public function __construct(?array $data = null)
{
$this->setIfExists('id', $data ?? [], null);
$this->setIfExists('facility_id', $data ?? [], null);
$this->setIfExists('employee_id', $data ?? [], null);
$this->setIfExists('employee_first_name', $data ?? [], null);
$this->setIfExists('employee_last_name', $data ?? [], null);
$this->setIfExists('distance_km', $data ?? [], null);
}
/**
* Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName
* is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the
* $this->openAPINullablesSetToNull array
*
* @param string $variableName
* @param array $fields
* @param mixed $defaultValue
*/
private function setIfExists(string $variableName, array $fields, $defaultValue): void
{
if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) {
$this->openAPINullablesSetToNull[] = $variableName;
}
$this->container[$variableName] = $fields[$variableName] ?? $defaultValue;
}
/**
* Show all the invalid properties with reasons.
*
* @return array invalid properties with reasons
*/
public function listInvalidProperties()
{
$invalidProperties = [];
return $invalidProperties;
}
/**
* Validate all the properties in the model
* return true if all passed
*
* @return bool True if all properties are valid
*/
public function valid()
{
return count($this->listInvalidProperties()) === 0;
}
/**
* Gets id
*
* @return string|null
*/
public function getId()
{
return $this->container['id'];
}
/**
* Sets id
*
* @param string|null $id id
*
* @return self
*/
public function setId($id)
{
if (is_null($id)) {
throw new \InvalidArgumentException('non-nullable id cannot be null');
}
$this->container['id'] = $id;
return $this;
}
/**
* Gets facility_id
*
* @return string|null
*/
public function getFacilityId()
{
return $this->container['facility_id'];
}
/**
* Sets facility_id
*
* @param string|null $facility_id facility_id
*
* @return self
*/
public function setFacilityId($facility_id)
{
if (is_null($facility_id)) {
throw new \InvalidArgumentException('non-nullable facility_id cannot be null');
}
$this->container['facility_id'] = $facility_id;
return $this;
}
/**
* Gets employee_id
*
* @return string|null
*/
public function getEmployeeId()
{
return $this->container['employee_id'];
}
/**
* Sets employee_id
*
* @param string|null $employee_id employee_id
*
* @return self
*/
public function setEmployeeId($employee_id)
{
if (is_null($employee_id)) {
throw new \InvalidArgumentException('non-nullable employee_id cannot be null');
}
$this->container['employee_id'] = $employee_id;
return $this;
}
/**
* Gets employee_first_name
*
* @return string|null
*/
public function getEmployeeFirstName()
{
return $this->container['employee_first_name'];
}
/**
* Sets employee_first_name
*
* @param string|null $employee_first_name employee_first_name
*
* @return self
*/
public function setEmployeeFirstName($employee_first_name)
{
if (is_null($employee_first_name)) {
array_push($this->openAPINullablesSetToNull, 'employee_first_name');
} else {
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
$index = array_search('employee_first_name', $nullablesSetToNull);
if ($index !== FALSE) {
unset($nullablesSetToNull[$index]);
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
}
}
$this->container['employee_first_name'] = $employee_first_name;
return $this;
}
/**
* Gets employee_last_name
*
* @return string|null
*/
public function getEmployeeLastName()
{
return $this->container['employee_last_name'];
}
/**
* Sets employee_last_name
*
* @param string|null $employee_last_name employee_last_name
*
* @return self
*/
public function setEmployeeLastName($employee_last_name)
{
if (is_null($employee_last_name)) {
array_push($this->openAPINullablesSetToNull, 'employee_last_name');
} else {
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
$index = array_search('employee_last_name', $nullablesSetToNull);
if ($index !== FALSE) {
unset($nullablesSetToNull[$index]);
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
}
}
$this->container['employee_last_name'] = $employee_last_name;
return $this;
}
/**
* Gets distance_km
*
* @return float|null
*/
public function getDistanceKm()
{
return $this->container['distance_km'];
}
/**
* Sets distance_km
*
* @param float|null $distance_km distance_km
*
* @return self
*/
public function setDistanceKm($distance_km)
{
if (is_null($distance_km)) {
throw new \InvalidArgumentException('non-nullable distance_km cannot be null');
}
$this->container['distance_km'] = $distance_km;
return $this;
}
/**
* Returns true if offset exists. False otherwise.
*
* @param integer $offset Offset
*
* @return boolean
*/
public function offsetExists($offset): bool
{
return isset($this->container[$offset]);
}
/**
* Gets offset.
*
* @param integer $offset Offset
*
* @return mixed|null
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->container[$offset] ?? null;
}
/**
* Sets value based on offset.
*
* @param int|null $offset Offset
* @param mixed $value Value to be set
*
* @return void
*/
public function offsetSet($offset, $value): void
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
/**
* Unsets offset.
*
* @param integer $offset Offset
*
* @return void
*/
public function offsetUnset($offset): void
{
unset($this->container[$offset]);
}
/**
* Serializes the object to a value that can be serialized natively by json_encode().
* @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed Returns data which can be serialized by json_encode(), which is a value
* of any type other than a resource.
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return ObjectSerializer::sanitizeForSerialization($this);
}
/**
* Gets the string presentation of the object
*
* @return string
*/
public function __toString()
{
return json_encode(
ObjectSerializer::sanitizeForSerialization($this),
JSON_PRETTY_PRINT
);
}
/**
* Gets a header-safe presentation of the object
*
* @return string
*/
public function toHeaderValue()
{
return json_encode(ObjectSerializer::sanitizeForSerialization($this));
}
}
@@ -0,0 +1,450 @@
<?php
/**
* FacilityOptionResponse
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
namespace OmsorgCoreClient\Model;
use \ArrayAccess;
use \OmsorgCoreClient\ObjectSerializer;
/**
* FacilityOptionResponse Class Doc Comment
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
* @implements \ArrayAccess<string, mixed>
*/
class FacilityOptionResponse implements ModelInterface, ArrayAccess, \JsonSerializable
{
public const DISCRIMINATOR = null;
/**
* The original name of the model.
*
* @var string
*/
protected static $openAPIModelName = 'FacilityOptionResponse';
/**
* Array of property to type mappings. Used for (de)serialization
*
* @var string[]
*/
protected static $openAPITypes = [
'id' => 'string',
'name' => 'string'
];
/**
* Array of property to format mappings. Used for (de)serialization
*
* @var string[]
* @phpstan-var array<string, string|null>
* @psalm-var array<string, string|null>
*/
protected static $openAPIFormats = [
'id' => 'uuid',
'name' => null
];
/**
* Array of nullable properties. Used for (de)serialization
*
* @var boolean[]
*/
protected static array $openAPINullables = [
'id' => false,
'name' => true
];
/**
* If a nullable field gets set to null, insert it here
*
* @var boolean[]
*/
protected array $openAPINullablesSetToNull = [];
/**
* Array of property to type mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPITypes()
{
return self::$openAPITypes;
}
/**
* Array of property to format mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPIFormats()
{
return self::$openAPIFormats;
}
/**
* Array of nullable properties
*
* @return array
*/
protected static function openAPINullables(): array
{
return self::$openAPINullables;
}
/**
* Array of nullable field names deliberately set to null
*
* @return boolean[]
*/
private function getOpenAPINullablesSetToNull(): array
{
return $this->openAPINullablesSetToNull;
}
/**
* Setter - Array of nullable field names deliberately set to null
*
* @param boolean[] $openAPINullablesSetToNull
*/
private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void
{
$this->openAPINullablesSetToNull = $openAPINullablesSetToNull;
}
/**
* Checks if a property is nullable
*
* @param string $property
* @return bool
*/
public static function isNullable(string $property): bool
{
return self::openAPINullables()[$property] ?? false;
}
/**
* Checks if a nullable property is set to null.
*
* @param string $property
* @return bool
*/
public function isNullableSetToNull(string $property): bool
{
return in_array($property, $this->getOpenAPINullablesSetToNull(), true);
}
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @var string[]
*/
protected static $attributeMap = [
'id' => 'id',
'name' => 'name'
];
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @var string[]
*/
protected static $setters = [
'id' => 'setId',
'name' => 'setName'
];
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @var string[]
*/
protected static $getters = [
'id' => 'getId',
'name' => 'getName'
];
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @return array
*/
public static function attributeMap()
{
return self::$attributeMap;
}
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @return array
*/
public static function setters()
{
return self::$setters;
}
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @return array
*/
public static function getters()
{
return self::$getters;
}
/**
* The original name of the model.
*
* @return string
*/
public function getModelName()
{
return self::$openAPIModelName;
}
/**
* Associative array for storing property values
*
* @var mixed[]
*/
protected $container = [];
/**
* Constructor
*
* @param mixed[]|null $data Associated array of property values
* initializing the model
*/
public function __construct(?array $data = null)
{
$this->setIfExists('id', $data ?? [], null);
$this->setIfExists('name', $data ?? [], null);
}
/**
* Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName
* is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the
* $this->openAPINullablesSetToNull array
*
* @param string $variableName
* @param array $fields
* @param mixed $defaultValue
*/
private function setIfExists(string $variableName, array $fields, $defaultValue): void
{
if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) {
$this->openAPINullablesSetToNull[] = $variableName;
}
$this->container[$variableName] = $fields[$variableName] ?? $defaultValue;
}
/**
* Show all the invalid properties with reasons.
*
* @return array invalid properties with reasons
*/
public function listInvalidProperties()
{
$invalidProperties = [];
return $invalidProperties;
}
/**
* Validate all the properties in the model
* return true if all passed
*
* @return bool True if all properties are valid
*/
public function valid()
{
return count($this->listInvalidProperties()) === 0;
}
/**
* Gets id
*
* @return string|null
*/
public function getId()
{
return $this->container['id'];
}
/**
* Sets id
*
* @param string|null $id id
*
* @return self
*/
public function setId($id)
{
if (is_null($id)) {
throw new \InvalidArgumentException('non-nullable id cannot be null');
}
$this->container['id'] = $id;
return $this;
}
/**
* Gets name
*
* @return string|null
*/
public function getName()
{
return $this->container['name'];
}
/**
* Sets name
*
* @param string|null $name name
*
* @return self
*/
public function setName($name)
{
if (is_null($name)) {
array_push($this->openAPINullablesSetToNull, 'name');
} else {
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
$index = array_search('name', $nullablesSetToNull);
if ($index !== FALSE) {
unset($nullablesSetToNull[$index]);
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
}
}
$this->container['name'] = $name;
return $this;
}
/**
* Returns true if offset exists. False otherwise.
*
* @param integer $offset Offset
*
* @return boolean
*/
public function offsetExists($offset): bool
{
return isset($this->container[$offset]);
}
/**
* Gets offset.
*
* @param integer $offset Offset
*
* @return mixed|null
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->container[$offset] ?? null;
}
/**
* Sets value based on offset.
*
* @param int|null $offset Offset
* @param mixed $value Value to be set
*
* @return void
*/
public function offsetSet($offset, $value): void
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
/**
* Unsets offset.
*
* @param integer $offset Offset
*
* @return void
*/
public function offsetUnset($offset): void
{
unset($this->container[$offset]);
}
/**
* Serializes the object to a value that can be serialized natively by json_encode().
* @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed Returns data which can be serialized by json_encode(), which is a value
* of any type other than a resource.
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return ObjectSerializer::sanitizeForSerialization($this);
}
/**
* Gets the string presentation of the object
*
* @return string
*/
public function __toString()
{
return json_encode(
ObjectSerializer::sanitizeForSerialization($this),
JSON_PRETTY_PRINT
);
}
/**
* Gets a header-safe presentation of the object
*
* @return string
*/
public function toHeaderValue()
{
return json_encode(ObjectSerializer::sanitizeForSerialization($this));
}
}
@@ -77,6 +77,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
'sunday_surcharge_percent' => 'float', 'sunday_surcharge_percent' => 'float',
'holiday_surcharge_percent' => 'float', 'holiday_surcharge_percent' => 'float',
'travel_cost_rate' => 'float', 'travel_cost_rate' => 'float',
'travel_cost_mode' => 'string',
'travel_cost_per_km' => 'float',
'minimum_hours' => 'float', 'minimum_hours' => 'float',
'break_policy' => 'string', 'break_policy' => 'string',
'billing_interval' => 'string', 'billing_interval' => 'string',
@@ -112,6 +114,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
'sunday_surcharge_percent' => 'double', 'sunday_surcharge_percent' => 'double',
'holiday_surcharge_percent' => 'double', 'holiday_surcharge_percent' => 'double',
'travel_cost_rate' => 'double', 'travel_cost_rate' => 'double',
'travel_cost_mode' => null,
'travel_cost_per_km' => 'double',
'minimum_hours' => 'double', 'minimum_hours' => 'double',
'break_policy' => null, 'break_policy' => null,
'billing_interval' => null, 'billing_interval' => null,
@@ -145,6 +149,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
'sunday_surcharge_percent' => true, 'sunday_surcharge_percent' => true,
'holiday_surcharge_percent' => true, 'holiday_surcharge_percent' => true,
'travel_cost_rate' => true, 'travel_cost_rate' => true,
'travel_cost_mode' => true,
'travel_cost_per_km' => true,
'minimum_hours' => true, 'minimum_hours' => true,
'break_policy' => true, 'break_policy' => true,
'billing_interval' => true, 'billing_interval' => true,
@@ -258,6 +264,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
'sunday_surcharge_percent' => 'sundaySurchargePercent', 'sunday_surcharge_percent' => 'sundaySurchargePercent',
'holiday_surcharge_percent' => 'holidaySurchargePercent', 'holiday_surcharge_percent' => 'holidaySurchargePercent',
'travel_cost_rate' => 'travelCostRate', 'travel_cost_rate' => 'travelCostRate',
'travel_cost_mode' => 'travelCostMode',
'travel_cost_per_km' => 'travelCostPerKm',
'minimum_hours' => 'minimumHours', 'minimum_hours' => 'minimumHours',
'break_policy' => 'breakPolicy', 'break_policy' => 'breakPolicy',
'billing_interval' => 'billingInterval', 'billing_interval' => 'billingInterval',
@@ -291,6 +299,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
'sunday_surcharge_percent' => 'setSundaySurchargePercent', 'sunday_surcharge_percent' => 'setSundaySurchargePercent',
'holiday_surcharge_percent' => 'setHolidaySurchargePercent', 'holiday_surcharge_percent' => 'setHolidaySurchargePercent',
'travel_cost_rate' => 'setTravelCostRate', 'travel_cost_rate' => 'setTravelCostRate',
'travel_cost_mode' => 'setTravelCostMode',
'travel_cost_per_km' => 'setTravelCostPerKm',
'minimum_hours' => 'setMinimumHours', 'minimum_hours' => 'setMinimumHours',
'break_policy' => 'setBreakPolicy', 'break_policy' => 'setBreakPolicy',
'billing_interval' => 'setBillingInterval', 'billing_interval' => 'setBillingInterval',
@@ -324,6 +334,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
'sunday_surcharge_percent' => 'getSundaySurchargePercent', 'sunday_surcharge_percent' => 'getSundaySurchargePercent',
'holiday_surcharge_percent' => 'getHolidaySurchargePercent', 'holiday_surcharge_percent' => 'getHolidaySurchargePercent',
'travel_cost_rate' => 'getTravelCostRate', 'travel_cost_rate' => 'getTravelCostRate',
'travel_cost_mode' => 'getTravelCostMode',
'travel_cost_per_km' => 'getTravelCostPerKm',
'minimum_hours' => 'getMinimumHours', 'minimum_hours' => 'getMinimumHours',
'break_policy' => 'getBreakPolicy', 'break_policy' => 'getBreakPolicy',
'billing_interval' => 'getBillingInterval', 'billing_interval' => 'getBillingInterval',
@@ -408,6 +420,8 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
$this->setIfExists('sunday_surcharge_percent', $data ?? [], null); $this->setIfExists('sunday_surcharge_percent', $data ?? [], null);
$this->setIfExists('holiday_surcharge_percent', $data ?? [], null); $this->setIfExists('holiday_surcharge_percent', $data ?? [], null);
$this->setIfExists('travel_cost_rate', $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('minimum_hours', $data ?? [], null); $this->setIfExists('minimum_hours', $data ?? [], null);
$this->setIfExists('break_policy', $data ?? [], null); $this->setIfExists('break_policy', $data ?? [], null);
$this->setIfExists('billing_interval', $data ?? [], null); $this->setIfExists('billing_interval', $data ?? [], null);
@@ -1130,6 +1144,74 @@ class FacilityResponse implements ModelInterface, ArrayAccess, \JsonSerializable
return $this; 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 minimum_hours * Gets minimum_hours
* *
@@ -70,6 +70,8 @@ class ModuleType
public const ABSENCES = 'Absences'; public const ABSENCES = 'Absences';
public const EMPLOYEE_FACILITY_DISTANCES = 'EmployeeFacilityDistances';
/** /**
* Gets allowable values of the enum * Gets allowable values of the enum
* @return string[] * @return string[]
@@ -90,7 +92,8 @@ class ModuleType
self::DOCUMENTS, self::DOCUMENTS,
self::USERS, self::USERS,
self::CONFIGURATION, self::CONFIGURATION,
self::ABSENCES self::ABSENCES,
self::EMPLOYEE_FACILITY_DISTANCES
]; ];
} }
} }
@@ -0,0 +1,518 @@
<?php
/**
* MyFacilityDistanceResponse
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
namespace OmsorgCoreClient\Model;
use \ArrayAccess;
use \OmsorgCoreClient\ObjectSerializer;
/**
* MyFacilityDistanceResponse Class Doc Comment
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
* @implements \ArrayAccess<string, mixed>
*/
class MyFacilityDistanceResponse implements ModelInterface, ArrayAccess, \JsonSerializable
{
public const DISCRIMINATOR = null;
/**
* The original name of the model.
*
* @var string
*/
protected static $openAPIModelName = 'MyFacilityDistanceResponse';
/**
* Array of property to type mappings. Used for (de)serialization
*
* @var string[]
*/
protected static $openAPITypes = [
'id' => 'string',
'facility_id' => 'string',
'facility_name' => 'string',
'distance_km' => 'float'
];
/**
* Array of property to format mappings. Used for (de)serialization
*
* @var string[]
* @phpstan-var array<string, string|null>
* @psalm-var array<string, string|null>
*/
protected static $openAPIFormats = [
'id' => 'uuid',
'facility_id' => 'uuid',
'facility_name' => null,
'distance_km' => 'double'
];
/**
* Array of nullable properties. Used for (de)serialization
*
* @var boolean[]
*/
protected static array $openAPINullables = [
'id' => false,
'facility_id' => false,
'facility_name' => true,
'distance_km' => false
];
/**
* If a nullable field gets set to null, insert it here
*
* @var boolean[]
*/
protected array $openAPINullablesSetToNull = [];
/**
* Array of property to type mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPITypes()
{
return self::$openAPITypes;
}
/**
* Array of property to format mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPIFormats()
{
return self::$openAPIFormats;
}
/**
* Array of nullable properties
*
* @return array
*/
protected static function openAPINullables(): array
{
return self::$openAPINullables;
}
/**
* Array of nullable field names deliberately set to null
*
* @return boolean[]
*/
private function getOpenAPINullablesSetToNull(): array
{
return $this->openAPINullablesSetToNull;
}
/**
* Setter - Array of nullable field names deliberately set to null
*
* @param boolean[] $openAPINullablesSetToNull
*/
private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void
{
$this->openAPINullablesSetToNull = $openAPINullablesSetToNull;
}
/**
* Checks if a property is nullable
*
* @param string $property
* @return bool
*/
public static function isNullable(string $property): bool
{
return self::openAPINullables()[$property] ?? false;
}
/**
* Checks if a nullable property is set to null.
*
* @param string $property
* @return bool
*/
public function isNullableSetToNull(string $property): bool
{
return in_array($property, $this->getOpenAPINullablesSetToNull(), true);
}
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @var string[]
*/
protected static $attributeMap = [
'id' => 'id',
'facility_id' => 'facilityId',
'facility_name' => 'facilityName',
'distance_km' => 'distanceKm'
];
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @var string[]
*/
protected static $setters = [
'id' => 'setId',
'facility_id' => 'setFacilityId',
'facility_name' => 'setFacilityName',
'distance_km' => 'setDistanceKm'
];
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @var string[]
*/
protected static $getters = [
'id' => 'getId',
'facility_id' => 'getFacilityId',
'facility_name' => 'getFacilityName',
'distance_km' => 'getDistanceKm'
];
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @return array
*/
public static function attributeMap()
{
return self::$attributeMap;
}
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @return array
*/
public static function setters()
{
return self::$setters;
}
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @return array
*/
public static function getters()
{
return self::$getters;
}
/**
* The original name of the model.
*
* @return string
*/
public function getModelName()
{
return self::$openAPIModelName;
}
/**
* Associative array for storing property values
*
* @var mixed[]
*/
protected $container = [];
/**
* Constructor
*
* @param mixed[]|null $data Associated array of property values
* initializing the model
*/
public function __construct(?array $data = null)
{
$this->setIfExists('id', $data ?? [], null);
$this->setIfExists('facility_id', $data ?? [], null);
$this->setIfExists('facility_name', $data ?? [], null);
$this->setIfExists('distance_km', $data ?? [], null);
}
/**
* Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName
* is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the
* $this->openAPINullablesSetToNull array
*
* @param string $variableName
* @param array $fields
* @param mixed $defaultValue
*/
private function setIfExists(string $variableName, array $fields, $defaultValue): void
{
if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) {
$this->openAPINullablesSetToNull[] = $variableName;
}
$this->container[$variableName] = $fields[$variableName] ?? $defaultValue;
}
/**
* Show all the invalid properties with reasons.
*
* @return array invalid properties with reasons
*/
public function listInvalidProperties()
{
$invalidProperties = [];
return $invalidProperties;
}
/**
* Validate all the properties in the model
* return true if all passed
*
* @return bool True if all properties are valid
*/
public function valid()
{
return count($this->listInvalidProperties()) === 0;
}
/**
* Gets id
*
* @return string|null
*/
public function getId()
{
return $this->container['id'];
}
/**
* Sets id
*
* @param string|null $id id
*
* @return self
*/
public function setId($id)
{
if (is_null($id)) {
throw new \InvalidArgumentException('non-nullable id cannot be null');
}
$this->container['id'] = $id;
return $this;
}
/**
* Gets facility_id
*
* @return string|null
*/
public function getFacilityId()
{
return $this->container['facility_id'];
}
/**
* Sets facility_id
*
* @param string|null $facility_id facility_id
*
* @return self
*/
public function setFacilityId($facility_id)
{
if (is_null($facility_id)) {
throw new \InvalidArgumentException('non-nullable facility_id cannot be null');
}
$this->container['facility_id'] = $facility_id;
return $this;
}
/**
* Gets facility_name
*
* @return string|null
*/
public function getFacilityName()
{
return $this->container['facility_name'];
}
/**
* Sets facility_name
*
* @param string|null $facility_name facility_name
*
* @return self
*/
public function setFacilityName($facility_name)
{
if (is_null($facility_name)) {
array_push($this->openAPINullablesSetToNull, 'facility_name');
} else {
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
$index = array_search('facility_name', $nullablesSetToNull);
if ($index !== FALSE) {
unset($nullablesSetToNull[$index]);
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
}
}
$this->container['facility_name'] = $facility_name;
return $this;
}
/**
* Gets distance_km
*
* @return float|null
*/
public function getDistanceKm()
{
return $this->container['distance_km'];
}
/**
* Sets distance_km
*
* @param float|null $distance_km distance_km
*
* @return self
*/
public function setDistanceKm($distance_km)
{
if (is_null($distance_km)) {
throw new \InvalidArgumentException('non-nullable distance_km cannot be null');
}
$this->container['distance_km'] = $distance_km;
return $this;
}
/**
* Returns true if offset exists. False otherwise.
*
* @param integer $offset Offset
*
* @return boolean
*/
public function offsetExists($offset): bool
{
return isset($this->container[$offset]);
}
/**
* Gets offset.
*
* @param integer $offset Offset
*
* @return mixed|null
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->container[$offset] ?? null;
}
/**
* Sets value based on offset.
*
* @param int|null $offset Offset
* @param mixed $value Value to be set
*
* @return void
*/
public function offsetSet($offset, $value): void
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
/**
* Unsets offset.
*
* @param integer $offset Offset
*
* @return void
*/
public function offsetUnset($offset): void
{
unset($this->container[$offset]);
}
/**
* Serializes the object to a value that can be serialized natively by json_encode().
* @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed Returns data which can be serialized by json_encode(), which is a value
* of any type other than a resource.
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return ObjectSerializer::sanitizeForSerialization($this);
}
/**
* Gets the string presentation of the object
*
* @return string
*/
public function __toString()
{
return json_encode(
ObjectSerializer::sanitizeForSerialization($this),
JSON_PRETTY_PRINT
);
}
/**
* Gets a header-safe presentation of the object
*
* @return string
*/
public function toHeaderValue()
{
return json_encode(ObjectSerializer::sanitizeForSerialization($this));
}
}
@@ -0,0 +1,552 @@
<?php
/**
* TrashEmployeeFacilityDistanceResponse
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
namespace OmsorgCoreClient\Model;
use \ArrayAccess;
use \OmsorgCoreClient\ObjectSerializer;
/**
* TrashEmployeeFacilityDistanceResponse Class Doc Comment
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
* @implements \ArrayAccess<string, mixed>
*/
class TrashEmployeeFacilityDistanceResponse implements ModelInterface, ArrayAccess, \JsonSerializable
{
public const DISCRIMINATOR = null;
/**
* The original name of the model.
*
* @var string
*/
protected static $openAPIModelName = 'TrashEmployeeFacilityDistanceResponse';
/**
* Array of property to type mappings. Used for (de)serialization
*
* @var string[]
*/
protected static $openAPITypes = [
'id' => 'string',
'facility_id' => 'string',
'employee_id' => 'string',
'distance_km' => 'float',
'deleted_at' => '\DateTime'
];
/**
* Array of property to format mappings. Used for (de)serialization
*
* @var string[]
* @phpstan-var array<string, string|null>
* @psalm-var array<string, string|null>
*/
protected static $openAPIFormats = [
'id' => 'uuid',
'facility_id' => 'uuid',
'employee_id' => 'uuid',
'distance_km' => 'double',
'deleted_at' => 'date-time'
];
/**
* Array of nullable properties. Used for (de)serialization
*
* @var boolean[]
*/
protected static array $openAPINullables = [
'id' => false,
'facility_id' => false,
'employee_id' => false,
'distance_km' => false,
'deleted_at' => true
];
/**
* If a nullable field gets set to null, insert it here
*
* @var boolean[]
*/
protected array $openAPINullablesSetToNull = [];
/**
* Array of property to type mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPITypes()
{
return self::$openAPITypes;
}
/**
* Array of property to format mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPIFormats()
{
return self::$openAPIFormats;
}
/**
* Array of nullable properties
*
* @return array
*/
protected static function openAPINullables(): array
{
return self::$openAPINullables;
}
/**
* Array of nullable field names deliberately set to null
*
* @return boolean[]
*/
private function getOpenAPINullablesSetToNull(): array
{
return $this->openAPINullablesSetToNull;
}
/**
* Setter - Array of nullable field names deliberately set to null
*
* @param boolean[] $openAPINullablesSetToNull
*/
private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void
{
$this->openAPINullablesSetToNull = $openAPINullablesSetToNull;
}
/**
* Checks if a property is nullable
*
* @param string $property
* @return bool
*/
public static function isNullable(string $property): bool
{
return self::openAPINullables()[$property] ?? false;
}
/**
* Checks if a nullable property is set to null.
*
* @param string $property
* @return bool
*/
public function isNullableSetToNull(string $property): bool
{
return in_array($property, $this->getOpenAPINullablesSetToNull(), true);
}
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @var string[]
*/
protected static $attributeMap = [
'id' => 'id',
'facility_id' => 'facilityId',
'employee_id' => 'employeeId',
'distance_km' => 'distanceKm',
'deleted_at' => 'deletedAt'
];
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @var string[]
*/
protected static $setters = [
'id' => 'setId',
'facility_id' => 'setFacilityId',
'employee_id' => 'setEmployeeId',
'distance_km' => 'setDistanceKm',
'deleted_at' => 'setDeletedAt'
];
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @var string[]
*/
protected static $getters = [
'id' => 'getId',
'facility_id' => 'getFacilityId',
'employee_id' => 'getEmployeeId',
'distance_km' => 'getDistanceKm',
'deleted_at' => 'getDeletedAt'
];
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @return array
*/
public static function attributeMap()
{
return self::$attributeMap;
}
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @return array
*/
public static function setters()
{
return self::$setters;
}
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @return array
*/
public static function getters()
{
return self::$getters;
}
/**
* The original name of the model.
*
* @return string
*/
public function getModelName()
{
return self::$openAPIModelName;
}
/**
* Associative array for storing property values
*
* @var mixed[]
*/
protected $container = [];
/**
* Constructor
*
* @param mixed[]|null $data Associated array of property values
* initializing the model
*/
public function __construct(?array $data = null)
{
$this->setIfExists('id', $data ?? [], null);
$this->setIfExists('facility_id', $data ?? [], null);
$this->setIfExists('employee_id', $data ?? [], null);
$this->setIfExists('distance_km', $data ?? [], null);
$this->setIfExists('deleted_at', $data ?? [], null);
}
/**
* Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName
* is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the
* $this->openAPINullablesSetToNull array
*
* @param string $variableName
* @param array $fields
* @param mixed $defaultValue
*/
private function setIfExists(string $variableName, array $fields, $defaultValue): void
{
if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) {
$this->openAPINullablesSetToNull[] = $variableName;
}
$this->container[$variableName] = $fields[$variableName] ?? $defaultValue;
}
/**
* Show all the invalid properties with reasons.
*
* @return array invalid properties with reasons
*/
public function listInvalidProperties()
{
$invalidProperties = [];
return $invalidProperties;
}
/**
* Validate all the properties in the model
* return true if all passed
*
* @return bool True if all properties are valid
*/
public function valid()
{
return count($this->listInvalidProperties()) === 0;
}
/**
* Gets id
*
* @return string|null
*/
public function getId()
{
return $this->container['id'];
}
/**
* Sets id
*
* @param string|null $id id
*
* @return self
*/
public function setId($id)
{
if (is_null($id)) {
throw new \InvalidArgumentException('non-nullable id cannot be null');
}
$this->container['id'] = $id;
return $this;
}
/**
* Gets facility_id
*
* @return string|null
*/
public function getFacilityId()
{
return $this->container['facility_id'];
}
/**
* Sets facility_id
*
* @param string|null $facility_id facility_id
*
* @return self
*/
public function setFacilityId($facility_id)
{
if (is_null($facility_id)) {
throw new \InvalidArgumentException('non-nullable facility_id cannot be null');
}
$this->container['facility_id'] = $facility_id;
return $this;
}
/**
* Gets employee_id
*
* @return string|null
*/
public function getEmployeeId()
{
return $this->container['employee_id'];
}
/**
* Sets employee_id
*
* @param string|null $employee_id employee_id
*
* @return self
*/
public function setEmployeeId($employee_id)
{
if (is_null($employee_id)) {
throw new \InvalidArgumentException('non-nullable employee_id cannot be null');
}
$this->container['employee_id'] = $employee_id;
return $this;
}
/**
* Gets distance_km
*
* @return float|null
*/
public function getDistanceKm()
{
return $this->container['distance_km'];
}
/**
* Sets distance_km
*
* @param float|null $distance_km distance_km
*
* @return self
*/
public function setDistanceKm($distance_km)
{
if (is_null($distance_km)) {
throw new \InvalidArgumentException('non-nullable distance_km cannot be null');
}
$this->container['distance_km'] = $distance_km;
return $this;
}
/**
* Gets deleted_at
*
* @return \DateTime|null
*/
public function getDeletedAt()
{
return $this->container['deleted_at'];
}
/**
* Sets deleted_at
*
* @param \DateTime|null $deleted_at deleted_at
*
* @return self
*/
public function setDeletedAt($deleted_at)
{
if (is_null($deleted_at)) {
array_push($this->openAPINullablesSetToNull, 'deleted_at');
} else {
$nullablesSetToNull = $this->getOpenAPINullablesSetToNull();
$index = array_search('deleted_at', $nullablesSetToNull);
if ($index !== FALSE) {
unset($nullablesSetToNull[$index]);
$this->setOpenAPINullablesSetToNull($nullablesSetToNull);
}
}
$this->container['deleted_at'] = $deleted_at;
return $this;
}
/**
* Returns true if offset exists. False otherwise.
*
* @param integer $offset Offset
*
* @return boolean
*/
public function offsetExists($offset): bool
{
return isset($this->container[$offset]);
}
/**
* Gets offset.
*
* @param integer $offset Offset
*
* @return mixed|null
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->container[$offset] ?? null;
}
/**
* Sets value based on offset.
*
* @param int|null $offset Offset
* @param mixed $value Value to be set
*
* @return void
*/
public function offsetSet($offset, $value): void
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
/**
* Unsets offset.
*
* @param integer $offset Offset
*
* @return void
*/
public function offsetUnset($offset): void
{
unset($this->container[$offset]);
}
/**
* Serializes the object to a value that can be serialized natively by json_encode().
* @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed Returns data which can be serialized by json_encode(), which is a value
* of any type other than a resource.
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return ObjectSerializer::sanitizeForSerialization($this);
}
/**
* Gets the string presentation of the object
*
* @return string
*/
public function __toString()
{
return json_encode(
ObjectSerializer::sanitizeForSerialization($this),
JSON_PRETTY_PRINT
);
}
/**
* Gets a header-safe presentation of the object
*
* @return string
*/
public function toHeaderValue()
{
return json_encode(ObjectSerializer::sanitizeForSerialization($this));
}
}
@@ -0,0 +1,409 @@
<?php
/**
* UpdateEmployeeFacilityDistanceRequest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
namespace OmsorgCoreClient\Model;
use \ArrayAccess;
use \OmsorgCoreClient\ObjectSerializer;
/**
* UpdateEmployeeFacilityDistanceRequest Class Doc Comment
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
* @implements \ArrayAccess<string, mixed>
*/
class UpdateEmployeeFacilityDistanceRequest implements ModelInterface, ArrayAccess, \JsonSerializable
{
public const DISCRIMINATOR = null;
/**
* The original name of the model.
*
* @var string
*/
protected static $openAPIModelName = 'UpdateEmployeeFacilityDistanceRequest';
/**
* Array of property to type mappings. Used for (de)serialization
*
* @var string[]
*/
protected static $openAPITypes = [
'distance_km' => 'float'
];
/**
* Array of property to format mappings. Used for (de)serialization
*
* @var string[]
* @phpstan-var array<string, string|null>
* @psalm-var array<string, string|null>
*/
protected static $openAPIFormats = [
'distance_km' => 'double'
];
/**
* Array of nullable properties. Used for (de)serialization
*
* @var boolean[]
*/
protected static array $openAPINullables = [
'distance_km' => false
];
/**
* If a nullable field gets set to null, insert it here
*
* @var boolean[]
*/
protected array $openAPINullablesSetToNull = [];
/**
* Array of property to type mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPITypes()
{
return self::$openAPITypes;
}
/**
* Array of property to format mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPIFormats()
{
return self::$openAPIFormats;
}
/**
* Array of nullable properties
*
* @return array
*/
protected static function openAPINullables(): array
{
return self::$openAPINullables;
}
/**
* Array of nullable field names deliberately set to null
*
* @return boolean[]
*/
private function getOpenAPINullablesSetToNull(): array
{
return $this->openAPINullablesSetToNull;
}
/**
* Setter - Array of nullable field names deliberately set to null
*
* @param boolean[] $openAPINullablesSetToNull
*/
private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void
{
$this->openAPINullablesSetToNull = $openAPINullablesSetToNull;
}
/**
* Checks if a property is nullable
*
* @param string $property
* @return bool
*/
public static function isNullable(string $property): bool
{
return self::openAPINullables()[$property] ?? false;
}
/**
* Checks if a nullable property is set to null.
*
* @param string $property
* @return bool
*/
public function isNullableSetToNull(string $property): bool
{
return in_array($property, $this->getOpenAPINullablesSetToNull(), true);
}
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @var string[]
*/
protected static $attributeMap = [
'distance_km' => 'distanceKm'
];
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @var string[]
*/
protected static $setters = [
'distance_km' => 'setDistanceKm'
];
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @var string[]
*/
protected static $getters = [
'distance_km' => 'getDistanceKm'
];
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @return array
*/
public static function attributeMap()
{
return self::$attributeMap;
}
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @return array
*/
public static function setters()
{
return self::$setters;
}
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @return array
*/
public static function getters()
{
return self::$getters;
}
/**
* The original name of the model.
*
* @return string
*/
public function getModelName()
{
return self::$openAPIModelName;
}
/**
* Associative array for storing property values
*
* @var mixed[]
*/
protected $container = [];
/**
* Constructor
*
* @param mixed[]|null $data Associated array of property values
* initializing the model
*/
public function __construct(?array $data = null)
{
$this->setIfExists('distance_km', $data ?? [], null);
}
/**
* Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName
* is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the
* $this->openAPINullablesSetToNull array
*
* @param string $variableName
* @param array $fields
* @param mixed $defaultValue
*/
private function setIfExists(string $variableName, array $fields, $defaultValue): void
{
if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) {
$this->openAPINullablesSetToNull[] = $variableName;
}
$this->container[$variableName] = $fields[$variableName] ?? $defaultValue;
}
/**
* Show all the invalid properties with reasons.
*
* @return array invalid properties with reasons
*/
public function listInvalidProperties()
{
$invalidProperties = [];
return $invalidProperties;
}
/**
* Validate all the properties in the model
* return true if all passed
*
* @return bool True if all properties are valid
*/
public function valid()
{
return count($this->listInvalidProperties()) === 0;
}
/**
* Gets distance_km
*
* @return float|null
*/
public function getDistanceKm()
{
return $this->container['distance_km'];
}
/**
* Sets distance_km
*
* @param float|null $distance_km distance_km
*
* @return self
*/
public function setDistanceKm($distance_km)
{
if (is_null($distance_km)) {
throw new \InvalidArgumentException('non-nullable distance_km cannot be null');
}
$this->container['distance_km'] = $distance_km;
return $this;
}
/**
* Returns true if offset exists. False otherwise.
*
* @param integer $offset Offset
*
* @return boolean
*/
public function offsetExists($offset): bool
{
return isset($this->container[$offset]);
}
/**
* Gets offset.
*
* @param integer $offset Offset
*
* @return mixed|null
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->container[$offset] ?? null;
}
/**
* Sets value based on offset.
*
* @param int|null $offset Offset
* @param mixed $value Value to be set
*
* @return void
*/
public function offsetSet($offset, $value): void
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
/**
* Unsets offset.
*
* @param integer $offset Offset
*
* @return void
*/
public function offsetUnset($offset): void
{
unset($this->container[$offset]);
}
/**
* Serializes the object to a value that can be serialized natively by json_encode().
* @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed Returns data which can be serialized by json_encode(), which is a value
* of any type other than a resource.
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return ObjectSerializer::sanitizeForSerialization($this);
}
/**
* Gets the string presentation of the object
*
* @return string
*/
public function __toString()
{
return json_encode(
ObjectSerializer::sanitizeForSerialization($this),
JSON_PRETTY_PRINT
);
}
/**
* Gets a header-safe presentation of the object
*
* @return string
*/
public function toHeaderValue()
{
return json_encode(ObjectSerializer::sanitizeForSerialization($this));
}
}
@@ -76,6 +76,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
'sunday_surcharge_percent' => 'float', 'sunday_surcharge_percent' => 'float',
'holiday_surcharge_percent' => 'float', 'holiday_surcharge_percent' => 'float',
'travel_cost_rate' => 'float', 'travel_cost_rate' => 'float',
'travel_cost_mode' => 'string',
'travel_cost_per_km' => 'float',
'minimum_hours' => 'float', 'minimum_hours' => 'float',
'break_policy' => 'string', 'break_policy' => 'string',
'billing_interval' => 'string', 'billing_interval' => 'string',
@@ -110,6 +112,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
'sunday_surcharge_percent' => 'double', 'sunday_surcharge_percent' => 'double',
'holiday_surcharge_percent' => 'double', 'holiday_surcharge_percent' => 'double',
'travel_cost_rate' => 'double', 'travel_cost_rate' => 'double',
'travel_cost_mode' => null,
'travel_cost_per_km' => 'double',
'minimum_hours' => 'double', 'minimum_hours' => 'double',
'break_policy' => null, 'break_policy' => null,
'billing_interval' => null, 'billing_interval' => null,
@@ -142,6 +146,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
'sunday_surcharge_percent' => true, 'sunday_surcharge_percent' => true,
'holiday_surcharge_percent' => true, 'holiday_surcharge_percent' => true,
'travel_cost_rate' => true, 'travel_cost_rate' => true,
'travel_cost_mode' => true,
'travel_cost_per_km' => true,
'minimum_hours' => true, 'minimum_hours' => true,
'break_policy' => true, 'break_policy' => true,
'billing_interval' => true, 'billing_interval' => true,
@@ -254,6 +260,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
'sunday_surcharge_percent' => 'sundaySurchargePercent', 'sunday_surcharge_percent' => 'sundaySurchargePercent',
'holiday_surcharge_percent' => 'holidaySurchargePercent', 'holiday_surcharge_percent' => 'holidaySurchargePercent',
'travel_cost_rate' => 'travelCostRate', 'travel_cost_rate' => 'travelCostRate',
'travel_cost_mode' => 'travelCostMode',
'travel_cost_per_km' => 'travelCostPerKm',
'minimum_hours' => 'minimumHours', 'minimum_hours' => 'minimumHours',
'break_policy' => 'breakPolicy', 'break_policy' => 'breakPolicy',
'billing_interval' => 'billingInterval', 'billing_interval' => 'billingInterval',
@@ -286,6 +294,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
'sunday_surcharge_percent' => 'setSundaySurchargePercent', 'sunday_surcharge_percent' => 'setSundaySurchargePercent',
'holiday_surcharge_percent' => 'setHolidaySurchargePercent', 'holiday_surcharge_percent' => 'setHolidaySurchargePercent',
'travel_cost_rate' => 'setTravelCostRate', 'travel_cost_rate' => 'setTravelCostRate',
'travel_cost_mode' => 'setTravelCostMode',
'travel_cost_per_km' => 'setTravelCostPerKm',
'minimum_hours' => 'setMinimumHours', 'minimum_hours' => 'setMinimumHours',
'break_policy' => 'setBreakPolicy', 'break_policy' => 'setBreakPolicy',
'billing_interval' => 'setBillingInterval', 'billing_interval' => 'setBillingInterval',
@@ -318,6 +328,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
'sunday_surcharge_percent' => 'getSundaySurchargePercent', 'sunday_surcharge_percent' => 'getSundaySurchargePercent',
'holiday_surcharge_percent' => 'getHolidaySurchargePercent', 'holiday_surcharge_percent' => 'getHolidaySurchargePercent',
'travel_cost_rate' => 'getTravelCostRate', 'travel_cost_rate' => 'getTravelCostRate',
'travel_cost_mode' => 'getTravelCostMode',
'travel_cost_per_km' => 'getTravelCostPerKm',
'minimum_hours' => 'getMinimumHours', 'minimum_hours' => 'getMinimumHours',
'break_policy' => 'getBreakPolicy', 'break_policy' => 'getBreakPolicy',
'billing_interval' => 'getBillingInterval', 'billing_interval' => 'getBillingInterval',
@@ -401,6 +413,8 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
$this->setIfExists('sunday_surcharge_percent', $data ?? [], null); $this->setIfExists('sunday_surcharge_percent', $data ?? [], null);
$this->setIfExists('holiday_surcharge_percent', $data ?? [], null); $this->setIfExists('holiday_surcharge_percent', $data ?? [], null);
$this->setIfExists('travel_cost_rate', $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('minimum_hours', $data ?? [], null); $this->setIfExists('minimum_hours', $data ?? [], null);
$this->setIfExists('break_policy', $data ?? [], null); $this->setIfExists('break_policy', $data ?? [], null);
$this->setIfExists('billing_interval', $data ?? [], null); $this->setIfExists('billing_interval', $data ?? [], null);
@@ -1096,6 +1110,74 @@ class UpdateFacilityRequest implements ModelInterface, ArrayAccess, \JsonSeriali
return $this; 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 minimum_hours * Gets minimum_hours
* *
@@ -0,0 +1,409 @@
<?php
/**
* UpdateMyFacilityDistanceRequest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
namespace OmsorgCoreClient\Model;
use \ArrayAccess;
use \OmsorgCoreClient\ObjectSerializer;
/**
* UpdateMyFacilityDistanceRequest Class Doc Comment
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
* @implements \ArrayAccess<string, mixed>
*/
class UpdateMyFacilityDistanceRequest implements ModelInterface, ArrayAccess, \JsonSerializable
{
public const DISCRIMINATOR = null;
/**
* The original name of the model.
*
* @var string
*/
protected static $openAPIModelName = 'UpdateMyFacilityDistanceRequest';
/**
* Array of property to type mappings. Used for (de)serialization
*
* @var string[]
*/
protected static $openAPITypes = [
'distance_km' => 'float'
];
/**
* Array of property to format mappings. Used for (de)serialization
*
* @var string[]
* @phpstan-var array<string, string|null>
* @psalm-var array<string, string|null>
*/
protected static $openAPIFormats = [
'distance_km' => 'double'
];
/**
* Array of nullable properties. Used for (de)serialization
*
* @var boolean[]
*/
protected static array $openAPINullables = [
'distance_km' => false
];
/**
* If a nullable field gets set to null, insert it here
*
* @var boolean[]
*/
protected array $openAPINullablesSetToNull = [];
/**
* Array of property to type mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPITypes()
{
return self::$openAPITypes;
}
/**
* Array of property to format mappings. Used for (de)serialization
*
* @return array
*/
public static function openAPIFormats()
{
return self::$openAPIFormats;
}
/**
* Array of nullable properties
*
* @return array
*/
protected static function openAPINullables(): array
{
return self::$openAPINullables;
}
/**
* Array of nullable field names deliberately set to null
*
* @return boolean[]
*/
private function getOpenAPINullablesSetToNull(): array
{
return $this->openAPINullablesSetToNull;
}
/**
* Setter - Array of nullable field names deliberately set to null
*
* @param boolean[] $openAPINullablesSetToNull
*/
private function setOpenAPINullablesSetToNull(array $openAPINullablesSetToNull): void
{
$this->openAPINullablesSetToNull = $openAPINullablesSetToNull;
}
/**
* Checks if a property is nullable
*
* @param string $property
* @return bool
*/
public static function isNullable(string $property): bool
{
return self::openAPINullables()[$property] ?? false;
}
/**
* Checks if a nullable property is set to null.
*
* @param string $property
* @return bool
*/
public function isNullableSetToNull(string $property): bool
{
return in_array($property, $this->getOpenAPINullablesSetToNull(), true);
}
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @var string[]
*/
protected static $attributeMap = [
'distance_km' => 'distanceKm'
];
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @var string[]
*/
protected static $setters = [
'distance_km' => 'setDistanceKm'
];
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @var string[]
*/
protected static $getters = [
'distance_km' => 'getDistanceKm'
];
/**
* Array of attributes where the key is the local name,
* and the value is the original name
*
* @return array
*/
public static function attributeMap()
{
return self::$attributeMap;
}
/**
* Array of attributes to setter functions (for deserialization of responses)
*
* @return array
*/
public static function setters()
{
return self::$setters;
}
/**
* Array of attributes to getter functions (for serialization of requests)
*
* @return array
*/
public static function getters()
{
return self::$getters;
}
/**
* The original name of the model.
*
* @return string
*/
public function getModelName()
{
return self::$openAPIModelName;
}
/**
* Associative array for storing property values
*
* @var mixed[]
*/
protected $container = [];
/**
* Constructor
*
* @param mixed[]|null $data Associated array of property values
* initializing the model
*/
public function __construct(?array $data = null)
{
$this->setIfExists('distance_km', $data ?? [], null);
}
/**
* Sets $this->container[$variableName] to the given data or to the given default Value; if $variableName
* is nullable and its value is set to null in the $fields array, then mark it as "set to null" in the
* $this->openAPINullablesSetToNull array
*
* @param string $variableName
* @param array $fields
* @param mixed $defaultValue
*/
private function setIfExists(string $variableName, array $fields, $defaultValue): void
{
if (self::isNullable($variableName) && array_key_exists($variableName, $fields) && is_null($fields[$variableName])) {
$this->openAPINullablesSetToNull[] = $variableName;
}
$this->container[$variableName] = $fields[$variableName] ?? $defaultValue;
}
/**
* Show all the invalid properties with reasons.
*
* @return array invalid properties with reasons
*/
public function listInvalidProperties()
{
$invalidProperties = [];
return $invalidProperties;
}
/**
* Validate all the properties in the model
* return true if all passed
*
* @return bool True if all properties are valid
*/
public function valid()
{
return count($this->listInvalidProperties()) === 0;
}
/**
* Gets distance_km
*
* @return float|null
*/
public function getDistanceKm()
{
return $this->container['distance_km'];
}
/**
* Sets distance_km
*
* @param float|null $distance_km distance_km
*
* @return self
*/
public function setDistanceKm($distance_km)
{
if (is_null($distance_km)) {
throw new \InvalidArgumentException('non-nullable distance_km cannot be null');
}
$this->container['distance_km'] = $distance_km;
return $this;
}
/**
* Returns true if offset exists. False otherwise.
*
* @param integer $offset Offset
*
* @return boolean
*/
public function offsetExists($offset): bool
{
return isset($this->container[$offset]);
}
/**
* Gets offset.
*
* @param integer $offset Offset
*
* @return mixed|null
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->container[$offset] ?? null;
}
/**
* Sets value based on offset.
*
* @param int|null $offset Offset
* @param mixed $value Value to be set
*
* @return void
*/
public function offsetSet($offset, $value): void
{
if (is_null($offset)) {
$this->container[] = $value;
} else {
$this->container[$offset] = $value;
}
}
/**
* Unsets offset.
*
* @param integer $offset Offset
*
* @return void
*/
public function offsetUnset($offset): void
{
unset($this->container[$offset]);
}
/**
* Serializes the object to a value that can be serialized natively by json_encode().
* @link https://www.php.net/manual/en/jsonserializable.jsonserialize.php
*
* @return mixed Returns data which can be serialized by json_encode(), which is a value
* of any type other than a resource.
*/
#[\ReturnTypeWillChange]
public function jsonSerialize()
{
return ObjectSerializer::sanitizeForSerialization($this);
}
/**
* Gets the string presentation of the object
*
* @return string
*/
public function __toString()
{
return json_encode(
ObjectSerializer::sanitizeForSerialization($this),
JSON_PRETTY_PRINT
);
}
/**
* Gets a header-safe presentation of the object
*
* @return string
*/
public function toHeaderValue()
{
return json_encode(ObjectSerializer::sanitizeForSerialization($this));
}
}
@@ -0,0 +1,121 @@
<?php
/**
* EmployeeFacilityDistancesApiTest
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Please update the test case below to test the endpoint.
*/
namespace OmsorgCoreClient\Test\Api;
use \OmsorgCoreClient\Configuration;
use \OmsorgCoreClient\ApiException;
use \OmsorgCoreClient\ObjectSerializer;
use PHPUnit\Framework\TestCase;
/**
* EmployeeFacilityDistancesApiTest Class Doc Comment
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
class EmployeeFacilityDistancesApiTest extends TestCase
{
/**
* Setup before running any test cases
*/
public static function setUpBeforeClass(): void
{
}
/**
* Setup before running each test case
*/
public function setUp(): void
{
}
/**
* Clean up after running each test case
*/
public function tearDown(): void
{
}
/**
* Clean up after running all test cases
*/
public static function tearDownAfterClass(): void
{
}
/**
* Test case for apiFacilitiesFacilityIdEmployeeDistancesGet
*
* .
*
*/
public function testApiFacilitiesFacilityIdEmployeeDistancesGet()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test case for apiFacilitiesFacilityIdEmployeeDistancesIdDelete
*
* .
*
*/
public function testApiFacilitiesFacilityIdEmployeeDistancesIdDelete()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test case for apiFacilitiesFacilityIdEmployeeDistancesIdPut
*
* .
*
*/
public function testApiFacilitiesFacilityIdEmployeeDistancesIdPut()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test case for apiFacilitiesFacilityIdEmployeeDistancesPost
*
* .
*
*/
public function testApiFacilitiesFacilityIdEmployeeDistancesPost()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
}
@@ -0,0 +1,121 @@
<?php
/**
* MyFacilityDistancesApiTest
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Please update the test case below to test the endpoint.
*/
namespace OmsorgCoreClient\Test\Api;
use \OmsorgCoreClient\Configuration;
use \OmsorgCoreClient\ApiException;
use \OmsorgCoreClient\ObjectSerializer;
use PHPUnit\Framework\TestCase;
/**
* MyFacilityDistancesApiTest Class Doc Comment
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
class MyFacilityDistancesApiTest extends TestCase
{
/**
* Setup before running any test cases
*/
public static function setUpBeforeClass(): void
{
}
/**
* Setup before running each test case
*/
public function setUp(): void
{
}
/**
* Clean up after running each test case
*/
public function tearDown(): void
{
}
/**
* Clean up after running all test cases
*/
public static function tearDownAfterClass(): void
{
}
/**
* Test case for apiMeFacilityDistancesFacilitiesGet
*
* .
*
*/
public function testApiMeFacilityDistancesFacilitiesGet()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test case for apiMeFacilityDistancesGet
*
* .
*
*/
public function testApiMeFacilityDistancesGet()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test case for apiMeFacilityDistancesIdPut
*
* .
*
*/
public function testApiMeFacilityDistancesIdPut()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test case for apiMeFacilityDistancesPost
*
* .
*
*/
public function testApiMeFacilityDistancesPost()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
}
@@ -0,0 +1,99 @@
<?php
/**
* CreateEmployeeFacilityDistanceRequestTest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Please update the test case below to test the model.
*/
namespace OmsorgCoreClient\Test\Model;
use PHPUnit\Framework\TestCase;
/**
* CreateEmployeeFacilityDistanceRequestTest Class Doc Comment
*
* @category Class
* @description CreateEmployeeFacilityDistanceRequest
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
class CreateEmployeeFacilityDistanceRequestTest extends TestCase
{
/**
* Setup before running any test case
*/
public static function setUpBeforeClass(): void
{
}
/**
* Setup before running each test case
*/
public function setUp(): void
{
}
/**
* Clean up after running each test case
*/
public function tearDown(): void
{
}
/**
* Clean up after running all test cases
*/
public static function tearDownAfterClass(): void
{
}
/**
* Test "CreateEmployeeFacilityDistanceRequest"
*/
public function testCreateEmployeeFacilityDistanceRequest()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "employee_id"
*/
public function testPropertyEmployeeId()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "distance_km"
*/
public function testPropertyDistanceKm()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
}
@@ -0,0 +1,99 @@
<?php
/**
* CreateMyFacilityDistanceRequestTest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Please update the test case below to test the model.
*/
namespace OmsorgCoreClient\Test\Model;
use PHPUnit\Framework\TestCase;
/**
* CreateMyFacilityDistanceRequestTest Class Doc Comment
*
* @category Class
* @description CreateMyFacilityDistanceRequest
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
class CreateMyFacilityDistanceRequestTest extends TestCase
{
/**
* Setup before running any test case
*/
public static function setUpBeforeClass(): void
{
}
/**
* Setup before running each test case
*/
public function setUp(): void
{
}
/**
* Clean up after running each test case
*/
public function tearDown(): void
{
}
/**
* Clean up after running all test cases
*/
public static function tearDownAfterClass(): void
{
}
/**
* Test "CreateMyFacilityDistanceRequest"
*/
public function testCreateMyFacilityDistanceRequest()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "facility_id"
*/
public function testPropertyFacilityId()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "distance_km"
*/
public function testPropertyDistanceKm()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
}
@@ -0,0 +1,135 @@
<?php
/**
* EmployeeFacilityDistanceResponseTest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Please update the test case below to test the model.
*/
namespace OmsorgCoreClient\Test\Model;
use PHPUnit\Framework\TestCase;
/**
* EmployeeFacilityDistanceResponseTest Class Doc Comment
*
* @category Class
* @description EmployeeFacilityDistanceResponse
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
class EmployeeFacilityDistanceResponseTest extends TestCase
{
/**
* Setup before running any test case
*/
public static function setUpBeforeClass(): void
{
}
/**
* Setup before running each test case
*/
public function setUp(): void
{
}
/**
* Clean up after running each test case
*/
public function tearDown(): void
{
}
/**
* Clean up after running all test cases
*/
public static function tearDownAfterClass(): void
{
}
/**
* Test "EmployeeFacilityDistanceResponse"
*/
public function testEmployeeFacilityDistanceResponse()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "id"
*/
public function testPropertyId()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "facility_id"
*/
public function testPropertyFacilityId()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "employee_id"
*/
public function testPropertyEmployeeId()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "employee_first_name"
*/
public function testPropertyEmployeeFirstName()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "employee_last_name"
*/
public function testPropertyEmployeeLastName()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "distance_km"
*/
public function testPropertyDistanceKm()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
}
@@ -0,0 +1,99 @@
<?php
/**
* FacilityOptionResponseTest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Please update the test case below to test the model.
*/
namespace OmsorgCoreClient\Test\Model;
use PHPUnit\Framework\TestCase;
/**
* FacilityOptionResponseTest Class Doc Comment
*
* @category Class
* @description FacilityOptionResponse
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
class FacilityOptionResponseTest extends TestCase
{
/**
* Setup before running any test case
*/
public static function setUpBeforeClass(): void
{
}
/**
* Setup before running each test case
*/
public function setUp(): void
{
}
/**
* Clean up after running each test case
*/
public function tearDown(): void
{
}
/**
* Clean up after running all test cases
*/
public static function tearDownAfterClass(): void
{
}
/**
* Test "FacilityOptionResponse"
*/
public function testFacilityOptionResponse()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "id"
*/
public function testPropertyId()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "name"
*/
public function testPropertyName()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
}
@@ -0,0 +1,117 @@
<?php
/**
* MyFacilityDistanceResponseTest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Please update the test case below to test the model.
*/
namespace OmsorgCoreClient\Test\Model;
use PHPUnit\Framework\TestCase;
/**
* MyFacilityDistanceResponseTest Class Doc Comment
*
* @category Class
* @description MyFacilityDistanceResponse
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
class MyFacilityDistanceResponseTest extends TestCase
{
/**
* Setup before running any test case
*/
public static function setUpBeforeClass(): void
{
}
/**
* Setup before running each test case
*/
public function setUp(): void
{
}
/**
* Clean up after running each test case
*/
public function tearDown(): void
{
}
/**
* Clean up after running all test cases
*/
public static function tearDownAfterClass(): void
{
}
/**
* Test "MyFacilityDistanceResponse"
*/
public function testMyFacilityDistanceResponse()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "id"
*/
public function testPropertyId()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "facility_id"
*/
public function testPropertyFacilityId()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "facility_name"
*/
public function testPropertyFacilityName()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "distance_km"
*/
public function testPropertyDistanceKm()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
}
@@ -0,0 +1,126 @@
<?php
/**
* TrashEmployeeFacilityDistanceResponseTest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Please update the test case below to test the model.
*/
namespace OmsorgCoreClient\Test\Model;
use PHPUnit\Framework\TestCase;
/**
* TrashEmployeeFacilityDistanceResponseTest Class Doc Comment
*
* @category Class
* @description TrashEmployeeFacilityDistanceResponse
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
class TrashEmployeeFacilityDistanceResponseTest extends TestCase
{
/**
* Setup before running any test case
*/
public static function setUpBeforeClass(): void
{
}
/**
* Setup before running each test case
*/
public function setUp(): void
{
}
/**
* Clean up after running each test case
*/
public function tearDown(): void
{
}
/**
* Clean up after running all test cases
*/
public static function tearDownAfterClass(): void
{
}
/**
* Test "TrashEmployeeFacilityDistanceResponse"
*/
public function testTrashEmployeeFacilityDistanceResponse()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "id"
*/
public function testPropertyId()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "facility_id"
*/
public function testPropertyFacilityId()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "employee_id"
*/
public function testPropertyEmployeeId()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "distance_km"
*/
public function testPropertyDistanceKm()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "deleted_at"
*/
public function testPropertyDeletedAt()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
}
@@ -0,0 +1,90 @@
<?php
/**
* UpdateEmployeeFacilityDistanceRequestTest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Please update the test case below to test the model.
*/
namespace OmsorgCoreClient\Test\Model;
use PHPUnit\Framework\TestCase;
/**
* UpdateEmployeeFacilityDistanceRequestTest Class Doc Comment
*
* @category Class
* @description UpdateEmployeeFacilityDistanceRequest
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
class UpdateEmployeeFacilityDistanceRequestTest extends TestCase
{
/**
* Setup before running any test case
*/
public static function setUpBeforeClass(): void
{
}
/**
* Setup before running each test case
*/
public function setUp(): void
{
}
/**
* Clean up after running each test case
*/
public function tearDown(): void
{
}
/**
* Clean up after running all test cases
*/
public static function tearDownAfterClass(): void
{
}
/**
* Test "UpdateEmployeeFacilityDistanceRequest"
*/
public function testUpdateEmployeeFacilityDistanceRequest()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "distance_km"
*/
public function testPropertyDistanceKm()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
}
@@ -0,0 +1,90 @@
<?php
/**
* UpdateMyFacilityDistanceRequestTest
*
* PHP version 8.1
*
* @category Class
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
/**
* OmsorgCore.Api
*
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0
* Generated by: https://openapi-generator.tech
* Generator version: 7.14.0
*/
/**
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Please update the test case below to test the model.
*/
namespace OmsorgCoreClient\Test\Model;
use PHPUnit\Framework\TestCase;
/**
* UpdateMyFacilityDistanceRequestTest Class Doc Comment
*
* @category Class
* @description UpdateMyFacilityDistanceRequest
* @package OmsorgCoreClient
* @author OpenAPI Generator team
* @link https://openapi-generator.tech
*/
class UpdateMyFacilityDistanceRequestTest extends TestCase
{
/**
* Setup before running any test case
*/
public static function setUpBeforeClass(): void
{
}
/**
* Setup before running each test case
*/
public function setUp(): void
{
}
/**
* Clean up after running each test case
*/
public function tearDown(): void
{
}
/**
* Clean up after running all test cases
*/
public static function tearDownAfterClass(): void
{
}
/**
* Test "UpdateMyFacilityDistanceRequest"
*/
public function testUpdateMyFacilityDistanceRequest()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
/**
* Test attribute "distance_km"
*/
public function testPropertyDistanceKm()
{
// TODO: implement
self::markTestIncomplete('Not implemented');
}
}
@@ -0,0 +1,25 @@
<?php
// autoload.php @generated by Composer
if (PHP_VERSION_ID < 50600) {
if (!headers_sent()) {
header('HTTP/1.1 500 Internal Server Error');
}
$err = 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL;
if (!ini_get('display_errors')) {
if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
fwrite(STDERR, $err);
} elseif (!headers_sent()) {
echo $err;
}
}
trigger_error(
$err,
E_USER_ERROR
);
}
require_once __DIR__ . '/composer/autoload_real.php';
return ComposerAutoloaderInit3d156d10b574f52a2dfd9a5ced1e76a6::getLoader();
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../friendsofphp/php-cs-fixer/php-cs-fixer)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/friendsofphp/php-cs-fixer/php-cs-fixer');
}
}
return include __DIR__ . '/..'.'/friendsofphp/php-cs-fixer/php-cs-fixer';
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../nikic/php-parser/bin/php-parse)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = $this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse');
}
}
return include __DIR__ . '/..'.'/nikic/php-parser/bin/php-parse';
+122
View File
@@ -0,0 +1,122 @@
#!/usr/bin/env php
<?php
/**
* Proxy PHP file generated by Composer
*
* This file includes the referenced bin path (../phpunit/phpunit/phpunit)
* using a stream wrapper to prevent the shebang from being output on PHP<8
*
* @generated
*/
namespace Composer;
$GLOBALS['_composer_bin_dir'] = __DIR__;
$GLOBALS['_composer_autoload_path'] = __DIR__ . '/..'.'/autoload.php';
$GLOBALS['__PHPUNIT_ISOLATION_EXCLUDE_LIST'] = $GLOBALS['__PHPUNIT_ISOLATION_BLACKLIST'] = array(realpath(__DIR__ . '/..'.'/phpunit/phpunit/phpunit'));
if (PHP_VERSION_ID < 80000) {
if (!class_exists('Composer\BinProxyWrapper')) {
/**
* @internal
*/
final class BinProxyWrapper
{
private $handle;
private $position;
private $realpath;
public function stream_open($path, $mode, $options, &$opened_path)
{
// get rid of phpvfscomposer:// prefix for __FILE__ & __DIR__ resolution
$opened_path = substr($path, 17);
$this->realpath = realpath($opened_path) ?: $opened_path;
$opened_path = 'phpvfscomposer://'.$this->realpath;
$this->handle = fopen($this->realpath, $mode);
$this->position = 0;
return (bool) $this->handle;
}
public function stream_read($count)
{
$data = fread($this->handle, $count);
if ($this->position === 0) {
$data = preg_replace('{^#!.*\r?\n}', '', $data);
}
$data = str_replace('__DIR__', var_export(dirname($this->realpath), true), $data);
$data = str_replace('__FILE__', var_export($this->realpath, true), $data);
$this->position += strlen($data);
return $data;
}
public function stream_cast($castAs)
{
return $this->handle;
}
public function stream_close()
{
fclose($this->handle);
}
public function stream_lock($operation)
{
return $operation ? flock($this->handle, $operation) : true;
}
public function stream_seek($offset, $whence)
{
if (0 === fseek($this->handle, $offset, $whence)) {
$this->position = ftell($this->handle);
return true;
}
return false;
}
public function stream_tell()
{
return $this->position;
}
public function stream_eof()
{
return feof($this->handle);
}
public function stream_stat()
{
return array();
}
public function stream_set_option($option, $arg1, $arg2)
{
return true;
}
public function url_stat($path, $flags)
{
$path = substr($path, 17);
if (file_exists($path)) {
return stat($path);
}
return false;
}
}
}
if (
(function_exists('stream_get_wrappers') && in_array('phpvfscomposer', stream_get_wrappers(), true))
|| (function_exists('stream_wrapper_register') && stream_wrapper_register('phpvfscomposer', 'Composer\BinProxyWrapper'))
) {
return include("phpvfscomposer://" . __DIR__ . '/..'.'/phpunit/phpunit/phpunit');
}
}
return include __DIR__ . '/..'.'/phpunit/phpunit/phpunit';
@@ -0,0 +1,2 @@
github: clue
custom: https://clue.engineering/support
@@ -0,0 +1,75 @@
# Changelog
## 1.3.0 (2022-12-23)
* Feature: Add support for PHP 8.1 and PHP 8.2.
(#31 by @clue and #30 by @SimonFring)
* Feature: Check type of incoming `data` before trying to decode NDJSON.
(#29 by @SimonFrings)
* Improve documentation and examples and update to new [default loop](https://reactphp.org/event-loop/#loop).
(#26 by @clue, #27 by @SimonFrings and #25 by @PaulRotmann)
* Improve test suite, report failed assertions and ensure 100% code coverage.
(#32 and #33 by @clue and #28 by @SimonFrings)
## 1.2.0 (2020-12-09)
* Improve test suite and add `.gitattributes` to exclude dev files from exports.
Add PHP 8 support, update to PHPUnit 9 and simplify test setup.
(#18 by @clue and #19, #22 and #23 by @SimonFrings)
## 1.1.0 (2020-02-04)
* Feature: Improve error reporting and add parsing error message to Exception and
ignore `JSON_THROW_ON_ERROR` option (available as of PHP 7.3).
(#14 by @clue)
* Feature: Add bechmarking script and import all global function references.
(#16 by @clue)
* Improve documentation and add NDJSON format description and
add support / sponsorship info.
(#12 and #17 by @clue)
* Improve test suite to run tests on PHP 7.4 and simplify test matrix and
apply minor code style adjustments to make phpstan happy.
(#13 and #15 by @clue)
## 1.0.0 (2018-05-17)
* First stable release, now following SemVer
* Improve documentation and usage examples
> Contains no other changes, so it's actually fully compatible with the v0.1.2 release.
## 0.1.2 (2018-05-11)
* Feature: Limit buffer size to 64 KiB by default.
(#10 by @clue)
* Feature: Forward compatiblity with EventLoop v0.5 and upcoming v1.0.
(#8 by @clue)
* Fix: Return bool `false` if encoding fails due to invalid value to pause source.
(#9 by @clue)
* Improve test suite by supporting PHPUnit v6 and test against legacy PHP 5.3 through PHP 7.2.
(#7 by @clue)
* Update project homepage.
(#11 by @clue)
## 0.1.1 (2017-05-22)
* Feature: Forward compatibility with Stream v0.7, v0.6, v0.5 and upcoming v1.0 (while keeping BC)
(#6 by @thklein)
* Improved test suite by adding PHPUnit to `require-dev`
(#5 by @thklein)
## 0.1.0 (2016-11-24)
* First tagged release
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 Christian Lück
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Some files were not shown because too many files have changed in this diff Show More