Files
awo-hamburg-intranet/module/tasks/doc/task-cert-module-lifecycle.md
Moritz Weinmann 743476f64a Module tasks + knowledgecenter_update aus Kundenprojekt übernehmen (bereinigt)
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>
2026-06-22 09:20:02 +02:00

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_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

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: 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

# 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