[Series] Xây dựng RESTful API từ con số 0 với PHP Thuần & MVC - Phần 22: Quản lý Trạng thái Đơn hàng & Lịch sử Thay đổi
Chào các bạn, mình đã quay trở lại!
Trong các hệ thống thực tế (như tại Hasaki), việc thay đổi trạng thái đơn hàng là một hành động cực kỳ nhạy cảm. Bạn không thể chỉ đơn giản là UPDATE status = 'delivered'. Bạn cần biết: Ai đã đổi? Đổi lúc nào? Và lý do (Note) là gì?
Hôm nay, chúng ta sẽ xây dựng endpoint PUT /api/orders/{id}/status và áp dụng kỹ thuật Logging để ghi lại mọi biến động của đơn hàng vào bảng Order_Status_History.
1. Cấu trúc bảng Lịch sử (Order_Status_History)
Trước khi vào code, hãy đảm bảo Database của bạn có bảng này để "audit" (kiểm tra) lại sau này nhé:
CREATE TABLE Order_Status_History (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT,
status VARCHAR(50),
note TEXT,
changed_at DATETIME,
created_at DATETIME,
FOREIGN KEY (order_id) REFERENCES Orders(id) ON DELETE CASCADE
);
2. Tầng Model: Cập nhật kép (Order.php)
Tại Model, chúng ta sẽ thực hiện hai nhiệm vụ: Cập nhật trạng thái mới nhất ở bảng chính và chèn một dòng nhật ký vào bảng lịch sử.
File: app/Models/Order.php (Bổ sung)
<?php
namespace App\Models;
// ... (Giữ nguyên các hàm cũ)
/**
* Cập nhật trạng thái đơn hàng kèm ghi nhật ký
*/
public function updateStatus($orderId, $status, $note = null) {
// 1. Cập nhật trạng thái ở bảng Orders
$stmt = $this->db->prepare("UPDATE Orders SET status = ?, updated_at = NOW() WHERE id = ?");
$stmt->execute([$status, $orderId]);
// 2. Ghi lại lịch sử thay đổi vào bảng Order_Status_History
$stmt2 = $this->db->prepare("
INSERT INTO Order_Status_History (order_id, status, note, changed_at, created_at)
VALUES (?, ?, ?, NOW(), NOW())
Bonner");
return $stmt2->execute([$orderId, $status, $note]);
}
3. Tầng Controller: Kiểm soát Trạng thái (OrderController.php)
Controller sẽ chịu trách nhiệm Validate xem trạng thái gửi lên có nằm trong danh sách "cho phép" hay không (tránh việc ai đó gửi trạng thái linh tinh như status = 'deleted').
File: app/Controllers/OrderController.php (Bổ sung method)
<?php
namespace App\Controllers;
use App\Core\Response;
use App\Models\Order;
use App\Middleware\AuthMiddleware;
class OrderController
{
// ... (Hàm create() ở bài trước)
public function updateStatus($id) {
// Bước 1: Xác thực Admin (Ở đây tạm dùng AuthMiddleware chung)
$user = AuthMiddleware::check();
$data = json_decode(file_get_contents("php://input"), true);
// Bước 2: Validation
if (empty($data['status'])) {
Response::json(['error' => 'Trạng thái là bắt buộc'], 422);
}
// Danh sách các trạng thái hợp lệ
$validStatuses = ['pending', 'processing', 'shipping', 'delivered', 'cancelled'];
if (!in_array($data['status'], $validStatuses)) {
Response::json(['error' => 'Trạng thái không hợp lệ'], 422);
}
$orderModel = new Order();
// Bước 3: Thực hiện cập nhật
$success = $orderModel->updateStatus($id, $data['status'], $data['note'] ?? null);
if ($success) {
Response::json(['message' => 'Cập nhật trạng thái đơn hàng thành công!']);
} else {
Response::json(['error' => 'Cập nhật thất bại, vui lòng thử lại'], 500);
}
}
}
4. Cấu hình Route (index.php)
Đăng ký Endpoint để cập nhật trạng thái. Chúng ta vẫn dùng Regex để bắt ID đơn hàng.
File: public/index.php
use App\Controllers\OrderController;
$orderController = new OrderController();
// API Cập nhật trạng thái: PUT /api/orders/{id}/status
if (preg_match('#^/api/orders/(\d+)/status$#', $uri, $matches) && $method === 'PUT') {
$orderController->updateStatus($matches[1]);
}
5. Kiểm thử API (Test with Curl)
Giả sử đơn hàng ID số 15 của bạn vừa được đóng gói xong và sẵn sàng giao cho Shipper:
curl -X PUT http://localhost:8000/api/orders/15/status \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-d '{
"status": "shipping",
"note": "Đơn hàng đã được bàn giao cho đơn vị vận chuyển GHTK"
}'
Tạm kết
Vậy là đơn hàng của chúng ta đã có một "cuốn nhật ký" ghi lại hành trình của nó. Việc quản lý trạng thái bài bản giúp hệ thống của bạn minh bạch hơn và hỗ trợ chăm sóc khách hàng cực tốt. Hãy để lại bình luận phía dưới nhé! Đừng quên Upvote để tiếp thêm động lực cho "bố đời". Chúc các bạn code vui vẻ!
All rights reserved