# 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