Files
2026-11-04 07:55:25 +00:00

81 lines
4.2 KiB
PHP

<?php
/**
* Erinnerung an den Bearbeiter: Wartet ein ihm zugewiesenes Ticket seit mehreren Arbeitsstunden auf eine Antwort, bekommt er einen Hinweis
* (Push auf die Android-App und im Browser, auf Wunsch auch per E-Mail), bevor die SLA-Frist reißt. Pro Kundennachricht gibt es nur eine
* Erinnerung. Gezählt wird in Arbeitszeit (Geschäftszeiten und Feiertage aus der SLA-Einstellung, sonst die Uhr).
* Läuft im Hintergrundjob „SLA-Prüfung“ (cron/check_sla.php, alle 15 Minuten).
*/
function wait_reminder_enabled(): bool
{
return get_setting('wait_reminder_enabled', '1') === '1';
}
function wait_reminder_hours(): int
{
return max(1, min(72, (int) get_setting('wait_reminder_hours', '4')));
}
function wait_reminder_email(): bool
{
return get_setting('wait_reminder_email', '0') === '1';
}
/**
* Prüft alle zugewiesenen, unbeantworteten Tickets und erinnert die Bearbeiter.
* @return array{checked:int,sent:int,log:string[]}
*/
function wait_reminders_run(PDO $pdo, ?string $now = null): array
{
$res = ['checked' => 0, 'sent' => 0, 'log' => []];
if (!wait_reminder_enabled()) return $res;
$now = $now ?? db_now();
$needSeconds = wait_reminder_hours() * 3600;
$snooze = function_exists('ticket_not_snoozed_sql') ? ticket_not_snoozed_sql('t') : '1=1';
$stmt = $pdo->prepare(
"SELECT t.*, s.name AS staff_name, s.email AS staff_email,
COALESCE((SELECT MAX(th.created_at) FROM ticket_threads th WHERE th.ticket_id = t.id AND th.author_type = 'customer'), t.created_at) AS last_customer_at
FROM tickets t
JOIN staff s ON s.id = t.assigned_staff_id AND s.is_active = 1
WHERE t.status = 'open' AND t.is_spam = 0 AND t.merged_into IS NULL AND t.source <> 'staff'
AND t.sla_paused_at IS NULL AND t.is_overdue = 0
AND (t.resolution_due_at IS NULL OR t.resolution_due_at > ?)
AND {$snooze}
ORDER BY t.id
LIMIT 500"
);
$stmt->execute([$now]);
$mark = $pdo->prepare('UPDATE tickets SET wait_reminded_at = ?, updated_at = updated_at WHERE id = ?');
foreach ($stmt->fetchAll() as $t) {
$res['checked']++;
$since = (string) $t['last_customer_at'];
// Pro Kundennachricht nur eine Erinnerung
if (!empty($t['wait_reminded_at']) && (string) $t['wait_reminded_at'] >= $since) continue;
$waited = ticket_sla_working_seconds_between($since, $now);
if ($waited < $needSeconds) continue;
$mark->execute([$now, (int) $t['id']]);
$hours = (int) floor($waited / 3600);
$assigned = (int) $t['assigned_staff_id'];
log_activity($pdo, (int) $t['id'], null, "Erinnerung an den Bearbeiter: wartet seit {$hours} Std. auf Antwort");
if (function_exists('fcm_notify_staff') && fcm_configured()) {
fcm_notify_staff([$assigned], fcm_data('ticket_wait', $t), (int) $t['department_id']);
}
if (function_exists('push_to_staff')) {
[$title, $body] = function_exists('fcm_message') ? fcm_message('ticket_wait', $t) : ['Ticket wartet auf Antwort', '#' . $t['ticket_number']];
try { push_to_staff($assigned, $title, $body, '/staff/ticket_view.php?n=' . urlencode((string) $t['ticket_number'])); } catch (Throwable $e) { error_log('wait_reminder push: ' . $e->getMessage()); }
}
if (wait_reminder_email() && trim((string) $t['staff_email']) !== '') {
$url = base_url() . '/staff/ticket_view.php?n=' . urlencode((string) $t['ticket_number']);
$inner = email_p('Hallo ' . e(explode(' ', trim((string) $t['staff_name']))[0]) . ', das Ticket <strong>#' . e((string) $t['ticket_number']) . '</strong> „' . e(mb_strimwidth((string) $t['subject'], 0, 80, '…')) . '“ wartet seit etwa ' . $hours . ' Stunden auf deine Antwort.')
. email_button('Ticket öffnen', $url);
email_send_and_log((string) $t['staff_email'], 'Ticket #' . $t['ticket_number'] . ' wartet auf deine Antwort', email_wrap($inner), null, 'Erinnerung-an-Bearbeiter-Mail', (string) $t['ticket_number']);
}
$res['sent']++;
$res['log'][] = "[ok] Erinnerung an {$t['staff_name']}: Ticket #{$t['ticket_number']} wartet seit {$hours} Std.";
}
return $res;
}