Files
gravity/บริหารจัดการงานเปล/config/Dispatcher.php
T
2026-09-16 23:20:08 +07:00

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();
}
}