diff --git a/models/BulkOperationsModel.php b/models/BulkOperationsModel.php index 6c5c8d8..ece90e7 100644 --- a/models/BulkOperationsModel.php +++ b/models/BulkOperationsModel.php @@ -70,6 +70,9 @@ class BulkOperationsModel { $ticketModel = new TicketModel($this->conn); $auditLogModel = new AuditLogModel($this->conn); + // Batch load all tickets in one query to eliminate N+1 problem + $ticketsById = $ticketModel->getTicketsByIds($ticketIds); + foreach ($ticketIds as $ticketId) { $ticketId = trim($ticketId); $success = false; @@ -77,8 +80,8 @@ class BulkOperationsModel { try { switch ($operation['operation_type']) { case 'bulk_close': - // Get current ticket to preserve other fields - $currentTicket = $ticketModel->getTicketById($ticketId); + // Get current ticket from pre-loaded batch + $currentTicket = $ticketsById[$ticketId] ?? null; if ($currentTicket) { $success = $ticketModel->updateTicket([ 'ticket_id' => $ticketId, @@ -109,7 +112,7 @@ class BulkOperationsModel { case 'bulk_priority': if (isset($parameters['priority'])) { - $currentTicket = $ticketModel->getTicketById($ticketId); + $currentTicket = $ticketsById[$ticketId] ?? null; if ($currentTicket) { $success = $ticketModel->updateTicket([ 'ticket_id' => $ticketId, @@ -131,7 +134,7 @@ class BulkOperationsModel { case 'bulk_status': if (isset($parameters['status'])) { - $currentTicket = $ticketModel->getTicketById($ticketId); + $currentTicket = $ticketsById[$ticketId] ?? null; if ($currentTicket) { $success = $ticketModel->updateTicket([ 'ticket_id' => $ticketId, diff --git a/models/TicketModel.php b/models/TicketModel.php index fb10919..c76f98f 100644 --- a/models/TicketModel.php +++ b/models/TicketModel.php @@ -377,4 +377,50 @@ class TicketModel { $stmt->close(); return $result; } + + /** + * Get multiple tickets by IDs in a single query (batch loading) + * Eliminates N+1 query problem in bulk operations + * + * @param array $ticketIds Array of ticket IDs + * @return array Associative array keyed by ticket_id + */ + public function getTicketsByIds($ticketIds) { + if (empty($ticketIds)) { + return []; + } + + // Sanitize ticket IDs + $ticketIds = array_map('intval', $ticketIds); + + // Create placeholders for IN clause + $placeholders = str_repeat('?,', count($ticketIds) - 1) . '?'; + + $sql = "SELECT t.*, + u_created.username as creator_username, + u_created.display_name as creator_display_name, + u_updated.username as updater_username, + u_updated.display_name as updater_display_name, + u_assigned.username as assigned_username, + u_assigned.display_name as assigned_display_name + FROM tickets t + LEFT JOIN users u_created ON t.created_by = u_created.user_id + LEFT JOIN users u_updated ON t.updated_by = u_updated.user_id + LEFT JOIN users u_assigned ON t.assigned_to = u_assigned.user_id + WHERE t.ticket_id IN ($placeholders)"; + + $stmt = $this->conn->prepare($sql); + $types = str_repeat('i', count($ticketIds)); + $stmt->bind_param($types, ...$ticketIds); + $stmt->execute(); + $result = $stmt->get_result(); + + $tickets = []; + while ($row = $result->fetch_assoc()) { + $tickets[$row['ticket_id']] = $row; + } + + $stmt->close(); + return $tickets; + } } \ No newline at end of file