127 lines
6.2 KiB
PHP
127 lines
6.2 KiB
PHP
<?php
|
|
// config/Dispatcher.php
|
|
require_once dirname(__DIR__) . '/config/database.php';
|
|
|
|
class Dispatcher {
|
|
private $conn;
|
|
|
|
public function __construct() {
|
|
$this->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();
|
|
}
|
|
}
|