Merge branch 'master' into fix/tru-88-qr-sms-dispatcher-approval

This commit is contained in:
Jeppe B
2026-08-17 21:03:45 +02:00
committed by GitHub
2 changed files with 111 additions and 1 deletions
+43 -1
View File
@@ -1421,7 +1421,49 @@ function GoalsProgressAlertsCron(): void
case Dest::SLACK: case Dest::SLACK:
$departments = (array)$goal->departments->value(); $departments = (array)$goal->departments->value();
$sentToDept = false; $sentToDept = false;
if (count($departments) > 0) { $internalDepartmentIds = [];
try {
$slackConfig = new Slack();
if (method_exists($slackConfig, 'get_internal_department_ids')) {
$internalDepartmentIds = array_map('intval', (array)$slackConfig->get_internal_department_ids());
}
} catch (Throwable $slackConfigError) {
// Ignore - falls back to per-department webhooks
$internalDepartmentIds = [];
}
$goalDeptIds = [];
foreach ($departments as $deptId) {
if (is_numeric($deptId)) {
$goalDeptIds[] = (int)$deptId;
}
}
$allInternal = count($goalDeptIds) > 0
&& count(array_diff($goalDeptIds, $internalDepartmentIds)) === 0;
if ($allInternal) {
// TRU-76: For internal departments (e.g. Taulov/Taastrup DHL daily
// goal), post to the dedicated internal goal progress webhook
// instead of per-department webhooks, which are typically empty
// for internal locations.
$internalWebhook = '';
try {
$slackInstance = new Slack();
if (method_exists($slackInstance, 'get_internal_department_goal_progress_webhook_url')) {
$internalWebhook = trim((string)$slackInstance->get_internal_department_goal_progress_webhook_url());
}
} catch (Throwable $internalWebhookError) {
$internalWebhook = '';
}
if ($internalWebhook !== '') {
(new Slack())->send_webhook_message((string)goals_progress_alert_renderer::render($criteria), $internalWebhook);
$sentToDept = true;
echo "[" . date('Y-m-d H:i:s') . "][CRON] GoalsProgressAlertsCron: goal #" . $goalId . " sent to internal goal progress webhook (departments: " . implode(',', $goalDeptIds) . ")\n";
} else {
echo "[" . date('Y-m-d H:i:s') . "][CRON] GoalsProgressAlertsCron: goal #" . $goalId . " has only internal departments but internal_department_goal_progress_webhook_url is empty; falling back to per-department webhooks\n";
}
}
if (!$sentToDept && count($departments) > 0) {
foreach ($departments as $deptId) { foreach ($departments as $deptId) {
if (!is_numeric($deptId)) { continue; } if (!is_numeric($deptId)) { continue; }
$dept = (new departments_o())->select((int)$deptId); $dept = (new departments_o())->select((int)$deptId);
@@ -0,0 +1,68 @@
<?php
/**
* TRU-76: DHL daily goal for Taulov/Taastrup was not posting to Slack because
* GoalsProgressAlertsCron only consulted the per-department `slack_webhook`
* field. For "internal" departments (Taulov/Taastrup are configured as
* internal) those per-department webhooks are intentionally empty — the
* dedicated internal goal progress webhook is the right destination.
*
* These tests pin the new dispatch behavior:
* 1. The cron reads internal_department_ids from the Slack config.
* 2. When ALL goal departments are internal AND the dedicated
* internal_department_goal_progress_webhook_url is configured, the cron
* posts to that webhook (not the per-department one).
* 3. When the dedicated webhook is empty, the cron logs a diagnostic
* message and falls back to the per-department webhook loop.
* 4. When the goal includes any non-internal department, the cron skips
* the dedicated webhook entirely and uses the per-department loop.
*/
it('loads internal department ids from the Slack config helper', function (): void {
$content = file_get_contents(app_path('cron/Cron.php'));
expect($content)->not->toBeFalse();
expect($content)->toContain("get_internal_department_ids");
expect($content)->toContain("get_internal_department_goal_progress_webhook_url");
});
it('posts to the dedicated internal goal progress webhook when all goal departments are internal', function (): void {
$content = file_get_contents(app_path('cron/Cron.php'));
expect($content)->not->toBeFalse();
// The new branch should be guarded by an "all internal" check.
expect($content)->toContain('$allInternal');
expect($content)->toContain('count(array_diff($goalDeptIds, $internalDepartmentIds)) === 0');
// It should call send_webhook_message with the dedicated internal URL.
expect($content)->toContain("send_webhook_message((string)goals_progress_alert_renderer::render(\$criteria), \$internalWebhook)");
// It should log a confirmation line referencing the goal id and the
// department list so an operator can verify the message actually went
// somewhere.
expect($content)->toContain('internal goal progress webhook');
expect($content)->toContain('departments: ');
});
it('logs a diagnostic and falls back to per-department webhooks when the internal goal progress webhook is empty', function (): void {
$content = file_get_contents(app_path('cron/Cron.php'));
expect($content)->not->toBeFalse();
expect($content)->toContain('internal_department_goal_progress_webhook_url is empty');
expect($content)->toContain('falling back to per-department webhooks');
// The per-department fallback should still run after the internal-webhook
// branch is skipped.
expect($content)->toContain('$dept->slack_webhook->value()');
});
it('skips the internal goal progress webhook for goals that include any non-internal department', function (): void {
$content = file_get_contents(app_path('cron/Cron.php'));
expect($content)->not->toBeFalse();
// The internal-webhook branch must be guarded by the all-internal check;
// otherwise external customers' goal alerts would be silently redirected
// to the internal Slack channel.
expect($content)->toContain('if ($allInternal) {');
expect($content)->toContain('send_webhook_message((string)goals_progress_alert_renderer::render($criteria), $internalWebhook)');
// The per-department loop must still be reachable for mixed/external goals.
expect($content)->toContain('$sentToDept = false;');
expect($content)->toContain('$dept->slack_webhook->value()');
});