conn = Database::getInstance(); } /** * ค้นหาและจ่ายงานให้เจ้าหน้าที่โดยอัตโนมัติ (Smart Auto Dispatch) * ปัจจัยการประเมิน (Scoring Algorithm): * 1. สถานะ: ว่าง (Online) ได้คะแนนสูงสุด, กำลังรับส่ง (Busy) ถูกหักคะแนน * 2. ภาระงาน: เจ้าหน้าที่ที่มีงานน้อยที่สุดในวันนี้ จะได้คะแนนบวกเพิ่ม * 3. ระยะทาง: คำนวณจากพิกัด (user_locations) หาคนที่อยู่ใกล้ที่สุด (Haversine formula) * @param int $job_id * @param float $dest_lat พิกัดจุดรับ (ถ้ามี) * @param float $dest_lng พิกัดจุดรับ (ถ้ามี) */ public function autoDispatch($job_id, $dest_lat = null, $dest_lng = null) { // ดึงการตั้งค่า require_once dirname(__DIR__) . '/models/Setting.php'; $settingModel = new Setting(); $max_distance_str = $settingModel->get('dispatch_max_distance', '2.0'); $max_distance = floatval($max_distance_str); // ดึงรายการเจ้าหน้าที่ทั้งหมดที่ไม่ได้ออฟไลน์ พร้อมภาระงานในวันนี้ และพิกัดล่าสุด $query = " SELECT u.id, u.username, u.status, (SELECT COUNT(*) FROM jobs j WHERE j.requester_id = u.id AND DATE(j.created_at) = CURDATE()) as today_jobs, l.latitude, l.longitude FROM users u LEFT JOIN user_locations l ON u.id = l.user_id AND l.last_updated >= NOW() - INTERVAL 15 MINUTE WHERE u.role IN ('staff', 'nurse') AND u.status != 'offline' "; $stmt = $this->conn->prepare($query); $stmt->execute(); $staffs = $stmt->fetchAll(); if (empty($staffs)) { return false; // ไม่มีเจ้าหน้าที่พร้อมรับงาน } $best_staff_id = null; $highest_score = -9999; foreach ($staffs as $staff) { $score = 0; // 1. ประเมินจากสถานะ (Status Score) if ($staff['status'] === 'online') { $score += 100; // ว่าง } else { $score += 20; // ไม่ว่าง (อาจจะรับงานซ้อนได้ถ้าระบบอนุญาต) } // 2. ประเมินจากภาระงาน (Workload Score) // ยิ่งงานน้อย ยิ่งได้คะแนนเยอะ (ลดหลั่นลงไป งานละ -5 คะแนน) $score -= ($staff['today_jobs'] * 5); // 3. ประเมินจากระยะทาง (Distance Score) if ($dest_lat !== null && $dest_lng !== null && $staff['latitude'] !== null && $staff['longitude'] !== null) { $distance = $this->calculateDistance($staff['latitude'], $staff['longitude'], $dest_lat, $dest_lng); // หากระยะทางเกินกว่าที่กำหนด จะถูกหักคะแนนหนัก หรือข้ามไปเลย if ($distance > $max_distance) { $score -= 100; // ตัดสิทธิ์กลายๆ } else { // ยิ่งใกล้ ยิ่งได้คะแนนเยอะ (สมมติว่ารัศมี 1km ได้ 50 คะแนน, หักลบตามระยะทาง) $distance_score = max(0, 50 - ($distance * (50 / $max_distance))); $score += $distance_score; } } // ค้นหาผู้ที่ได้คะแนนสูงสุด if ($score > $highest_score) { $highest_score = $score; $best_staff_id = $staff['id']; } } if ($best_staff_id) { $this->assignJob($job_id, $best_staff_id); return $best_staff_id; } return false; } /** * คำนวณระยะทางแบบ Haversine Formula (กิโลเมตร) */ private function calculateDistance($lat1, $lon1, $lat2, $lon2) { $earth_radius = 6371; // km $dLat = deg2rad($lat2 - $lat1); $dLon = deg2rad($lon2 - $lon1); $a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLon/2) * sin($dLon/2); $c = 2 * asin(sqrt($a)); return $earth_radius * $c; } private function assignJob($job_id, $staff_id) { // อัปเดตตาราง jobs $query = "UPDATE jobs SET requester_id = :staff_id WHERE id = :job_id"; $stmt = $this->conn->prepare($query); $stmt->bindParam(':staff_id', $staff_id); $stmt->bindParam(':job_id', $job_id); $stmt->execute(); // เปลี่ยนสถานะเจ้าหน้าที่เป็นกำลังทำงาน $update_status = "UPDATE users SET status = 'busy' WHERE id = :staff_id"; $stmt3 = $this->conn->prepare($update_status); $stmt3->bindParam(':staff_id', $staff_id); $stmt3->execute(); // บันทึก Timeline $query_timeline = "INSERT INTO job_timelines (job_id, status, description, created_by) VALUES (:job_id, 'assigned', 'Smart Auto Dispatch จ่ายงานอัตโนมัติ', 0)"; $stmt2 = $this->conn->prepare($query_timeline); $stmt2->bindParam(':job_id', $job_id); $stmt2->execute(); } }