Files
awo-hamburg-intranet/module/tasks/doc/task-cert-module-lifecycle.md

340 lines
13 KiB
Markdown
Raw Normal View History

# Task-Cert Modul: Architektur und Lifecycle
## Zweck
Dieses Modul verwaltet Aufgaben inkl. Zielgruppen-Zuweisung, Erledigungsmethoden, Freigabeprozess, Zertifikaten, Eskalationen und Monitoring.
## Verzeichnisstruktur
- `bootstrap.php`: Initialisierung, DI, URL-Mapping, Asset-Helper.
- `pages/`: Views (Admin + User).
- `actions/`: View-Controller (Datenaufbereitung pro Seite).
- `endpoints/`: Schreibende Endpunkte (POST/CLI).
- `service/`: Business-Logik.
- `repo/`: DB-Zugriff.
- `sql/`: Schema + Migrationen.
## Zentrale Services
- `TaskDefinitionService`: Aufgabe anlegen/ändern, Reset aktueller Zyklus, Soft-/Hard-Delete, Audit-Log.
- `AssignmentResolverService`: materialisiert Zuweisungen aus Target-Regeln + Overrides.
- `CycleCalculatorService`: berechnet Zyklus (`cycle_key`) und Fälligkeit (`due_at`) für kalender- und erledigungsbasierte Wiederholung.
- `CompletionService`: verarbeitet User-Einreichungen und bewertet Methodenabschluss.
- `ApprovalService`: Admin-Freigabe/Ablehnung manueller Einreichungen.
- `CertificateService`: Zertifikate ausstellen/widerrufen/ablaufen lassen.
- `EscalationService`: setzt Überfällig-Status und Eskalationslevel.
- `TaskMonitoringService`: Admin-Detaildashboard (KPIs, Freigaben, Überschreitungen, Antworten).
## Datenmodell (Kernobjekte)
- `task_definition`: Aufgabenstammdaten inkl. Kategorie, Rhythmus, Rhythmus-Basis (`calendar|completion`), Fälligkeitslogik, Zertifikatsmodus.
- `task_category`: Kategorien inkl. Admin-CRUD (inline) und Schutzregeln.
- `task_category_change_log`: Audit-Log für Kategorieänderungen.
- `task_method` + Untertabellen:
- `task_method_knowledge_post`
- `task_form_field`
- `task_quiz_question`
- `task_quiz_option`
- `task_target_rule` + `task_target_override`: Zielgruppenlogik.
- `task_assignment`: Instanz pro Mitarbeiter + Zyklus (Status, Due-Date, Eskalation).
- `task_submission`: Einreichungen pro Methode.
- `task_certificate`: Zertifikate pro Assignment.
- `task_reminder_policy`: Eskalationsregeln.
- `task_escalation_event`: Historie der Eskalationslevel-Events.
- `task_change_log`: Audit-Log für Task-Konfigurationsänderungen.
Referenz: `sql/init.sql`
## Statusmodelle
### Assignment-Status (`task_assignment.status`)
- `open` -> `in_progress` -> `completed`
- Sonder-/Nebenpfade: `approval_pending`, `overdue`, `rejected`, `expired`
### Submission-Status (`task_submission.status`)
- `submitted`, `auto_passed`, `awaiting_approval`, `approved`, `rejected`
Wichtig:
- `escalation_level = 0` bedeutet nur „keine aktive Eskalationsstufe“.
- Ein Assignment kann trotzdem überfällig sein (z. B. `status = overdue`, Eskalation noch nicht gestartet).
## Lifecycle Ende-zu-Ende
### 1) Aufgabe erstellen/ändern (Admin)
1. Formular: `pages/admin/task_form.php`
2. Endpoint: `endpoints/admin/task_upsert.php`
3. Service:
- `TaskDefinitionService::createTask()` oder `updateTask()`
- Speichert Definition + Regeln (`TaskRuleRepository::replaceTaskRules`)
- Schreibt Audit-Eintrag (`task_change_log`)
4. Danach Materialisierung:
- `AssignmentResolverService::materializeAssignmentsForTask()`
### 2) Zuweisungen materialisieren
`AssignmentResolverService`:
- Lädt aktive Aufgabe + Rhythmus-Kontext.
- Ermittelt Zielkontakte über `task_target_rule` und `task_target_override`.
- Erzeugt/aktualisiert `task_assignment` mit `cycle_key`, `cycle_start_at`, `cycle_end_at`, `due_at`.
- Modus `calendar`: wie bisher zykluskalenderbasiert.
- Modus `completion`: pro Mitarbeiter individueller Folgeturnus ab letzter Erledigung (ohne Vorziehen bei früher Erledigung).
Zusätzlicher Sammel-Endpoint:
- `endpoints/admin/materialize_assignments.php` (POST/CLI): für alle aktiven Aufgaben.
### 3) Nutzer bearbeitet Aufgabe
User-Endpunkte:
- `endpoints/user/submit_read.php`
- `endpoints/user/submit_form.php`
- `endpoints/user/submit_quiz.php`
- `endpoints/user/request_manual_approval.php`
`CompletionService`:
- validiert Assignment + Methode + Berechtigung,
- legt `task_submission` an,
- entscheidet danach über Assignment-Fortschritt (`finalizeAssignmentState`).
### 4) Freigabeprozess
Bei manueller Freigabe:
- Submission wird mit `awaiting_approval` angelegt.
- Admin entscheidet über `endpoints/admin/approval_decision.php`.
- `ApprovalService::approveSubmission()` -> `CompletionService::refreshAssignmentAfterReview()`.
- `ApprovalService::rejectSubmission()` -> Assignment wird `rejected`.
### 5) Abschluss und Zertifikat
Wenn erforderliche Methoden erfüllt sind (`fulfillment_mode = all|any`):
- Assignment wird `completed`.
- `CertificateService::issueForAssignment()` erstellt Zertifikat, falls aktiviert.
- Bei Zertifikatsmodus `cycle` gilt:
- `valid_until = assignment.cycle_end_at`
- dadurch ist die Gültigkeit automatisch an die gewählte Rhythmus-Basis gekoppelt.
## Rhythmus-Basis bei wiederkehrenden Aufgaben
- `calendar`:
- Zyklus folgt festen Kalenderintervallen ab `start_date`.
- `completion`:
- pro Mitarbeiter individuell,
- neue Periode erst nach `completed_at`,
- kein Vorziehen bei früher Erledigung,
- erste Periode für neu zugewiesene Mitarbeiter startet am Zuweisungstag (bzw. nicht vor `start_date`).
Hinweis fürs Admin-Monitoring:
- Für `completion` zeigt das Dashboard den aktuellen Stand je Mitarbeiter (ohne Cycle-Key-Auswahl).
### 6) Überfälligkeit und Eskalation
`EscalationService::recalculateEscalations()`:
- setzt bei überfälligen Assignments `status = overdue`,
- berechnet Eskalationsstufe nach `task_reminder_policy`:
- `first_escalation_days`
- `step_days`
- `max_level`
- schreibt Events nach `task_escalation_event`.
Aktueller Betriebsmodus:
- Recalc läuft nur bei explizitem Trigger (Admin-Button oder Endpoint):
- `actions/admin/escalation_dashboard_action.php` via `?recalc=1`
- `endpoints/admin/recalculate_escalations.php` (POST/CLI)
- Für Automatik ist ein externer Job (z. B. Cron) nötig.
### 7) Reset / Löschen
#### Reset aktueller Zyklus
`TaskDefinitionService::resetCurrentCycleProgress()`:
- widerruft Zertifikate (`task_certificate.status = revoked`),
- löscht Einreichungen,
- setzt Assignments zurück (`status=open`, `overdue_at=NULL`, `escalation_level=0`, `last_escalated_at=NULL`),
- schreibt Audit-Eintrag `reset_cycle_progress`.
Hinweis:
- Wenn `due_at` weiterhin in der Vergangenheit liegt, kann eine spätere Eskalations-Neuberechnung wieder `overdue`/Eskalation setzen.
#### Soft Delete
- Endpoint: `endpoints/admin/task_delete.php`
- Wirkung: Aufgabe deaktivieren (`active = 0`), Daten bleiben erhalten.
#### Hard Delete
- Endpoint: `endpoints/admin/task_delete_hard.php`
- Wirkung: vollständiges Löschen inkl. abhängiger Datensätze (per FK-Cascade).
## Audit / Nachvollziehbarkeit
`task_change_log` wird derzeit für folgende Actions geschrieben:
- `create`
- `update`
- `reset_cycle_progress`
- `soft_delete`
- `hard_delete`
Payloads:
- `before_json`, `after_json`, `diff_json`
- Zugriff über `TaskChangeLogRepository` (derzeit ohne dedizierte Admin-UI).
## Views und Routen
Routen-Mapping in `bootstrap.php` (`task_cert_view_url`):
- Admin:
- `admin-task-list`
- `edit`
- `view`
- `approvals`
- `escalations`
- `categories`
- User:
- `my-tasks`
- `task`
- `overdue-widget`
## CLI/Cron Beispiele
Alle Beispiele gehen davon aus, dass der Job im Modul-Verzeichnis ausgeführt wird:
`/tasks`
### Cron-CLI Jobs
#### 1) Assignments materialisieren
```bash
php cli/task_cert_materialize_assignments.php
```
Optional:
- `--no-lock` (nur Debug)
- `--json` (Summary zusätzlich auf stdout)
Operation ist idempotent (kein --apply nötig).
#### 2) Zuweisungen synchronisieren
```bash
php cli/task_cert_sync_assignments.php --apply
```
Optional:
- `--dry-run` (keine Änderungen schreiben)
- `--task_id=123` (nur eine Aufgabe)
- `--no-lock` (nur Debug)
- `--json` (Summary zusätzlich auf stdout)
#### 3) Eskalationen neu berechnen
```bash
php cli/task_cert_recalculate_escalations.php --apply
```
Optional:
- `--dry-run` (keine Änderungen schreiben)
- `--task_id=123` (nur eine Aufgabe)
- `--no-lock` (nur Debug)
- `--json` (Summary zusätzlich auf stdout)
### Exit-Codes der CLI Jobs
- `0`: Erfolg
- `1`: Lauf mit Fehler beendet
- `2`: Ungültige CLI-Parameter
- `3`: Lauf übersprungen (Lock bereits aktiv)
### CLI-Ausgabe
Jeder Lauf schreibt eine Zeile auf stdout bzw. stderr:
- `[OK] sync_assignments (apply) abgeschlossen in 312ms.`
- `[SKIP] sync_assignments: Lock aktiv, Job wird übersprungen.`
- `[ERROR] sync_assignments (apply): Fehlermeldung`
### Locking und Cron-Log
- Lockfiles:
- `logs/locks/task_cert_materialize_assignments.lock`
- `logs/locks/task_cert_sync_assignments.lock`
- `logs/locks/task_cert_recalculate_escalations.lock`
- Separates Cron-Log (JSON-Lines):
- `logs/task_cert_cron.log`
### Cron-Empfehlung
```cron
# Assignments materialisieren: alle 15 Minuten
*/15 * * * * cd /tasks && php cli/task_cert_materialize_assignments.php
# Eskalationen berechnen: alle 30 Minuten
*/30 * * * * cd /tasks && php cli/task_cert_recalculate_escalations.php --apply
# Zuweisungs-Sync: täglich um 02:00
0 2 * * * cd /tasks && php cli/task_cert_sync_assignments.php --apply
```
### Legacy-Endpoints (nur noch als HTTP-Endpoints für Admin-UI)
- `endpoints/admin/materialize_assignments.php`
- `endpoints/admin/sync_assignments.php`
- `endpoints/admin/recalculate_escalations.php`
Diese sollten **nicht mehr per Cron** aufgerufen werden (kein Locking, keine CLI-Ausgabe).
## Betriebsempfehlungen
1. Für produktiven Betrieb einen periodischen Recalc-Job einrichten:
- `cli/task_cert_sync_assignments.php`
- `cli/task_cert_recalculate_escalations.php`
2. Zeitzonen konsistent halten (Schema nutzt UTC-Default).
3. Vor Migrationen Backup einplanen (insb. Kategorie- und Lifecycle-Migrationen).
4. Bei UI-Entscheidungen klar trennen:
- „Überfällig“ vs. „Eskalation“ (nicht dasselbe).
## Schnelle Troubleshooting-Checks
- Eskalation fehlt:
- `reminder_active = 1`?
- `due_at` gesetzt und in Vergangenheit?
- Recalc ausgelöst?
- In Eskalationsliste „zu viel“ sichtbar:
- Prüfen, ob Filter auf `escalation_level > 0` gesetzt ist.
- Reset scheinbar ohne Effekt:
- Prüfen, ob `due_at` weiterhin alt ist und Recalc erneut `overdue` setzt.
## Neue Features (Stand 2026-03-18)
### Karenzzeit (Grace Period)
- **Feld:** `task_reminder_policy.grace_days` (Default: 7)
- **Feld:** `task_assignment.grace_until` (berechnet bei Erstellung: `now() + grace_days`)
- **Logik:** Eskalation greift erst wenn `now > max(due_at, grace_until)`
- **Zweck:** Verhindert sofortige Eskalation bei Späteinsteigern im Zyklus
- **Migration:** `sql/migrations/2026-03-18_add_grace_period.sql`
### Formular-Vorbefüllung (Prefill)
- **Feld:** `task_form_field.prefill_column` (varchar, nullable)
- **Whitelist:** `lib/ContactPrefillColumns.php` definiert erlaubte `main_contact`-Spalten
- **Aktuell erlaubt:** `name`, `email`, `vehicle_registration_place`
- **Verhalten:** Bei Typ `text` wird der Wert aus dem Kontakt des Nutzers als Default gesetzt (überschreibbar)
- **Migration:** `sql/migrations/2026-03-18_add_form_field_prefill.sql`
### Verantwortliche (Responsible Contacts)
- **Tabelle:** `task_responsible` (N:M zwischen `task_definition` und `main_contact`)
- **Admin:** Multiselect im Stammdaten-Tab
- **User:** Ansprechpartner-Section im Aufgaben-Detail (Name + E-Mail)
- **Migration:** `sql/migrations/2026-03-17_add_task_responsible.sql`
### Completion-Guard
- Backend: `CompletionService::validateContext()` blockiert Einreichungen bei `status = completed|expired`
- Frontend: Alle Submit-Buttons disabled bei abgeschlossenen Assignments
### Sofortige Folgezyklus-Erstellung
- Bei erledigungsbasierten Aufgaben erstellt `CompletionService::finalizeAssignmentState()` nach Completion direkt den nächsten Zyklus
- Deduplizierung: `createIfNotExists` + DB Unique-Key verhindert Doppel-Erstellung durch Cron
### Cron-Heartbeat-Monitoring
- `lib/CronHeartbeat.php`: Schreibt JSON-Dateien nach `logs/heartbeat/` bei erfolgreichem Cron-Lauf
- Admin-Aufgabenliste zeigt letzten erfolgreichen Lauf pro Job
- Jobs: `materialize_assignments`, `recalculate_escalations`, `sync_assignments`
### Einreichungs-Historie
- User sieht alle bisherigen Einreichungen (nicht nur die letzte) im Aufgaben-Detail
- Neueste Einreichung direkt sichtbar, ältere aufklappbar
- Formular-Antworten formatiert als Key-Value-Liste (nicht JSON)
### Lesestatus-Persistenz
- Bei Wissensbeiträgen: bereits gelesene Beiträge bleiben im Select vorausgewählt
- Grüner "Gelesen"-Badge neben dem Beitragstitel
### Admin-Monitoring Übersicht
- Fortschrittsbalken statt 8 KPI-Kacheln
- Handlungsbedarf-Cards nur bei Überfällig/Freigaben/Eskalationen
- Rhythmus-Badge (Kalender/Erledigungsbasiert) in Task-Liste und Detail-Header
### Mitarbeiter-Aufgabensuche (Admin)
- Kontakt-Dropdown in der Admin-Aufgabenliste
- Zeigt alle Assignments des gewählten Mitarbeiters mit Status, Daten, Zyklus
- Default: Erster Kontakt vorausgewählt
## Utility-Klassen
### `lib/ContactPrefillColumns.php`
Zentrale Whitelist für vorbefüllbare Kontaktspalten.
- `MAP`: Spaltenname → Label (für Admin-Dropdown)
- `sanitize()`: Validiert gegen Whitelist
- `resolve()`: Holt Wert aus Kontakt-Array
- Erweiterbar: Neue Spalten in `MAP` ergänzen
### `lib/CronHeartbeat.php`
Dateibasiertes Monitoring für Cron-Jobs.
- `record($jobName, $summary)`: Schreibt `logs/heartbeat/{job}.json`
- `readAll()`: Liest alle Heartbeats für Admin-Anzeige
- Kein DB-Schema nötig