Initial commit

This commit is contained in:
Porawit Dongwang
2026-09-16 23:20:08 +07:00
commit 0041668dbb
32577 changed files with 3687927 additions and 0 deletions
@@ -0,0 +1,312 @@
<?php
// models/Job.php
require_once dirname(__DIR__) . '/config/database.php';
require_once __DIR__ . '/Department.php';
require_once __DIR__ . '/Notification.php';
require_once __DIR__ . '/User.php';
require_once __DIR__ . '/DispatchEngine.php';
class Job {
private $conn;
private $table_name = "jobs";
public function __construct() {
$this->conn = Database::getInstance();
}
public function create($data) {
// Generate unique job number
$job_number = "JOB-" . date('Ymd') . "-" . str_pad(rand(1, 999), 3, '0', STR_PAD_LEFT);
$query = "INSERT INTO " . $this->table_name . "
(job_number, patient_hn, patient_name, patient_image, from_department_id, to_department_id, to_department_custom, equipment_type, priority, requester_id, notes, is_scheduled, scheduled_time, infection_control, is_round_trip)
VALUES (:job_number, :patient_hn, :patient_name, :patient_image, :from_department_id, :to_department_id, :to_department_custom, :equipment_type, :priority, :requester_id, :notes, :is_scheduled, :scheduled_time, :infection_control, :is_round_trip)";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':job_number', $job_number);
$stmt->bindParam(':patient_hn', $data['patient_hn']);
$stmt->bindValue(':patient_name', $data['patient_name'], PDO::PARAM_STR);
$stmt->bindValue(':patient_image', $data['patient_image'] ?? null, PDO::PARAM_STR);
$stmt->bindParam(':from_department_id', $data['from_department_id']);
$stmt->bindValue(':to_department_id', $data['to_department_id'], PDO::PARAM_INT);
$stmt->bindValue(':to_department_custom', $data['to_department_custom'] ?? null, PDO::PARAM_STR);
$stmt->bindParam(':equipment_type', $data['equipment_type']);
$stmt->bindParam(':priority', $data['priority']);
$stmt->bindParam(':requester_id', $data['requester_id']);
$stmt->bindParam(':notes', $data['notes']);
$is_scheduled = isset($data['is_scheduled']) ? (int)$data['is_scheduled'] : 0;
$scheduled_time = !empty($data['scheduled_time']) ? $data['scheduled_time'] : null;
$infection_control = isset($data['infection_control']) ? (int)$data['infection_control'] : 0;
$is_round_trip = isset($data['is_round_trip']) ? (int)$data['is_round_trip'] : 0;
$stmt->bindParam(':is_scheduled', $is_scheduled);
$stmt->bindValue(':scheduled_time', $scheduled_time, PDO::PARAM_STR);
$stmt->bindParam(':infection_control', $infection_control);
$stmt->bindParam(':is_round_trip', $is_round_trip);
if ($stmt->execute()) {
$job_id = $this->conn->lastInsertId();
$this->logTimeline($job_id, 'pending', 'สร้างคำของานเปล', $data['requester_id']);
// Send Notification
try {
$deptModel = new Department();
$from_dept = $deptModel->getById($data['from_department_id']);
$to_dept = $deptModel->getById($data['to_department_id']);
$from_name = $from_dept ? $from_dept['name'] : 'ไม่ระบุ';
$to_name = $to_dept ? $to_dept['name'] : 'ไม่ระบุ';
$notification = new Notification();
$msg = "🚨 มีคำของานเปลใหม่ 🚨\n";
$msg .= "เลขที่: " . $job_number . "\n";
$msg .= "ผู้ป่วย: " . $data['patient_name'] . " (HN: " . $data['patient_hn'] . ")\n";
$msg .= "จุดรับ: " . $from_name . "\n";
$msg .= "จุดส่ง: " . $to_name . "\n";
$msg .= "ความฉุกเฉิน: " . $data['priority'] . "\n";
$msg .= "อุปกรณ์: " . $data['equipment_type'];
if ($infection_control == 1) {
$msg .= "\n⚠️ ผู้ป่วยแยกโรค / เสี่ยงติดเชื้อ (Infection Control)";
}
if ($is_scheduled == 1) {
$msg .= "\n⏰ เวลานัดหมาย: " . $scheduled_time;
}
if (!empty($data['notes'])) {
$msg .= "\nหมายเหตุ: " . $data['notes'];
}
$notification->sendToAll($msg);
} catch (Exception $e) {
// Silently fail if notification fails, don't break job creation
}
// Run Auto Dispatch Engine only if it's not a scheduled job in the future
if ($is_scheduled == 0) {
try {
$dispatchEngine = new DispatchEngine();
$dispatchEngine->runAutoDispatch($job_id);
} catch (Exception $e) {
// Fail silently
}
}
return $job_number;
}
return false;
}
public function getById($id) {
$query = "SELECT * FROM " . $this->table_name . " WHERE id = :id LIMIT 1";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':id', $id);
$stmt->execute();
if ($stmt->rowCount() > 0) {
return $stmt->fetch(PDO::FETCH_ASSOC);
}
return false;
}
public function getByJobNumber($job_number) {
$query = "SELECT * FROM " . $this->table_name . " WHERE job_number = :job_number LIMIT 1";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':job_number', $job_number);
$stmt->execute();
if ($stmt->rowCount() > 0) {
return $stmt->fetch();
}
return false;
}
public function logTimeline($job_id, $status, $description, $user_id) {
$query = "INSERT INTO job_timelines (job_id, status, description, created_by) VALUES (:job_id, :status, :description, :created_by)";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':job_id', $job_id);
$stmt->bindParam(':status', $status);
$stmt->bindParam(':description', $description);
$stmt->bindParam(':created_by', $user_id);
$stmt->execute();
}
public function getActiveJobByStaff($staff_id) {
$query = "SELECT j.*,
fd.name as from_department_name,
td.name as to_department_name
FROM " . $this->table_name . " j
LEFT JOIN departments fd ON j.from_department_id = fd.id
LEFT JOIN departments td ON j.to_department_id = td.id
WHERE j.assigned_to = :staff_id
AND j.status IN ('assigned', 'accepted', 'in_progress', 'waiting_for_return')
ORDER BY j.id DESC LIMIT 1";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':staff_id', $staff_id);
$stmt->execute();
if ($stmt->rowCount() > 0) {
return $stmt->fetch(PDO::FETCH_ASSOC);
}
return false;
}
public function updateStatus($job_id, $status, $user_id) {
$update_field = "";
if ($status === 'accepted') {
$update_field = ", accepted_at = NOW()";
} elseif ($status === 'in_progress') {
$update_field = ", pickup_at = NOW()";
} elseif ($status === 'completed') {
$update_field = ", completed_at = NOW()";
}
$query = "UPDATE " . $this->table_name . "
SET status = :status {$update_field}
WHERE id = :job_id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':status', $status);
$stmt->bindParam(':job_id', $job_id);
if ($stmt->execute()) {
$description = "อัปเดตสถานะเป็น: " . $status;
$this->logTimeline($job_id, $status, $description, $user_id);
// Send Notification for key events
try {
if (in_array($status, ['accepted', 'completed'])) {
$job = $this->getById($job_id);
$job_number = $job ? $job['job_number'] : $job_id;
$userModel = new User();
$user = $userModel->getById($user_id);
$user_name = $user ? $user['full_name'] : 'ระบบ';
$notification = new Notification();
if ($status === 'accepted') {
$msg = "✅ เจ้าหน้าที่รับงานแล้ว\nงาน: " . $job_number . "\nผู้รับงาน: " . $user_name;
} else if ($status === 'completed') {
$msg = "🏁 งานเสร็จสิ้น\nงาน: " . $job_number . "\nผู้ดำเนินการ: " . $user_name;
}
$notification->sendToAll($msg);
}
} catch (Exception $e) {
// Fail silently
}
return true;
}
return false;
}
public function cancelJob($job_id, $reason, $user_id) {
$status = 'cancelled';
// Append reason to notes
$query = "UPDATE " . $this->table_name . "
SET status = :status,
notes = CONCAT(IFNULL(notes, ''), '\n[ยกเลิก: ', :reason, ']')
WHERE id = :job_id";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':status', $status);
$stmt->bindParam(':reason', $reason);
$stmt->bindParam(':job_id', $job_id);
if ($stmt->execute()) {
$description = "ยกเลิกงาน เหตุผล: " . $reason;
$this->logTimeline($job_id, $status, $description, $user_id);
// Send Notification for cancellation
try {
$job = $this->getById($job_id);
$job_number = $job ? $job['job_number'] : $job_id;
$userModel = new User();
$user = $userModel->getById($user_id);
$user_name = $user ? $user['full_name'] : 'ระบบ';
$notification = new Notification();
$msg = "❌ ปฏิเสธ/ยกเลิกงาน\nงาน: " . $job_number . "\nเหตุผล: " . $reason . "\nผู้ยกเลิก: " . $user_name;
$notification->sendToAll($msg);
} catch (Exception $e) {
// Fail silently
}
return true;
}
return false;
}
public function getJobTimelines($job_id) {
$query = "SELECT t.*, u.full_name as created_by_name
FROM job_timelines t
LEFT JOIN users u ON t.created_by = u.id
WHERE t.job_id = :job_id
ORDER BY t.created_at ASC";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':job_id', $job_id);
$stmt->execute();
return $stmt->fetchAll();
}
public function getStaffHistory($staff_id, $limit = 50) {
$query = "SELECT j.*,
fd.name as from_department_name,
td.name as to_department_name
FROM " . $this->table_name . " j
LEFT JOIN departments fd ON j.from_department_id = fd.id
LEFT JOIN departments td ON j.to_department_id = td.id
WHERE j.assigned_to = :staff_id AND j.status IN ('completed', 'cancelled')
ORDER BY j.completed_at DESC, j.created_at DESC LIMIT :limit";
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':staff_id', $staff_id);
$stmt->bindValue(':limit', (int)$limit, PDO::PARAM_INT);
$stmt->execute();
return $stmt->fetchAll();
}
public function getStaffPerformance($date_range = 'today') {
$date_condition = "DATE(j.created_at) = CURDATE()";
if ($date_range === 'week') {
$date_condition = "YEARWEEK(j.created_at, 1) = YEARWEEK(CURDATE(), 1)";
} elseif ($date_range === 'month') {
$date_condition = "YEAR(j.created_at) = YEAR(CURDATE()) AND MONTH(j.created_at) = MONTH(CURDATE())";
}
$query = "SELECT u.id, u.full_name,
COUNT(j.id) as total_jobs,
SUM(CASE WHEN j.status = 'completed' THEN 1 ELSE 0 END) as completed_jobs,
SUM(CASE WHEN j.status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_jobs,
AVG(TIMESTAMPDIFF(MINUTE, j.accepted_at, j.completed_at)) as avg_service_time
FROM users u
LEFT JOIN " . $this->table_name . " j ON u.id = j.assigned_to AND " . $date_condition . "
WHERE u.role = 'staff'
GROUP BY u.id, u.full_name
ORDER BY completed_jobs DESC";
$stmt = $this->conn->prepare($query);
$stmt->execute();
return $stmt->fetchAll();
}
public function getStaffStats($staff_id, $date_range = 'today') {
$date_condition = "DATE(created_at) = CURDATE()";
if ($date_range === 'week') {
$date_condition = "YEARWEEK(created_at, 1) = YEARWEEK(CURDATE(), 1)";
} elseif ($date_range === 'month') {
$date_condition = "YEAR(created_at) = YEAR(CURDATE()) AND MONTH(created_at) = MONTH(CURDATE())";
}
$query = "SELECT
COUNT(id) as total_jobs,
SUM(CASE WHEN status = 'completed' THEN 1 ELSE 0 END) as completed_jobs,
SUM(CASE WHEN status = 'cancelled' THEN 1 ELSE 0 END) as cancelled_jobs,
AVG(TIMESTAMPDIFF(MINUTE, accepted_at, completed_at)) as avg_service_time
FROM " . $this->table_name . "
WHERE assigned_to = :staff_id AND " . $date_condition;
$stmt = $this->conn->prepare($query);
$stmt->bindParam(':staff_id', $staff_id);
$stmt->execute();
return $stmt->fetch();
}
}