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

92 lines
4.7 KiB
PHP

<?php
/**
* Verknüpfte Tickets: beliebige Tickets lassen sich miteinander verbinden (verwandt, doppelt, blockiert), unabhängig von den
* „Problemen“ (includes/problems.php). Eine Verknüpfung wird einmal gespeichert (ticket_id → linked_ticket_id) und von beiden
* Tickets aus angezeigt, mit der jeweils passenden Bezeichnung.
*/
/** @return array<string,array{0:string,1:string}> Art => [Bezeichnung vom Quell-Ticket aus, Bezeichnung vom Ziel-Ticket aus] */
function ticket_link_types(): array
{
return [
'related' => ['Verwandt mit', 'Verwandt mit'],
'duplicate' => ['Doppelt zu', 'Hat Duplikat'],
'blocks' => ['Blockiert', 'Blockiert durch'],
];
}
/**
* Verknüpft zwei Tickets. Gibt eine Fehlermeldung zurück oder null bei Erfolg.
* Der Mitarbeiter muss beide Tickets sehen dürfen ($deptScope: Abteilungs-IDs des Mitarbeiters, null = alle).
*/
function ticket_link_add(PDO $pdo, int $ticketId, string $targetNumber, string $type, int $staffId, ?array $deptScope = null): ?string
{
if (!isset(ticket_link_types()[$type])) return 'Ungültige Art der Verknüpfung.';
$targetNumber = ltrim(trim($targetNumber), '#');
if ($targetNumber === '') return 'Bitte eine Ticketnummer angeben.';
$stmt = $pdo->prepare('SELECT id, department_id FROM tickets WHERE ticket_number = ?');
$stmt->execute([$targetNumber]);
$target = $stmt->fetch();
if (!$target) return 'Ticket #' . $targetNumber . ' wurde nicht gefunden.';
$targetId = (int) $target['id'];
if ($targetId === $ticketId) return 'Ein Ticket lässt sich nicht mit sich selbst verknüpfen.';
if ($deptScope !== null && !in_array((int) $target['department_id'], array_map('intval', $deptScope), true)) {
return 'Ticket #' . $targetNumber . ' gehört zu einer Abteilung, auf die Sie keinen Zugriff haben.';
}
$exists = $pdo->prepare('SELECT 1 FROM ticket_links WHERE (ticket_id = ? AND linked_ticket_id = ?) OR (ticket_id = ? AND linked_ticket_id = ?)');
$exists->execute([$ticketId, $targetId, $targetId, $ticketId]);
if ($exists->fetchColumn()) return 'Die beiden Tickets sind bereits verknüpft.';
$pdo->prepare('INSERT INTO ticket_links (ticket_id, linked_ticket_id, link_type, created_by, created_at) VALUES (?,?,?,?,?)')
->execute([$ticketId, $targetId, $type, $staffId > 0 ? $staffId : null, db_now()]);
return null;
}
/** Entfernt eine Verknüpfung, sofern sie zu diesem Ticket gehört. */
function ticket_link_remove(PDO $pdo, int $linkId, int $ticketId): bool
{
$stmt = $pdo->prepare('DELETE FROM ticket_links WHERE id = ? AND (ticket_id = ? OR linked_ticket_id = ?)');
$stmt->execute([$linkId, $ticketId, $ticketId]);
return $stmt->rowCount() > 0;
}
/**
* Alle Verknüpfungen eines Tickets aus dessen Sicht. Tickets aus Abteilungen, die der Mitarbeiter nicht sehen darf, bleiben weg.
* @return list<array{id:int,type:string,label:string,number:string,subject:string,status:string}>
*/
function ticket_links_for(PDO $pdo, int $ticketId, ?array $deptScope = null): array
{
$stmt = $pdo->prepare(
'SELECT l.id, l.ticket_id, l.linked_ticket_id, l.link_type, t.ticket_number, t.subject, t.status, t.department_id
FROM ticket_links l
JOIN tickets t ON t.id = CASE WHEN l.ticket_id = ? THEN l.linked_ticket_id ELSE l.ticket_id END
WHERE l.ticket_id = ? OR l.linked_ticket_id = ?
ORDER BY l.id'
);
$stmt->execute([$ticketId, $ticketId, $ticketId]);
$types = ticket_link_types();
$scope = $deptScope === null ? null : array_map('intval', $deptScope);
$out = [];
foreach ($stmt->fetchAll() as $row) {
if ($scope !== null && !in_array((int) $row['department_id'], $scope, true)) continue;
$outgoing = (int) $row['ticket_id'] === $ticketId;
$labels = $types[$row['link_type']] ?? ['Verknüpft mit', 'Verknüpft mit'];
$out[] = [
'id' => (int) $row['id'], 'type' => (string) $row['link_type'], 'label' => $outgoing ? $labels[0] : $labels[1],
'number' => (string) $row['ticket_number'], 'subject' => (string) $row['subject'], 'status' => (string) $row['status'],
];
}
return $out;
}
/** Offene Tickets, die dieses Ticket blockieren (Hinweis vor dem Schließen). */
function ticket_open_blockers(PDO $pdo, int $ticketId): array
{
return array_values(array_filter(ticket_links_for($pdo, $ticketId), fn($l) => $l['type'] === 'blocks' && $l['label'] === 'Blockiert durch' && !in_array($l['status'], ['resolved', 'closed'], true)));
}
/** Beim Löschen eines Tickets die Verknüpfungen mit entfernen. */
function ticket_links_purge(PDO $pdo, int $ticketId): void
{
$pdo->prepare('DELETE FROM ticket_links WHERE ticket_id = ? OR linked_ticket_id = ?')->execute([$ticketId, $ticketId]);
}