feat(helpdesk): add customer engagement analytics page [skip ci]

New "Customer analytics" page under Monitoring: surfaces who is actively in
contact vs. who has gone quiet, so the team can reach out before a customer
slips away.

- KPI tiles: total / active / no-contact / never-in-contact
- "Customers without recent contact" — longest silence first
- "Top customers by communication" — most ticket activity in the period
- Configurable no-contact threshold (helpdesk settings, default 60 days)

CustomerEngagementService aggregates the global ticket snapshot per customer
(same single-pull model as RiskRadarService — no N+1 to BC). Last contact is
the most recent ticket activity; silence beyond the threshold flags the
customer. Revenue ranking deferred (BC OData exposes no per-customer ledger).

All within modules/helpdesk/. Tests + PHPStan green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
aminovfariz
2026-06-25 10:30:05 +02:00
parent 2924407450
commit 14da36e83a
16 changed files with 1150 additions and 46 deletions

View File

@@ -130,9 +130,9 @@
"Close rate": "Close-Rate",
"Avg. resolution": "Ø Lösungszeit",
"Open backlog": "Offene Tickets",
"kpi_tooltip_close_rate": "Geschlossene \u00f7 erstellte Tickets im Zeitraum. \u00dcber 1\u00d7 = R\u00fcckstau wird abgebaut.",
"kpi_tooltip_close_rate": "Geschlossene ÷ erstellte Tickets im Zeitraum. Über 1× = Rückstau wird abgebaut.",
"kpi_tooltip_resolution": "Median der Bearbeitungsdauer aller im Zeitraum geschlossenen Tickets.",
"kpi_tooltip_backlog": "Aktueller Stand aller offenen Tickets \u2013 unabh\u00e4ngig vom gew\u00e4hlten Zeitraum. Zeigt den R\u00fcckstau.",
"kpi_tooltip_backlog": "Aktueller Stand aller offenen Tickets unabhängig vom gewählten Zeitraum. Zeigt den Rückstau.",
"Backlog shrinking": "Rückstau wird abgebaut",
"Backlog growing": "Rückstau wächst",
"unassigned": "ohne Bearbeiter",
@@ -372,7 +372,7 @@
"Back to list": "Zurück zur Liste",
"Save & close": "Speichern & schließen",
"Software product updated": "Software-Produkt aktualisiert",
"Software product not found": "Software-Produkt nicht gefunden",
"Software product not found": "Softwareprodukt nicht gefunden",
"Product details": "Produktdetails",
"Enter product name...": "Produktname eingeben...",
"Name must not exceed 255 characters": "Name darf maximal 255 Zeichen lang sein",
@@ -436,8 +436,6 @@
"Create handover": "Übergabe erstellen",
"Continue": "Weiter",
"Cancel": "Abbrechen",
"Back": "Zurück",
"Back to list": "Zurück zur Liste",
"Please select a customer": "Bitte wählen Sie einen Kunden",
"Please select a software product": "Bitte wählen Sie ein Softwareprodukt",
"Software product has no handover protocol schema": "Softwareprodukt hat kein Übergabeprotokoll-Schema",
@@ -445,27 +443,22 @@
"Please complete step 1 first": "Bitte schließen Sie zuerst Schritt 1 ab",
"Debitor number is required": "Debitornummer ist erforderlich",
"Software product is required": "Softwareprodukt ist erforderlich",
"Software product not found": "Softwareprodukt nicht gefunden",
"Failed to create handover": "Übergabe konnte nicht erstellt werden",
"You do not have permission to edit this handover": "Sie haben keine Berechtigung, diese Übergabe zu bearbeiten",
"You do not have permission to set this status": "Sie haben keine Berechtigung, diesen Status zu setzen",
"Invalid status": "Ungültiger Status",
"Unknown field: %s": "Unbekanntes Feld: %s",
"%s is required": "%s ist erforderlich",
"Failed to save": "Speichern fehlgeschlagen",
"Failed to update status": "Status konnte nicht aktualisiert werden",
"No protocol fields defined.": "Keine Protokollfelder definiert.",
"Please select...": "Bitte auswählen...",
"Handover protocol": "Übergabeprotokoll",
"Change status": "Status ändern",
"Customer": "Kunde",
"Debitor No.": "Debitornr.",
"Progress": "Fortschritt",
"Created by": "Erstellt von",
"Search handovers...": "Übergaben suchen...",
"ID": "ID",
"History": "Verlauf",
"Current": "Aktuell",
"Viewing version %d": "Version %d wird angezeigt",
"compared with version %d": "verglichen mit Version %d",
"Back to current": "Zurück zur aktuellen Version",
@@ -475,7 +468,6 @@
"Failed to restore": "Wiederherstellen fehlgeschlagen",
"Only managers can restore revisions": "Nur Manager können Versionen wiederherstellen",
"Revision not found": "Version nicht gefunden",
"Created": "Erstellt",
"Fields changed": "Felder geändert",
"Status changed": "Status geändert",
"Fields and status changed": "Felder und Status geändert",
@@ -521,11 +513,8 @@
"Ticket status": "Ticketstatus",
"Assignment": "Zuweisung",
"Resolved": "Erledigt",
"Closed": "Geschlossen",
"Open": "Offen",
"Assign update": "Update zuweisen",
"Assign": "Zuweisen",
"Unassigned": "Nicht zugewiesen",
"Assigned": "Zugewiesen",
"Update": "Update",
"Hotfix": "Hotfix",
@@ -537,8 +526,6 @@
"Update assigned successfully": "Update erfolgreich zugewiesen",
"Failed to assign update": "Update konnte nicht zugewiesen werden",
"Select domain...": "Domain auswählen...",
"Loading domains...": "Domains werden geladen...",
"No domains found for this customer": "Keine Domains für diesen Kunden gefunden",
"No updates for this domain yet": "Noch keine Updates für diese Domain",
"Search updates...": "Updates suchen...",
"Open in Gitea": "In Gitea öffnen",
@@ -547,9 +534,7 @@
"View software updates list": "Software-Updates-Liste anzeigen",
"Assign and manage software updates": "Software-Updates zuweisen und verwalten",
"Security level": "Sicherheitsstufe",
"Low": "Niedrig",
"Normal": "Normal",
"High": "Hoch",
"Critical": "Kritisch",
"Save failed": "Speichern fehlgeschlagen",
"Invalid CSRF token": "Ungültiges CSRF-Token",
@@ -557,13 +542,10 @@
"Missing domain number": "Domain-Nummer fehlt",
"Invalid security level": "Ungültige Sicherheitsstufe",
"Failed to save security level": "Sicherheitsstufe konnte nicht gespeichert werden",
"Under review": "In Prüfung",
"Assignment": "Zuweisung",
"Assigned to": "Zugewiesen an",
"Assigned at": "Zugewiesen am",
"Not assigned yet": "Noch nicht zugewiesen",
"Assign": "Zuweisen",
"Change assignment": "Zuweisung ändern",
"Assign handover": "Übergabe zuweisen",
"Assign to": "Zuweisen an",
@@ -586,7 +568,6 @@
"Failed to submit for review": "Einreichen zur Prüfung fehlgeschlagen",
"You are not assigned to this handover": "Sie sind dieser Übergabe nicht zugewiesen",
"Handover cannot be submitted for review in its current status": "Übergabe kann im aktuellen Status nicht zur Prüfung eingereicht werden",
"In progress": "In Bearbeitung",
"Done": "Abgeschlossen",
"Assigned to (column)": "Zugewiesen an",
"How do you want to proceed?": "Wie möchten Sie vorgehen?",
@@ -602,5 +583,24 @@
"Please select an employee to assign": "Bitte wählen Sie einen Mitarbeiter aus",
"Delete handover": "Übergabe löschen",
"Delete this handover?": "Diese Übergabe löschen?",
"Handover deleted": "Übergabe gelöscht"
"Handover deleted": "Übergabe gelöscht",
"Customer analytics": "Kundenanalyse",
"No-contact threshold": "Kein-Kontakt-Schwelle",
"Could not load customer analytics.": "Kundenanalyse konnte nicht geladen werden.",
"No customer data available.": "Keine Kundendaten verfügbar.",
"No contact": "Kein Kontakt",
"Never in contact": "Nie in Kontakt",
"days ago": "Tage her",
"never": "nie",
"Tickets in period": "Tickets im Zeitraum",
"Last contact": "Letzter Kontakt",
"Customers without recent contact": "Kunden ohne jüngsten Kontakt",
"Longest silence first — consider reaching out.": "Längste Funkstille zuerst — Kontaktaufnahme erwägen.",
"Top customers by communication": "Top-Kunden nach Kommunikation",
"Most ticket activity in the selected period.": "Meiste Ticket-Aktivität im gewählten Zeitraum.",
"Every customer has been in contact recently.": "Alle Kunden waren kürzlich in Kontakt.",
"No communication recorded in the selected period.": "Keine Kommunikation im gewählten Zeitraum erfasst.",
"Customer engagement": "Kundenbindung",
"No-contact threshold (days)": "Kein-Kontakt-Schwelle (Tage)",
"Customers without any communication for longer than this are flagged in the customer analytics view.": "Kunden ohne jegliche Kommunikation über diesen Zeitraum hinaus werden in der Kundenanalyse markiert."
}