beginTransaction(); // 1. Get the current order status. If it's a terminal state, don't override it. $stmt = $pdo->prepare("SELECT status FROM orders WHERE id = :order_id FOR UPDATE"); $stmt->execute(['order_id' => $order_id]); $current_status = $stmt->fetchColumn(); if ($current_status === 'completed' || $current_status === 'cancelled') { $pdo->commit(); return true; // No action needed } // TODO: Later, check payment status here. If pending_payment, we might not want to move to 'in_progress' yet. // 2. Get counts of item statuses for the order $stmt = $pdo->prepare(" SELECT COUNT(*) AS total_items, SUM(CASE WHEN item_status = 'shipped' THEN 1 ELSE 0 END) AS shipped_items, SUM(CASE WHEN item_status = 'pending' THEN 1 ELSE 0 END) AS pending_items, SUM(CASE WHEN item_status = 'in_progress' THEN 1 ELSE 0 END) AS in_progress_items FROM order_items WHERE order_id = :order_id "); $stmt->execute(['order_id' => $order_id]); $status_counts = $stmt->fetch(PDO::FETCH_ASSOC); if (!$status_counts || $status_counts['total_items'] == 0) { $pdo->rollBack(); return false; // No items found } $new_status = null; // 3. Apply status aggregation rules if ($status_counts['shipped_items'] == $status_counts['total_items']) { $new_status = 'shipped'; } elseif ($status_counts['pending_items'] > 0 || $status_counts['in_progress_items'] > 0) { // As long as payment is made, any non-shipped item means work is in progress. $new_status = 'in_progress'; } // 4. Update the order status if it has changed if ($new_status && $new_status !== $current_status) { $update_stmt = $pdo->prepare("UPDATE orders SET status = :status WHERE id = :order_id"); $update_stmt->execute(['status' => $new_status, 'order_id' => $order_id]); // TODO: send email when order.status changes to 'shipped' } // Commit the transaction $pdo->commit(); return true; } catch (Exception $e) { // Roll back on error if ($pdo->inTransaction()) { $pdo->rollBack(); } // Log error, e.g., error_log('Order status update failed: ' . $e->getMessage()); return false; } }