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:
@@ -24,6 +24,13 @@ class HelpdeskSettingsGateway
|
||||
public const KEY_OAUTH_TOKEN_ENDPOINT = 'helpdesk.bc_oauth_token_endpoint';
|
||||
public const KEY_SYSTEM_RECOMMENDATIONS_CONFIG = 'helpdesk.system_recommendations_config';
|
||||
public const KEY_CONTROLLING_RISK_CONFIG = 'helpdesk.controlling_risk_config';
|
||||
/** @api Read in helpdesk/settings + analytics pages */
|
||||
public const KEY_INACTIVITY_THRESHOLD_DAYS = 'helpdesk.inactivity_threshold_days';
|
||||
|
||||
/** @api Default surfaced in views + tests */
|
||||
public const DEFAULT_INACTIVITY_THRESHOLD_DAYS = 60;
|
||||
private const MIN_INACTIVITY_THRESHOLD_DAYS = 7;
|
||||
private const MAX_INACTIVITY_THRESHOLD_DAYS = 365;
|
||||
|
||||
public const AUTH_MODE_BASIC = 'basic';
|
||||
public const AUTH_MODE_OAUTH2 = 'oauth2';
|
||||
@@ -518,6 +525,49 @@ class HelpdeskSettingsGateway
|
||||
return $normalized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Days of silence after which a customer is flagged as "no contact".
|
||||
* Clamped to a sane range; falls back to the default on invalid/empty values.
|
||||
*
|
||||
* @api Used by helpdesk/analytics + helpdesk/settings pages
|
||||
*/
|
||||
public function getInactivityThresholdDays(): int
|
||||
{
|
||||
$value = $this->settingsMetadataGateway->getValue(self::KEY_INACTIVITY_THRESHOLD_DAYS);
|
||||
$value = trim((string) ($value ?? ''));
|
||||
if ($value === '' || !is_numeric($value)) {
|
||||
return self::DEFAULT_INACTIVITY_THRESHOLD_DAYS;
|
||||
}
|
||||
|
||||
return max(
|
||||
self::MIN_INACTIVITY_THRESHOLD_DAYS,
|
||||
min(self::MAX_INACTIVITY_THRESHOLD_DAYS, (int) $value)
|
||||
);
|
||||
}
|
||||
|
||||
/** @api Used by the helpdesk/settings page */
|
||||
public function setInactivityThresholdDays(?int $days): bool
|
||||
{
|
||||
if ($days === null) {
|
||||
return $this->settingsMetadataGateway->set(
|
||||
self::KEY_INACTIVITY_THRESHOLD_DAYS,
|
||||
null,
|
||||
'helpdesk.inactivity_threshold_days'
|
||||
);
|
||||
}
|
||||
|
||||
$clamped = max(
|
||||
self::MIN_INACTIVITY_THRESHOLD_DAYS,
|
||||
min(self::MAX_INACTIVITY_THRESHOLD_DAYS, $days)
|
||||
);
|
||||
|
||||
return $this->settingsMetadataGateway->set(
|
||||
self::KEY_INACTIVITY_THRESHOLD_DAYS,
|
||||
(string) $clamped,
|
||||
'helpdesk.inactivity_threshold_days'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the full OData entity URL for a given entity set.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user