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>
13 KiB
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_posttask_form_fieldtask_quiz_questiontask_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 = 0bedeutet 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)
- Formular:
pages/admin/task_form.php - Endpoint:
endpoints/admin/task_upsert.php - Service:
TaskDefinitionService::createTask()oderupdateTask()- Speichert Definition + Regeln (
TaskRuleRepository::replaceTaskRules) - Schreibt Audit-Eintrag (
task_change_log)
- Danach Materialisierung:
AssignmentResolverService::materializeAssignmentsForTask()
2) Zuweisungen materialisieren
AssignmentResolverService:
- Lädt aktive Aufgabe + Rhythmus-Kontext.
- Ermittelt Zielkontakte über
task_target_ruleundtask_target_override. - Erzeugt/aktualisiert
task_assignmentmitcycle_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.phpendpoints/user/submit_form.phpendpoints/user/submit_quiz.phpendpoints/user/request_manual_approval.php
CompletionService:
- validiert Assignment + Methode + Berechtigung,
- legt
task_submissionan, - entscheidet danach über Assignment-Fortschritt (
finalizeAssignmentState).
4) Freigabeprozess
Bei manueller Freigabe:
- Submission wird mit
awaiting_approvalangelegt. - Admin entscheidet über
endpoints/admin/approval_decision.php. ApprovalService::approveSubmission()->CompletionService::refreshAssignmentAfterReview().ApprovalService::rejectSubmission()-> Assignment wirdrejected.
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
cyclegilt: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.
- Zyklus folgt festen Kalenderintervallen ab
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
completionzeigt 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_daysstep_daysmax_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.phpvia?recalc=1endpoints/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_atweiterhin in der Vergangenheit liegt, kann eine spätere Eskalations-Neuberechnung wiederoverdue/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:
createupdatereset_cycle_progresssoft_deletehard_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-listeditviewapprovalsescalationscategories
- User:
my-taskstaskoverdue-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
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
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
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: Erfolg1: Lauf mit Fehler beendet2: Ungültige CLI-Parameter3: 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.locklogs/locks/task_cert_sync_assignments.locklogs/locks/task_cert_recalculate_escalations.lock
- Separates Cron-Log (JSON-Lines):
logs/task_cert_cron.log
Cron-Empfehlung
# 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.phpendpoints/admin/sync_assignments.phpendpoints/admin/recalculate_escalations.php
Diese sollten nicht mehr per Cron aufgerufen werden (kein Locking, keine CLI-Ausgabe).
Betriebsempfehlungen
- Für produktiven Betrieb einen periodischen Recalc-Job einrichten:
cli/task_cert_sync_assignments.phpcli/task_cert_recalculate_escalations.php
- Zeitzonen konsistent halten (Schema nutzt UTC-Default).
- Vor Migrationen Backup einplanen (insb. Kategorie- und Lifecycle-Migrationen).
- Bei UI-Entscheidungen klar trennen:
- „Überfällig“ vs. „Eskalation“ (nicht dasselbe).
Schnelle Troubleshooting-Checks
- Eskalation fehlt:
reminder_active = 1?due_atgesetzt und in Vergangenheit?- Recalc ausgelöst?
- In Eskalationsliste „zu viel“ sichtbar:
- Prüfen, ob Filter auf
escalation_level > 0gesetzt ist.
- Prüfen, ob Filter auf
- Reset scheinbar ohne Effekt:
- Prüfen, ob
due_atweiterhin alt ist und Recalc erneutoverduesetzt.
- Prüfen, ob
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.phpdefiniert erlaubtemain_contact-Spalten - Aktuell erlaubt:
name,email,vehicle_registration_place - Verhalten: Bei Typ
textwird 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 zwischentask_definitionundmain_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 beistatus = 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 nachlogs/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 Whitelistresolve(): Holt Wert aus Kontakt-Array- Erweiterbar: Neue Spalten in
MAPergänzen
lib/CronHeartbeat.php
Dateibasiertes Monitoring für Cron-Jobs.
record($jobName, $summary): Schreibtlogs/heartbeat/{job}.jsonreadAll(): Liest alle Heartbeats für Admin-Anzeige- Kein DB-Schema nötig