Upload via GUI (258 Dateien)
This commit is contained in:
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
/**
|
||||
* includes/time_tracking.php
|
||||
*
|
||||
* Automatische Zeiterfassung pro Ticket: Sobald ein Mitarbeiter ein Ticket
|
||||
* öffnet, startet eine "Arbeits-Sitzung" (ticket_work_sessions). Beendet er
|
||||
* die Bearbeitung (Antwort/Notiz senden, anderes Ticket öffnen, Logout) oder
|
||||
* bleibt länger als WORK_SESSION_STALE_MINUTES untätig, wird die Sitzung
|
||||
* automatisch als Eintrag in ticket_time_entries protokolliert – ganz ohne
|
||||
* dass jemand manuell die Minuten eintragen muss.
|
||||
*
|
||||
* Kann in Einstellungen über das Setting "auto_time_tracking_enabled"
|
||||
* komplett abgeschaltet werden (Standard: aktiv).
|
||||
*/
|
||||
|
||||
const WORK_SESSION_STALE_MINUTES = 10;
|
||||
|
||||
/** Startet eine neue Arbeits-Sitzung, oder frischt eine bestehende auf (Startzeit bleibt erhalten). */
|
||||
function start_or_touch_work_session(PDO $pdo, int $ticketId, int $staffId): void
|
||||
{
|
||||
if (get_setting('auto_time_tracking_enabled', '1') !== '1') return;
|
||||
|
||||
$now = db_now();
|
||||
$driver = $pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
|
||||
if ($driver === 'sqlite') {
|
||||
$pdo->prepare(
|
||||
'INSERT INTO ticket_work_sessions (ticket_id, staff_id, started_at, last_seen_at) VALUES (?,?,?,?)
|
||||
ON CONFLICT(ticket_id, staff_id) DO UPDATE SET last_seen_at = excluded.last_seen_at'
|
||||
)->execute([$ticketId, $staffId, $now, $now]);
|
||||
} else {
|
||||
$pdo->prepare(
|
||||
'INSERT INTO ticket_work_sessions (ticket_id, staff_id, started_at, last_seen_at) VALUES (?,?,?,?)
|
||||
ON DUPLICATE KEY UPDATE last_seen_at = VALUES(last_seen_at)'
|
||||
)->execute([$ticketId, $staffId, $now, $now]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Schließt eine offene Arbeits-Sitzung ab und protokolliert die verstrichene Zeit
|
||||
* (auf ganze Minuten gerundet, mindestens 1). Sitzungen unter 1 Minute werden
|
||||
* verworfen statt als "0 Min."-Rauschen gespeichert. $endAt erlaubt, statt "jetzt"
|
||||
* den letzten bekannten Aktivitätszeitpunkt zu verwenden (Stale-Sweep).
|
||||
*/
|
||||
function finalize_work_session(PDO $pdo, int $ticketId, int $staffId, ?string $endAt = null): void
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT * FROM ticket_work_sessions WHERE ticket_id = ? AND staff_id = ?');
|
||||
$stmt->execute([$ticketId, $staffId]);
|
||||
$session = $stmt->fetch();
|
||||
if (!$session) return;
|
||||
|
||||
$pdo->prepare('DELETE FROM ticket_work_sessions WHERE ticket_id = ? AND staff_id = ?')->execute([$ticketId, $staffId]);
|
||||
|
||||
$end = $endAt ?? db_now();
|
||||
$minutes = (int) round((strtotime($end) - strtotime($session['started_at'])) / 60);
|
||||
if ($minutes < 1) return;
|
||||
|
||||
$pdo->prepare(
|
||||
'INSERT INTO ticket_time_entries (ticket_id, staff_id, minutes, note, logged_at) VALUES (?,?,?,?,?)'
|
||||
)->execute([$ticketId, $staffId, $minutes, 'Automatisch erfasst', db_now()]);
|
||||
log_activity($pdo, $ticketId, $staffId, "Zeit automatisch erfasst: {$minutes} Min.");
|
||||
}
|
||||
|
||||
/** Schließt alle offenen Sitzungen eines Mitarbeiters außer der auf $exceptTicketId ab (Ticket gewechselt). */
|
||||
function finalize_other_open_sessions(PDO $pdo, int $staffId, int $exceptTicketId): void
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT ticket_id FROM ticket_work_sessions WHERE staff_id = ? AND ticket_id != ?');
|
||||
$stmt->execute([$staffId, $exceptTicketId]);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $otherTicketId) {
|
||||
finalize_work_session($pdo, (int) $otherTicketId, $staffId);
|
||||
}
|
||||
}
|
||||
|
||||
/** Schließt ALLE offenen Sitzungen eines Mitarbeiters ab (z.B. beim Logout). */
|
||||
function finalize_all_sessions_for_staff(PDO $pdo, int $staffId): void
|
||||
{
|
||||
$stmt = $pdo->prepare('SELECT ticket_id FROM ticket_work_sessions WHERE staff_id = ?');
|
||||
$stmt->execute([$staffId]);
|
||||
foreach ($stmt->fetchAll(PDO::FETCH_COLUMN) as $ticketId) {
|
||||
finalize_work_session($pdo, (int) $ticketId, $staffId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Räumt "verwaiste" Sitzungen auf (Tab geschlossen, Browser zu, Rechner aus –
|
||||
* ohne dass jemand explizit geantwortet oder sich abgemeldet hat). Wird
|
||||
* regelmäßig vom Auto-Cron (includes/pseudo_cron.php) aufgerufen. Nutzt den
|
||||
* letzten bekannten Aktivitätszeitpunkt (last_seen_at) als Sitzungsende,
|
||||
* nicht "jetzt" – die erfasste Zeit spiegelt also die tatsächliche Aktivität
|
||||
* wider, nicht die Zeit bis zum nächsten Cron-Durchlauf.
|
||||
*
|
||||
* @return array{finalized: int, log: string[]}
|
||||
*/
|
||||
function finalize_stale_work_sessions(PDO $pdo, bool $verbose = false): array
|
||||
{
|
||||
$log = [];
|
||||
$threshold = date('Y-m-d H:i:s', time() - WORK_SESSION_STALE_MINUTES * 60);
|
||||
$stmt = $pdo->prepare('SELECT ticket_id, staff_id, last_seen_at FROM ticket_work_sessions WHERE last_seen_at < ?');
|
||||
$stmt->execute([$threshold]);
|
||||
$stale = $stmt->fetchAll();
|
||||
|
||||
foreach ($stale as $row) {
|
||||
finalize_work_session($pdo, (int) $row['ticket_id'], (int) $row['staff_id'], $row['last_seen_at']);
|
||||
}
|
||||
|
||||
if ($verbose) {
|
||||
$log[] = "[" . date('Y-m-d H:i:s') . "] " . count($stale) . " inaktive Zeiterfassungs-Sitzung(en) abgeschlossen.";
|
||||
}
|
||||
|
||||
return ['finalized' => count($stale), 'log' => $log];
|
||||
}
|
||||
Reference in New Issue
Block a user