0) { echo json_encode(['status' => 'error', 'message' => 'ไฟล์รูปภาพมีขนาดใหญ่เกินกว่าที่เซิร์ฟเวอร์จะรับได้ (เกินขีดจำกัด post_max_size) กรุณาลดขนาดไฟล์ก่อนอัปโหลด']); exit; } // Need to be logged in (Except for public job creation) if (!isset($_SESSION['user_id']) && $action !== 'create_public_job') { echo json_encode(['status' => 'error', 'message' => 'Unauthorized']); exit; } if ($action === 'search_patient') { $hn = $_GET['hn'] ?? ''; if (empty($hn)) { echo json_encode(['status' => 'error', 'message' => 'Missing HN']); exit; } $patient = Patient::searchByHN($hn); if ($patient) { echo json_encode(['status' => 'success', 'data' => $patient]); } else { echo json_encode(['status' => 'error', 'message' => 'Patient not found']); } } elseif ($action === 'create_job') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['status' => 'error', 'message' => 'Invalid method']); exit; } // CSRF check if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) { echo json_encode(['status' => 'error', 'message' => 'Invalid CSRF Token']); exit; } $data = [ 'patient_hn' => $_POST['patient_hn'] ?? '', 'patient_name' => $_POST['patient_name'] ?? '', 'from_department_id' => $_POST['from_department_id'] ?? '', 'to_department_id' => $_POST['to_department_id'] ?? '', 'equipment_type' => $_POST['equipment_type'] ?? 'wheelchair', 'priority' => $_POST['priority'] ?? 'normal', 'notes' => $_POST['notes'] ?? '', 'requester_id' => $_SESSION['user_id'], 'infection_control' => isset($_POST['infection_control']) ? 1 : 0, 'is_scheduled' => isset($_POST['is_scheduled']) ? (int)$_POST['is_scheduled'] : 0, 'scheduled_time' => $_POST['scheduled_time'] ?? null, 'is_round_trip' => isset($_POST['is_round_trip']) ? 1 : 0 ]; if (empty($data['patient_hn']) || empty($data['from_department_id']) || empty($data['to_department_id'])) { echo json_encode(['status' => 'error', 'message' => 'ข้อมูลไม่ครบถ้วน']); exit; } $jobModel = new Job(); $job_number = $jobModel->create($data); if ($job_number) { require_once dirname(__DIR__) . '/models/Setting.php'; require_once dirname(__DIR__) . '/config/TelegramBot.php'; $settingModel = new Setting(); $auto_dispatch = $settingModel->get('auto_dispatch_enabled', '0'); $msg = "🚨 มีงานใหม่!\nหมายเลข: {$job_number}\nผู้ป่วย: {$data['patient_name']}\nอุปกรณ์: {$data['equipment_type']}"; // Trigger LINE Notification $lineBot = new LineBot(); $lineBot->sendGroupMessage($msg, $job_number); // will return false if no token // Trigger Telegram Notification $telegramBot = new TelegramBot(); $telegramBot->sendGroupMessage($msg, $job_number); // Smart Auto Dispatch (If enabled) if ($auto_dispatch === '1') { $dispatcher = new Dispatcher(); // Actually pass the job_number / ID to dispatcher // Wait, Dispatcher takes job_id. job_number is just a string (e.g. JOB2024...). // Let's assume jobModel->create() returns the inserted ID, or we need to find the ID. // Oh, previously $job_number = $jobModel->create($data) actually returned job_number. // Let's fetch the actual job_id to pass to autoDispatch. $jobInfo = $jobModel->getByJobNumber($job_number); if ($jobInfo) { $dispatcher->autoDispatch($jobInfo['id']); } } echo json_encode(['status' => 'success', 'job_number' => $job_number]); } else { echo json_encode(['status' => 'error', 'message' => 'เกิดข้อผิดพลาดในการบันทึกข้อมูล']); } } elseif ($action === 'create_public_job') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['status' => 'error', 'message' => 'Invalid method']); exit; } // CSRF check if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) { echo json_encode(['status' => 'error', 'message' => 'Invalid CSRF Token']); exit; } // Assign to a default admin user ID or get the first available admin $jobModel = new Job(); $db = Database::getInstance(); $stmt = $db->query("SELECT id FROM users WHERE role = 'admin' LIMIT 1"); $admin = $stmt->fetch(); $requester_id = $admin ? $admin['id'] : 1; $to_department_id = $_POST['to_department_id'] ?? ''; $to_department_custom = null; if ($to_department_id === 'custom') { $to_department_id = null; $to_department_custom = trim($_POST['to_department_custom'] ?? ''); } $patient_name = trim($_POST['patient_name'] ?? ''); // Handle Image Upload with Compression $patient_image_path = null; if (isset($_FILES['patient_image']) && $_FILES['patient_image']['error'] === UPLOAD_ERR_OK) { $uploadDir = dirname(__DIR__) . '/uploads/patients/'; if (!is_dir($uploadDir)) { mkdir($uploadDir, 0777, true); } $fileExt = strtolower(pathinfo($_FILES['patient_image']['name'], PATHINFO_EXTENSION)); $allowedExt = ['jpg', 'jpeg', 'png', 'webp']; if (in_array($fileExt, $allowedExt)) { // Force save as JPG for better compression of photos $fileName = uniqid('img_') . '_' . time() . '.jpg'; $destPath = $uploadDir . $fileName; $sourcePath = $_FILES['patient_image']['tmp_name']; // Image compression and resize logic (Max 800px, 75% quality) $info = getimagesize($sourcePath); if ($info !== false && function_exists('imagecreatefromjpeg')) { $image = null; if ($info['mime'] == 'image/jpeg') $image = imagecreatefromjpeg($sourcePath); elseif ($info['mime'] == 'image/png') $image = imagecreatefrompng($sourcePath); elseif ($info['mime'] == 'image/webp' && function_exists('imagecreatefromwebp')) $image = imagecreatefromwebp($sourcePath); elseif ($info['mime'] == 'image/gif') $image = imagecreatefromgif($sourcePath); if ($image !== null) { $maxWidth = 800; $width = $info[0]; $height = $info[1]; if ($width > $maxWidth || $height > $maxWidth) { $ratio = $width / $height; if ($ratio > 1) { $newWidth = $maxWidth; $newHeight = $maxWidth / $ratio; } else { $newWidth = $maxWidth * $ratio; $newHeight = $maxWidth; } $newImage = imagecreatetruecolor($newWidth, $newHeight); // Convert white background for transparent images $white = imagecolorallocate($newImage, 255, 255, 255); imagefill($newImage, 0, 0, $white); imagecopyresampled($newImage, $image, 0, 0, 0, 0, $newWidth, $newHeight, $width, $height); $image = $newImage; } // Save as JPEG if (imagejpeg($image, $destPath, 75)) { $patient_image_path = 'uploads/patients/' . $fileName; } imagedestroy($image); } else { if (move_uploaded_file($sourcePath, $destPath)) { $patient_image_path = 'uploads/patients/' . $fileName; } } } else { // Fallback if getimagesize fails, GD missing, or extension is allowed (rare) if (move_uploaded_file($sourcePath, $destPath)) { $patient_image_path = 'uploads/patients/' . $fileName; } } } } if (empty($patient_name) && empty($patient_image_path)) { echo json_encode(['status' => 'error', 'message' => 'กรุณาระบุชื่อผู้ป่วยหรือถ่ายรูปอย่างน้อยหนึ่งอย่าง']); exit; } if (empty($_POST['from_department_id']) || (empty($to_department_id) && empty($to_department_custom))) { echo json_encode(['status' => 'error', 'message' => 'ข้อมูลจุดรับส่งไม่ครบถ้วน']); exit; } $data = [ 'patient_hn' => $_POST['patient_hn'] ?? '', 'patient_name' => $patient_name, 'patient_image' => $patient_image_path, 'from_department_id' => $_POST['from_department_id'] ?? '', 'to_department_id' => $to_department_id, 'to_department_custom' => $to_department_custom, 'equipment_type' => $_POST['equipment_type'] ?? 'wheelchair', 'priority' => $_POST['priority'] ?? 'normal', 'notes' => ($_POST['notes'] ?? '') . "\n[เรียกผ่าน QR Code จุดรับผู้ป่วย]", 'requester_id' => $requester_id, 'infection_control' => isset($_POST['infection_control']) ? 1 : 0, 'is_scheduled' => 0, 'scheduled_time' => null, 'is_round_trip' => isset($_POST['is_round_trip']) ? 1 : 0 ]; try { $job_number = $jobModel->create($data); if ($job_number) { require_once dirname(__DIR__) . '/models/Setting.php'; require_once dirname(__DIR__) . '/config/TelegramBot.php'; $settingModel = new Setting(); $auto_dispatch = $settingModel->get('auto_dispatch_enabled', '0'); $display_name = empty($data['patient_name']) ? 'ไม่ระบุชื่อ (ดูรูปในระบบ)' : $data['patient_name']; $display_to = empty($data['to_department_id']) ? $data['to_department_custom'] : "แผนกปลายทาง (ID: {$data['to_department_id']})"; $msg = "🚨 แสกนเรียกจากจุดรับ!\nหมายเลข: {$job_number}\nผู้ป่วย: {$display_name}\nปลายทาง: {$display_to}\nอุปกรณ์: {$data['equipment_type']}"; // Trigger LINE Notification $lineBot = new LineBot(); $lineBot->sendGroupMessage($msg, $job_number); // Trigger Telegram Notification $telegramBot = new TelegramBot(); $telegramBot->sendGroupMessage($msg, $job_number); // Smart Auto Dispatch (If enabled) if ($auto_dispatch === '1') { $dispatcher = new Dispatcher(); $jobInfo = $jobModel->getByJobNumber($job_number); if ($jobInfo) { $dispatcher->autoDispatch($jobInfo['id']); } } echo json_encode(['status' => 'success', 'job_number' => $job_number]); } else { echo json_encode(['status' => 'error', 'message' => 'เกิดข้อผิดพลาดในการบันทึกข้อมูล']); } } catch (Throwable $e) { echo json_encode(['status' => 'error', 'message' => 'เกิดข้อผิดพลาด: ' . $e->getMessage() . ' (Line: ' . $e->getLine() . ')']); } } elseif ($action === 'save_signature') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['status' => 'error', 'message' => 'Invalid method']); exit; } $job_id = $_POST['job_id'] ?? ''; $signature_data = $_POST['signature'] ?? ''; if (empty($job_id) || empty($signature_data)) { echo json_encode(['status' => 'error', 'message' => 'Missing data']); exit; } // Decode Base64 image list($type, $signature_data) = explode(';', $signature_data); list(, $signature_data) = explode(',', $signature_data); $signature_data = base64_decode($signature_data); $filename = "sig_" . time() . "_" . uniqid() . ".png"; $filepath = dirname(__DIR__) . "/uploads/signatures/" . $filename; if (file_put_contents($filepath, $signature_data)) { // Update jobs table with signature_path and set status to completed $jobModel = new Job(); // Since we have job_number from client, we need job_id $jobInfo = $jobModel->getByJobNumber($job_id); if ($jobInfo) { $jobModel->updateStatus($jobInfo['id'], 'completed', $_SESSION['user_id']); // Add a quick direct SQL to update signature path $db = Database::getInstance(); $stmt = $db->prepare("UPDATE jobs SET notes = CONCAT(IFNULL(notes,''), '\n[Signature: ', :sig, ']') WHERE id = :id"); $stmt->execute([':sig' => $filename, ':id' => $jobInfo['id']]); } echo json_encode(['status' => 'success', 'file' => $filename]); } else { echo json_encode(['status' => 'error', 'message' => 'Failed to save signature']); } } elseif ($action === 'get_active_job') { $jobModel = new Job(); $activeJob = $jobModel->getActiveJobByStaff($_SESSION['user_id']); if ($activeJob) { require_once dirname(__DIR__) . '/models/Setting.php'; $settingModel = new Setting(); $equipments = json_decode($settingModel->get('equipment_types') ?: '[]', true); $priorities = json_decode($settingModel->get('job_priorities') ?: '[]', true); // Defaults $activeJob['equipment_name'] = $activeJob['equipment_type']; $activeJob['equipment_icon'] = 'fa-wheelchair'; $activeJob['priority_name'] = $activeJob['priority']; $activeJob['priority_color'] = 'blue'; // Match equipment foreach ($equipments as $eq) { if ($eq['id'] === $activeJob['equipment_type']) { $activeJob['equipment_name'] = $eq['name']; $activeJob['equipment_icon'] = $eq['icon']; break; } } // Match priority foreach ($priorities as $pri) { if ($pri['id'] === $activeJob['priority']) { $activeJob['priority_name'] = $pri['name']; $activeJob['priority_color'] = $pri['color']; break; } } echo json_encode(['status' => 'success', 'data' => $activeJob]); } else { echo json_encode(['status' => 'error', 'message' => 'No active job']); } } elseif ($action === 'update_status') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['status' => 'error', 'message' => 'Invalid method']); exit; } $job_id = $_POST['job_id'] ?? ''; $new_status = $_POST['status'] ?? ''; if (empty($job_id) || empty($new_status)) { echo json_encode(['status' => 'error', 'message' => 'Missing data']); exit; } $jobModel = new Job(); $jobInfo = $jobModel->getByJobNumber($job_id); if ($jobInfo) { if ($jobModel->updateStatus($jobInfo['id'], $new_status, $_SESSION['user_id'])) { echo json_encode(['status' => 'success', 'message' => 'Status updated']); } else { echo json_encode(['status' => 'error', 'message' => 'Failed to update status']); } } else { echo json_encode(['status' => 'error', 'message' => 'Job not found']); } } elseif ($action === 'assign_job') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['status' => 'error', 'message' => 'Invalid method']); exit; } $job_id = $_POST['job_id'] ?? ''; if (empty($job_id)) { echo json_encode(['status' => 'error', 'message' => 'Missing data']); exit; } $jobModel = new Job(); $jobInfo = $jobModel->getByJobNumber($job_id); if ($jobInfo && $jobInfo['status'] === 'pending') { $db = Database::getInstance(); $stmt = $db->prepare("UPDATE jobs SET status = 'assigned', assigned_to = :user_id WHERE id = :id"); $stmt->bindParam(':user_id', $_SESSION['user_id']); $stmt->bindParam(':id', $jobInfo['id']); if ($stmt->execute()) { echo json_encode(['status' => 'success', 'message' => 'รับงานสำเร็จ']); } else { echo json_encode(['status' => 'error', 'message' => 'ไม่สามารถรับงานได้']); } } else { echo json_encode(['status' => 'error', 'message' => 'งานนี้ถูกรับไปแล้วหรือไม่พบงาน']); } } elseif ($action === 'cancel_job') { if ($_SERVER['REQUEST_METHOD'] !== 'POST') { echo json_encode(['status' => 'error', 'message' => 'Invalid method']); exit; } $job_id = $_POST['job_id'] ?? ''; $reason = $_POST['reason'] ?? ''; if (empty($job_id) || empty($reason)) { echo json_encode(['status' => 'error', 'message' => 'Missing data']); exit; } $jobModel = new Job(); $jobInfo = $jobModel->getByJobNumber($job_id); if ($jobInfo) { if ($jobModel->cancelJob($jobInfo['id'], $reason, $_SESSION['user_id'])) { echo json_encode(['status' => 'success', 'message' => 'ยกเลิกงานสำเร็จ']); } else { echo json_encode(['status' => 'error', 'message' => 'ไม่สามารถยกเลิกงานได้']); } } else { echo json_encode(['status' => 'error', 'message' => 'Job not found']); } } else { echo json_encode(['status' => 'error', 'message' => 'Unknown action']); }