queueModel = new Queue(); $this->aiEngine = new SmartQueueEngine(); } /** * GET /api/v1/queues - Realtime Queue Board */ public function index(): void { RateLimitMiddleware::handle(60, 60); $branchId = isset($_GET['branch_id']) ? (int)$_GET['branch_id'] : null; $board = $this->queueModel->getRealtimeBoard($branchId); Response::success('Realtime Queue Board Data', [ 'total_active' => count($board), 'branch_id' => $branchId ?: 'All', 'queues' => $board, ]); } /** * POST /api/v1/queues/walkin - Create Walk-in Queue & Execute AI Assign */ public function walkin(): void { RateLimitMiddleware::handle(30, 60); $user = JwtAuthMiddleware::handle(); // ต้องเป็นเจ้าหน้าที่ Reception/Admin ที่ล็อกอิน $input = json_decode(file_get_contents('php://input'), true) ?? $_POST; $validator = Validator::make($input, [ 'patient_id' => 'required|numeric', 'service_id' => 'required|numeric', 'priority' => 'in:Normal,VIP,Emergency', ]); if ($validator->fails()) { Response::error('ข้อมูลไม่ครบถ้วนหรือไม่ถูกต้อง', 422, $validator->getErrors()); } $input['branch_id'] = $input['branch_id'] ?? $user['branch']; $input['created_by'] = $user['id']; $input['booking_type'] = 'Walk_in'; // Execute Smart Queue AI Engine $res = $this->aiEngine->allocateQueue($input); Response::success('ออกคิวนวดสำเร็จ พร้อมประเมินเวลาโดย Smart AI', $res, 201); } /** * POST /api/v1/queues/smart-assign - Manual Trigger AI Allocation for a waiting queue */ public function smartAssign(): void { JwtAuthMiddleware::handle(); $input = json_decode(file_get_contents('php://input'), true) ?? $_POST; if (empty($input['queue_id'])) { Response::error('กรุณาระบุหมายเลขคิวที่ต้องการจัดสรร', 400); } $db = \App\Models\Model::getDB(); $stmt = $db->prepare("CALL sp_assign_smart_queue(:qid, @t_id, @r_id, @e_start, @e_end, @msg)"); $stmt->execute(['qid' => (int)$input['queue_id']]); $stmt->closeCursor(); $res = $db->query("SELECT @t_id AS t_id, @r_id AS r_id, @e_start AS e_start, @e_end AS e_end, @msg AS msg")->fetch(); Response::success('Smart AI Allocation Executed', $res); } /** * PUT /api/v1/queues/{id}/status - Update Queue Status (Calling / In_Progress / Completed / Cancelled) */ public function updateStatus(int $queueId): void { $user = JwtAuthMiddleware::handle(); $input = json_decode(file_get_contents('php://input'), true) ?? $_POST; $newStatus = $input['status'] ?? null; $allowed = ['Waiting', 'Assigned', 'In_Progress', 'Completed', 'Cancelled', 'No_Show']; if (!in_array($newStatus, $allowed, true)) { Response::error('สถานะคิวไม่ถูกต้อง', 400); } $existing = $this->queueModel->find($queueId); if (!$existing) { Response::error('ไม่พบข้อมูลคิวนี้', 404); } $updateData = [ 'status' => $newStatus, 'updated_at' => date('Y-m-d H:i:s'), ]; if ($newStatus === 'In_Progress') { $updateData['actual_start_time'] = date('Y-m-d H:i:s'); // แจ้งเรียกคิวออกทีวีและ LINE Notify if (!empty($existing['room_id'])) { $room = (new \App\Models\Room())->find((int)$existing['room_id']); (new NotificationService())->notifyQueueCalled($existing['queue_no'], $room['room_no'] ?? '-', 'ผู้รับบริการ'); } } elseif ($newStatus === 'Completed') { $updateData['actual_end_time'] = date('Y-m-d H:i:s'); if (!empty($existing['actual_start_time'])) { $mins = round((time() - strtotime($existing['actual_start_time'])) / 60); $updateData['service_time_mins'] = $mins > 0 ? $mins : 60; } } $this->queueModel->update($queueId, $updateData); AuditLogger::logQueueUpdate($queueId, $existing['queue_no'], $existing['status'], $newStatus); // คำนวณ Workload ใหม่ให้หมอนวดหากคิวจบหรือยกเลิก if (!empty($existing['therapist_id'])) { (new \App\Services\WorkloadBalancer())->updateScore((int)$existing['therapist_id']); } Response::success("อัปเดตสถานะคิวเป็น {$newStatus} เรียบร้อยแล้ว", ['queue_id' => $queueId, 'status' => $newStatus]); } }