docs(api): 7 fehlende Router nachgezogen
hours_payouts, public_stamp, ical, reseller, tenants, special_assignments, tls_admin – seit dem letzten Doku-Update (2026-05-24) neu dazugekommen. projects.py bewusst ausgelassen (toter Code, nicht registriert). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015Ahyx6D3r7G1EuAc42nezn
This commit is contained in:
+427
@@ -22,6 +22,13 @@ In der Entwicklungsumgebung ist die interaktive Dokumentation unter `/docs` (Swa
|
|||||||
12. [CalDAV-Integration](#caldav-integration)
|
12. [CalDAV-Integration](#caldav-integration)
|
||||||
13. [Busylight-Integration](#busylight-integration)
|
13. [Busylight-Integration](#busylight-integration)
|
||||||
14. [Kimai-Import](#kimai-import)
|
14. [Kimai-Import](#kimai-import)
|
||||||
|
15. [Stunden-Auszahlung](#stunden-auszahlung)
|
||||||
|
16. [Öffentliches QR-Stempeln](#öffentliches-qr-stempeln)
|
||||||
|
17. [iCal-Abo-Feed](#ical-abo-feed)
|
||||||
|
18. [Reseller-Selfservice](#reseller-selfservice)
|
||||||
|
19. [Admin · Mandanten & Reseller](#admin--mandanten--reseller)
|
||||||
|
20. [Sondervertretungen](#sondervertretungen)
|
||||||
|
21. [Admin · TLS](#admin--tls)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -1373,3 +1380,423 @@ Kimai-CSV-Export vorschauen (keine DB-Änderungen).
|
|||||||
Kimai-CSV-Import durchführen.
|
Kimai-CSV-Import durchführen.
|
||||||
|
|
||||||
**Response:** `{ "time_imported": 150, "absence_imported": 12, "skipped": 3, "errors": [] }`
|
**Response:** `{ "time_imported": 150, "absence_imported": 12, "skipped": 3, "errors": [] }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Stunden-Auszahlung
|
||||||
|
|
||||||
|
Router-Präfix: `/hr/payouts`
|
||||||
|
|
||||||
|
Firmen-Opt-in `payout_request_enabled`: entweder bucht HR eine Auszahlung direkt
|
||||||
|
(sofort genehmigt, Saldo wird sofort abgezogen), oder ein Mitarbeiter beantragt
|
||||||
|
seine eigene Auszahlung und HR genehmigt/lehnt ab (Saldo-Abzug erst bei
|
||||||
|
Genehmigung). Alle Aktionen erzeugen einen `AuditLog`-Eintrag.
|
||||||
|
|
||||||
|
### `GET /hr/payouts`
|
||||||
|
|
||||||
|
Auszahlungen der eigenen Firma, optional gefiltert (`user_id`, `year`, `month`, `status`).
|
||||||
|
EMPLOYEE/MANAGER sehen dabei ausschließlich eigene Anträge.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** authentifiziert (jede Rolle)
|
||||||
|
|
||||||
|
**Response:** `HoursPayoutListResponse`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /hr/payouts`
|
||||||
|
|
||||||
|
HR bucht eine Auszahlung direkt – sofort genehmigt, Saldo wird sofort belastet.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** HR, COMPANY_ADMIN, SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response `201`:** `HoursPayoutOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /hr/payouts/request`
|
||||||
|
|
||||||
|
Mitarbeiter beantragt Auszahlung eigener Überstunden. Nur wenn die Firma
|
||||||
|
`payout_request_enabled` gesetzt hat (sonst `403`). Saldo wird erst bei
|
||||||
|
HR-Genehmigung abgezogen.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** authentifiziert (jede Rolle)
|
||||||
|
|
||||||
|
**Response `201`:** `HoursPayoutOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /hr/payouts/{payout_id}/approve`
|
||||||
|
|
||||||
|
HR genehmigt einen offenen Antrag – erst jetzt wird der Überstunden-Saldo abgezogen.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** HR, COMPANY_ADMIN, SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `HoursPayoutOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /hr/payouts/{payout_id}/reject`
|
||||||
|
|
||||||
|
HR lehnt einen offenen Antrag ab (kein Saldo-Abzug).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** HR, COMPANY_ADMIN, SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `HoursPayoutOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /hr/payouts/{payout_id}/cancel`
|
||||||
|
|
||||||
|
Antragsteller zieht seinen eigenen, noch offenen Antrag zurück.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** authentifiziert (jede Rolle, nur eigener Antrag)
|
||||||
|
|
||||||
|
**Response:** `HoursPayoutOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /hr/payouts/{payout_id}`
|
||||||
|
|
||||||
|
Auszahlung stornieren. Bei bereits genehmigten/gebuchten Auszahlungen werden die
|
||||||
|
Stunden dem Saldo zurückgebucht.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** HR, COMPANY_ADMIN, SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `204 No Content`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Öffentliches QR-Stempeln
|
||||||
|
|
||||||
|
Router-Präfix: `/time/public`
|
||||||
|
|
||||||
|
Statischer, firmenweiter QR-Code zum Aushängen (z. B. am Eingang). Mitarbeiter
|
||||||
|
scannen mit dem **eigenen** Handy und stempeln über Personalnummer + PIN.
|
||||||
|
|
||||||
|
⚠ **Kein Bearer-Token und keine Ed25519-Geräte-Signatur** (anders als der
|
||||||
|
Kiosk-Modus) – ein privates Handy hat keinen Geräteschlüssel. Absicherung
|
||||||
|
stattdessen über: Opt-in pro Firma (default OFF), PIN + Lockout, IP-Rate-Limit,
|
||||||
|
gehashtes rotierbares Token in der QR-URL, 120s-Kurzsession, AuditLog. Bewusst
|
||||||
|
schwächer als der Kiosk-Pfad (kein Replay-Nonce, keine IP-Whitelist) – tauscht
|
||||||
|
Sicherheit gegen BYOD-Komfort.
|
||||||
|
|
||||||
|
### `GET /time/public/company`
|
||||||
|
|
||||||
|
Firmen-Header für die Stempel-Seite (Name + `enabled`-Flag). Query-Param `t`
|
||||||
|
(Token aus dem QR-Code). Liefert auch bei deaktiviertem Feature den Namen,
|
||||||
|
damit die Seite einen Hinweis anzeigen kann.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** öffentlich (kein Auth), rate-limited 30/min
|
||||||
|
|
||||||
|
**Response:** `PublicStampCompanyInfo`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /time/public/auth`
|
||||||
|
|
||||||
|
Personalnummer + PIN + Token → 120s-Session + aktueller Stempel-Status.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** öffentlich (kein Auth), rate-limited 10/min
|
||||||
|
|
||||||
|
**Response:** `PublicStampAuthResponse`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /time/public/action`
|
||||||
|
|
||||||
|
Stempel-Aktion (`in`/`out`/`break_start`/`break_end`) über eine gültige
|
||||||
|
Kurz-Session ausführen.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** öffentlich (kein Auth, gültige Session-Token nötig), rate-limited 30/min
|
||||||
|
|
||||||
|
**Response:** `PublicStampActionResponse`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## iCal-Abo-Feed
|
||||||
|
|
||||||
|
Router-Präfix: `/absences` (Feed) bzw. `/users/me` (Token-Verwaltung)
|
||||||
|
|
||||||
|
Abonnierbarer, read-only `text/calendar`-Feed pro Nutzer (Outlook/Apple/Google
|
||||||
|
pollen die URL selbst) – Ergänzung zum CalDAV-Client, der Events aktiv nach
|
||||||
|
Nextcloud pusht. **Kein JWT**: Kalender-Apps können kein Bearer-Token schicken,
|
||||||
|
Zugang läuft ausschließlich über ein geheimes, rotierbares Token in der URL
|
||||||
|
(gehasht in `users.ical_token_hash`). Feed zeigt ausschließlich die eigenen
|
||||||
|
Abwesenheiten (APPROVED/FIRST_APPROVED/CANCELLATION_REQUESTED) des
|
||||||
|
Token-Inhabers, rein lesend.
|
||||||
|
|
||||||
|
### `GET /absences/ical/{token}.ics`
|
||||||
|
|
||||||
|
Öffentlicher Feed – liefert eine `.ics`-Datei mit den bestätigten/laufenden
|
||||||
|
Abwesenheiten des zum Token gehörenden Nutzers.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** öffentlich (kein JWT, Token in URL)
|
||||||
|
|
||||||
|
**Response:** `text/calendar`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /users/me/ical-token`
|
||||||
|
|
||||||
|
Status abfragen, ob der Feed aktiviert ist. Das Token selbst wird nie
|
||||||
|
zurückgegeben (nur der Hash ist gespeichert).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** authentifiziert (jede Rolle)
|
||||||
|
|
||||||
|
**Response:** `{ "enabled": true|false }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /users/me/ical-token`
|
||||||
|
|
||||||
|
Erzeugt ein neues Token (rotiert ein bestehendes) und liefert die Feed-URL
|
||||||
|
**einmalig** zurück.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** authentifiziert (jede Rolle)
|
||||||
|
|
||||||
|
**Response:** `{ "enabled": true, "url": "https://.../api/v1/absences/ical/<token>.ics" }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /users/me/ical-token`
|
||||||
|
|
||||||
|
Feed deaktivieren (Token löschen).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** authentifiziert (jede Rolle)
|
||||||
|
|
||||||
|
**Response:** `204 No Content`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Reseller-Selfservice
|
||||||
|
|
||||||
|
Router-Präfix: `/reseller`
|
||||||
|
|
||||||
|
Ein Reseller verwaltet ausschließlich die **von ihm selbst angelegten Firmen**
|
||||||
|
(Firmen-Ebene: Name, Slug, Plan, Aktiv-Status, erster COMPANY_ADMIN) – **kein
|
||||||
|
Zugriff auf personenbezogene Zeit-/Abwesenheitsdaten** (bewusste DSGVO-Abgrenzung).
|
||||||
|
RLS über `app.reseller_id` sorgt dafür, dass eine fremde Firmen-ID ins Leere läuft (`404`).
|
||||||
|
|
||||||
|
### `GET /reseller/companies`
|
||||||
|
|
||||||
|
Eigene Firmen mit Kennzahlen.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** RESELLER
|
||||||
|
|
||||||
|
**Response:** `list[TenantOut]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /reseller/companies`
|
||||||
|
|
||||||
|
Neue Firma anlegen (inkl. erstem COMPANY_ADMIN). Ohne E-Mail: interne
|
||||||
|
Login-Kennung + einmaliges Temp-Passwort, sofort aktiv.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** RESELLER
|
||||||
|
|
||||||
|
**Response `201`:** `TenantOut` (enthält bei Temp-Passwort-Flow `initial_password` einmalig)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PATCH /reseller/companies/{company_id}`
|
||||||
|
|
||||||
|
Eigene Firma bearbeiten. Schreibt kurzzeitig mit RLS-Bypass (nach
|
||||||
|
Python-seitiger Eigentümerprüfung), da der Reseller keine `app.company_id` besitzt.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** RESELLER (nur eigene Firma, sonst `404`)
|
||||||
|
|
||||||
|
**Response:** `TenantOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Admin · Mandanten & Reseller
|
||||||
|
|
||||||
|
Router-Präfix: `/admin`
|
||||||
|
|
||||||
|
Nur **SUPER_ADMIN** – läuft mit vollem RLS-Bypass, sieht daher alle Firmen
|
||||||
|
firmenübergreifend.
|
||||||
|
|
||||||
|
### `GET /admin/tenants`
|
||||||
|
|
||||||
|
Alle Firmen mit Kennzahlen (mandantenübergreifend).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `list[TenantOut]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /admin/tenants`
|
||||||
|
|
||||||
|
Neue Firma anlegen, optional direkt einem Reseller zugeordnet (`reseller_id`-Query-Param).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response `201`:** `TenantOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PATCH /admin/tenants/{company_id}`
|
||||||
|
|
||||||
|
Firma bearbeiten (Name, Plan, Aktiv-Status, ...).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `TenantOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PATCH /admin/tenants/{company_id}/reseller`
|
||||||
|
|
||||||
|
Firma einem Reseller zuordnen (oder Zuordnung entfernen mit `reseller_id: null`).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `TenantOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /admin/resellers`
|
||||||
|
|
||||||
|
Alle Reseller mit Anzahl zugeordneter Firmen.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `list[ResellerOut]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /admin/resellers`
|
||||||
|
|
||||||
|
Neuen Reseller anlegen. Mit E-Mail: Einladungs-Mail (7 Tage gültig). Ohne
|
||||||
|
E-Mail: interne Login-Kennung + einmaliges Temp-Passwort, sofort aktiv.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response `201`:** `ResellerOut` (enthält bei Temp-Passwort-Flow `initial_password` einmalig)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PATCH /admin/resellers/{reseller_id}`
|
||||||
|
|
||||||
|
Reseller aktivieren/deaktivieren (`is_active`-Query-Param).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `ResellerOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /admin/run-retention-purge`
|
||||||
|
|
||||||
|
Globaler DSGVO-Purge über **alle** Firmen (Zeiterfassung/Auszahlungen je Firma
|
||||||
|
gemäß konfigurierter Frist, plus firmenübergreifend: `audit_logs` > 3 Jahre,
|
||||||
|
abgelaufene Sessions/Password-Resets). Gleiche Logik wie der tägliche
|
||||||
|
Scheduler-Job, hier manuell für alle Mandanten auf einmal auslösbar.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `{ "deleted": { ... } }`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Sondervertretungen
|
||||||
|
|
||||||
|
Router-Präfix: `/users/{user_id}/special-assignments` (CRUD) bzw. `/reports/special-assignments` (Report)
|
||||||
|
|
||||||
|
Zeiträume mit Vertretungs-/Zuschlagsfaktor (z. B. Führungsvertretung) pro
|
||||||
|
Mitarbeiter, mit Überschneidungs-Prüfung.
|
||||||
|
|
||||||
|
### `GET /users/{user_id}/special-assignments`
|
||||||
|
|
||||||
|
Alle Zuweisungen eines Mitarbeiters.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** MANAGER, HR, COMPANY_ADMIN, SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `list[SpecialAssignmentOut]`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /users/{user_id}/special-assignments`
|
||||||
|
|
||||||
|
Neue Zuweisung anlegen. `409` bei Überschneidung mit vorhandener Zuweisung.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** MANAGER, HR, COMPANY_ADMIN, SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response `201`:** `SpecialAssignmentOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `PATCH /users/{user_id}/special-assignments/{assignment_id}`
|
||||||
|
|
||||||
|
Zuweisung bearbeiten (inkl. erneuter Überschneidungs-Prüfung).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** MANAGER, HR, COMPANY_ADMIN, SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `SpecialAssignmentOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `DELETE /users/{user_id}/special-assignments/{assignment_id}`
|
||||||
|
|
||||||
|
Zuweisung löschen.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** MANAGER, HR, COMPANY_ADMIN, SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `204 No Content`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `GET /reports/special-assignments/payroll`
|
||||||
|
|
||||||
|
Payroll-Report: je Mitarbeiter die Sondervertretungs-Stunden im gewählten
|
||||||
|
Monat (`year`, `month`), inkl. Normal-/Faktor-/Zuschlagsstunden auf Basis
|
||||||
|
genehmigter Zeiterfassungs-Einträge.
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** MANAGER, HR, COMPANY_ADMIN, SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `PayrollAssignmentReport`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Admin · TLS
|
||||||
|
|
||||||
|
Router-Präfix: `/admin/tls`
|
||||||
|
|
||||||
|
**Server-lokal, ohne Mandanten-Kontext** – zeigt/verwaltet ausschließlich das
|
||||||
|
TLS-Zertifikat des Servers, auf dem der Request ankommt (kein
|
||||||
|
Cross-Server-Wissen zwischen 137/164). Läuft mit Root-Rechten
|
||||||
|
(`timemaster.service` läuft als `root`), deshalb strikt auf SUPER_ADMIN
|
||||||
|
begrenzt, plus AuditLog und Rate-Limit auf den Renewal-Endpunkten.
|
||||||
|
|
||||||
|
### `GET /admin/tls/status`
|
||||||
|
|
||||||
|
Aktueller Zertifikats-Status (Modus, Domain, gültig bis, erneuerbar).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** SUPER_ADMIN
|
||||||
|
|
||||||
|
**Response:** `TlsStatusOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /admin/tls/renew/certbot`
|
||||||
|
|
||||||
|
Zertifikat per Certbot erneuern (öffentliche Domain).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** SUPER_ADMIN, rate-limited 5/h
|
||||||
|
|
||||||
|
**Request:** `{ "domain": "timemaster.example.com" }`
|
||||||
|
|
||||||
|
**Response:** `TlsStatusOut`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `POST /admin/tls/renew/internal`
|
||||||
|
|
||||||
|
Internes Zertifikat erneuern (Hostname/IP, kein öffentlicher Certbot-Weg).
|
||||||
|
|
||||||
|
- **Erforderliche Rolle:** SUPER_ADMIN, rate-limited 5/h
|
||||||
|
|
||||||
|
**Request:** `{ "hostname": "timemaster", "ip": "192.168.1.137" }`
|
||||||
|
|
||||||
|
**Response:** `TlsStatusOut`
|
||||||
|
|||||||
Reference in New Issue
Block a user