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

428 lines
19 KiB
PHP

<?php
// api/jobs.php
require_once dirname(__DIR__) . '/config/config.php';
require_once dirname(__DIR__) . '/models/Patient.php';
require_once dirname(__DIR__) . '/models/Job.php';
require_once dirname(__DIR__) . '/config/LineBot.php';
require_once dirname(__DIR__) . '/config/Dispatcher.php';
header('Content-Type: application/json');
$action = $_REQUEST['action'] ?? '';
// Check for post_max_size violation (which clears $_POST and $_FILES)
if ($_SERVER['REQUEST_METHOD'] === 'POST' && empty($_POST) && empty($_FILES) && isset($_SERVER['CONTENT_LENGTH']) && $_SERVER['CONTENT_LENGTH'] > 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']);
}