Beide Module wurden aus einem anderen Kundenprojekt kopiert und bereinigt: Knowledgecenter: - Bild-Cache (1.985 Dateien) geleert, Ordnerstruktur mit .gitkeep behalten - Files-Gallery-Lizenzschlüssel entfernt (config.php) - Kundenspezifische Kategorien gelöscht: Teil I/II/III QM-Struktur, nummerierte QM-Kapitel, Gremien (Präsidium/Finanzausschuss/Landesausschuss), Sitzungsservice-Serie, Protokoll-Abteilungen, Testeinträge - 56 generische Kategorien bleiben (Downloads, Onboarding, Personalthemen …) Tasks: - Alle Betriebsdaten gelöscht (114 Tasks, 914 Submissions, 579 Assignments) - Task-Definitionen und Verlinkungen geleert - Kategoriestruktur bleibt (Allgemein, IT, Personal, Buchhaltung …) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
340 lines
13 KiB
Markdown
340 lines
13 KiB
Markdown
# 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
|