telegramService = $telegramService; $this->chatId = $chatId; $this->userId = $userId; $this->requestMode = $requestMode; // e.g., 'leave', 'wfh' $this->loadEmployeeId(); } private function loadEmployeeId(): void { $employeeModel = new Employee(Database::getInstance()); $employee = $employeeModel->findByChatUserId((string) $this->userId); if ($employee && $employee->id) { $this->employeeId = $employee->id; log_message("Employee ID {$this->employeeId} loaded for Telegram user ID {$this->userId} in mode {$this->requestMode}"); } else { log_message("No employee record found for Telegram user ID {$this->userId}."); } } private function clearUserState(): void { $stateFile = __DIR__."/../../logs/user_{$this->userId}_chat_{$this->chatId}_state.txt"; if (file_exists($stateFile)) { if (unlink($stateFile)) { log_message("State cleared by Handler for user {$this->userId} chat {$this->chatId}: {$stateFile}"); } else { log_message("Failed to clear state file by Handler for user {$this->userId} chat {$this->chatId}: {$stateFile}"); } } } /** * @param string $state */ private function saveUserState(string $state): void { // Prefix state with mode to avoid collision if user switches mode mid-conversation (though /cancel should prevent this) $statePrefix = $this->requestMode === 'wfh' ? 'wfh_' : 'la_'; $fullState = $statePrefix.$state; // e.g. la_expect_startDate_for_1 or wfh_expect_startDate_for_5 $stateFilePath = __DIR__."/../../logs/user_{$this->userId}_chat_{$this->chatId}_state.txt"; if (file_put_contents($stateFilePath, $fullState) === false) { log_message("Failed to save state by Handler to {$stateFilePath}: {$fullState}"); } else { log_message("State saved by Handler to {$stateFilePath}: {$fullState}"); } } /** * Starts the leave/wfh request process. * If an alias is provided (e.g., 'wfh'), it tries to find a unique leave type for it. * If not, or if mode is 'leave', it shows all applicable leave types. */ public function initiateRequest(): void { if ($this->employeeId === null) { $this->telegramService->sendMessage($this->chatId, "ขออภัยค่ะ ไม่พบข้อมูลพนักงานของคุณ (Telegram User ID: {$this->userId}) ในระบบ โปรดติดต่อ HR เพื่อลงทะเบียน Telegram User ID ของคุณก่อนเริ่มใช้งานค่ะ"); $this->clearUserState(); return; } $this->clearUserState(); // Clear previous state before starting a new request $leaveTypeModel = new LeaveType(Database::getInstance()); if ($this->requestMode === 'wfh') { $wfhType = $leaveTypeModel->findByAlias('wfh'); if ($wfhType && $wfhType->id) { log_message("Unique WFH type found (ID: {$wfhType->id}) for user {$this->userId}. Asking for start date."); $this->saveUserState("expect_startDate_for_{$wfhType->id}"); $this->telegramService->sendMessage($this->chatId, "คุณกำลังขอ Work From Home (ประเภท: {$wfhType->name}).\nกรุณาพิมพ์วันที่เริ่ม (รูปแบบ YYYY-MM-DD):"); return; } else { log_message("No unique WFH type found for alias 'wfh'. Will show list of types or specific WFH types if implemented."); // Fallback to showing list. You might want to filter for WFH-specific types here if you have many. // For now, it will show all types if the specific 'wfh' alias is not found or unique. } } $leaveTypesData = $leaveTypeModel->read(); if (empty($leaveTypesData)) { $this->telegramService->sendMessage($this->chatId, "ขออภัยค่ะ ตอนนี้ยังไม่มีประเภทการลา/WFH ในระบบเลยค่ะ"); return; } $keyboardButtons = []; foreach ($leaveTypesData as $type) { if (is_array($type) && isset($type['id']) && isset($type['name'])) { $keyboardButtons[] = [['text' => $type['name'], 'callback_data' => "req_type_{$type['id']}"]]; } } if (empty($keyboardButtons)) { $this->telegramService->sendMessage($this->chatId, "มีประเภทการลา/WFH ในระบบ แต่ไม่สามารถสร้างปุ่มได้ค่ะ กรุณาติดต่อผู้ดูแล"); return; } $replyMarkup = ['inline_keyboard' => $keyboardButtons]; $promptMessage = $this->requestMode === 'wfh' ? "ไม่พบประเภท WFH อัตโนมัติ, กรุณาเลือกประเภท Work From Home ที่ต้องการค่ะ:" : "สวัสดีค่ะ (พนักงาน ID: {$this->employeeId}) ต้องการลาประเภทไหนคะ?"; $this->telegramService->sendMessage($this->chatId, $promptMessage, $replyMarkup); } /** * @param string $callbackData * @return null */ public function handleTypeSelection(string $callbackData): void { if ($this->employeeId === null) { $this->telegramService->sendMessage($this->chatId, "ขออภัยค่ะ ไม่พบข้อมูลพนักงานของคุณ"); $this->clearUserState();return; } $parts = explode('_', $callbackData); // req_type_{id} $leaveTypeId = (int) end($parts); $leaveTypeModel = new LeaveType(Database::getInstance()); $selectedTypeObj = $leaveTypeModel->read($leaveTypeId); if ($selectedTypeObj && $selectedTypeObj->id) { $this->saveUserState("expect_startDate_for_{$leaveTypeId}"); $prompt = ($this->requestMode === 'wfh' ? "ขอ WFH" : "ลา")."ประเภท: {$selectedTypeObj->name}.\nกรุณาพิมพ์วันที่เริ่ม (รูปแบบ YYYY-MM-DD):"; $this->telegramService->sendMessage($this->chatId, $prompt); } else { $this->telegramService->sendMessage($this->chatId, "ขออภัยค่ะ ไม่พบประเภทที่คุณเลือก (ID: {$leaveTypeId})"); $this->clearUserState(); } } /** * @param string $stateData * @param string $dateString * @return null */ public function handleStartDateInput(string $stateData, string $dateString): void { if ($this->employeeId === null) { $this->telegramService->sendMessage($this->chatId, "ขออภัยค่ะ ไม่พบข้อมูลพนักงานของคุณ"); $this->clearUserState();return; } $stateWithoutPrefix = preg_replace('/^(la_|wfh_)/', '', $stateData); $parts = explode('_', $stateWithoutPrefix); // expect_startDate_for_{id} $leaveTypeId = (int) end($parts); if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $dateString) || !($startDate = DateTime::createFromFormat('Y-m-d', $dateString)) || $startDate->format('Y-m-d') !== $dateString) { $this->telegramService->sendMessage($this->chatId, "รูปแบบวันที่เริ่มไม่ถูกต้อง ({$dateString}) กรุณาใช้ YYYY-MM-DD. ลองอีกครั้งค่ะ:"); return; } $today = new DateTime(); $today->setTime(0, 0, 0); $sevenDaysAgo = (new DateTime())->modify('-7 days')->setTime(0, 0, 0); if ($startDate < $sevenDaysAgo) { $this->telegramService->sendMessage($this->chatId, "วันที่เริ่ม ({$dateString}) ไม่ควรเป็นอดีตนานเกินไปค่ะ"); return; } $this->saveUserState("expect_endDate_for_{$leaveTypeId}_start_{$dateString}"); $this->telegramService->sendMessage($this->chatId, "วันที่เริ่ม: {$dateString}.\nกรุณาพิมพ์วันที่สิ้นสุด (รูปแบบ YYYY-MM-DD):"); } /** * @param string $stateData * @param string $endDateString * @return null */ public function handleEndDateInput(string $stateData, string $endDateString): void { if ($this->employeeId === null) { $this->telegramService->sendMessage($this->chatId, "ขออภัยค่ะ ไม่พบข้อมูลพนักงานของคุณ"); $this->clearUserState();return; } $stateWithoutPrefix = preg_replace('/^(la_|wfh_)/', '', $stateData); preg_match('/expect_endDate_for_(\d+)_start_(\d{4}-\d{2}-\d{2})/', $stateWithoutPrefix, $matches); if (count($matches) !== 3) { $this->telegramService->sendMessage($this->chatId, "เกิดข้อผิดพลาดในการอ่าน state (end date) ค่ะ ลอง /cancel แล้วเริ่มใหม่นะคะ"); $this->clearUserState();return; } $leaveTypeId = (int) $matches[1]; $startDateString = $matches[2]; if (!preg_match('/^\d{4}-\d{2}-\d{2}$/', $endDateString) || !($endDate = DateTime::createFromFormat('Y-m-d', $endDateString)) || $endDate->format('Y-m-d') !== $endDateString) { $this->telegramService->sendMessage($this->chatId, "รูปแบบวันที่สิ้นสุดไม่ถูกต้อง ({$endDateString}). ลองอีกครั้งค่ะ:"); return; } if (strtotime($endDateString) < strtotime($startDateString)) { $this->telegramService->sendMessage($this->chatId, "วันที่สิ้นสุด ({$endDateString}) ต้องไม่มาก่อนวันที่เริ่ม ({$startDateString}). ลองอีกครั้งค่ะ:"); return; } $this->saveUserState("expect_reason_for_{$leaveTypeId}_start_{$startDateString}_end_{$endDateString}"); $promptMessage = $this->requestMode === 'wfh' ? "กรุณาใส่หมายเหตุ (ถ้ามี, ถ้าไม่มีพิมพ์ '-'):" : "กรุณาใส่เหตุผลการลา (ถ้าไม่มี พิมพ์ 'ไม่มี' หรือ '-' ):"; $this->telegramService->sendMessage($this->chatId, "วันที่เริ่ม: {$startDateString}, สิ้นสุด: {$endDateString}.\n{$promptMessage}"); } /** * @param string $stateData * @param string $reasonText * @return null */ public function handleReasonInput(string $stateData, string $reasonText): void { if ($this->employeeId === null) { $this->telegramService->sendMessage($this->chatId, "ขออภัยค่ะ ไม่พบข้อมูลพนักงานของคุณ"); $this->clearUserState();return; } $stateWithoutPrefix = preg_replace('/^(la_|wfh_)/', '', $stateData); preg_match('/expect_reason_for_(\d+)_start_(\d{4}-\d{2}-\d{2})_end_(\d{4}-\d{2}-\d{2})/', $stateWithoutPrefix, $matches); if (count($matches) !== 4) { $this->telegramService->sendMessage($this->chatId, "เกิดข้อผิดพลาดในการอ่าน state (reason) ค่ะ ลอง /cancel แล้วเริ่มใหม่นะคะ"); $this->clearUserState();return; } $leaveTypeId = (int) $matches[1]; $startDateString = $matches[2]; $endDateString = $matches[3]; $reason = (strtolower(trim($reasonText)) === 'ไม่มี' || trim($reasonText) === '-') ? null : trim($reasonText); $leaveTypeModel = new LeaveType(Database::getInstance()); $selectedType = $leaveTypeModel->read($leaveTypeId); $typeName = ($selectedType && $selectedType->id) ? $selectedType->name : "N/A (ID: {$leaveTypeId})"; $escapeMarkdown = function ($text) { if ($text === null) { return "ไม่ได้ระบุ"; } // More comprehensive list from Telegram Bot API docs for MarkdownV2 return preg_replace('/([_*\[\]()~`>#\+\-=|{}.!\\\\])/', '\\\\$1', $text); }; $typeNameSafe = $escapeMarkdown($typeName); $startDateSafe = $escapeMarkdown($startDateString); $endDateSafe = $escapeMarkdown($endDateString); $reasonSafe = $escapeMarkdown($reason); $summaryTitle = $this->requestMode === 'wfh' ? "*สรุปคำขอ WFH ของคุณ:*\n" : "*สรุปคำขอลาของคุณ:*\n"; $summary = $summaryTitle; $summary .= "ประเภท: {$typeNameSafe}\n"; $summary .= "วันที่เริ่ม: {$startDateSafe}\n"; $summary .= "วันที่สิ้นสุด: {$endDateSafe}\n"; $summary .= ($this->requestMode === 'wfh' ? "หมายเหตุ: " : "เหตุผล: ").$reasonSafe."\n\n"; $summary .= "กรุณายืนยันข้อมูลค่ะ"; $tempRequestData = json_encode([ 'employee_id' => $this->employeeId, 'leave_type_id' => $leaveTypeId, 'start_date' => $startDateString, 'end_date' => $endDateString, 'reason' => $reason, 'mode' => $this->requestMode ]); $this->saveUserState("confirm_".base64_encode($tempRequestData)); $confirmButtonText = $this->requestMode === 'wfh' ? '✅ ยืนยัน WFH' : '✅ ยืนยันการลา'; $keyboardButtons = [ [['text' => $confirmButtonText, 'callback_data' => 'req_action_confirm']], [['text' => '❌ ยกเลิก', 'callback_data' => 'req_action_cancel']] ]; $replyMarkup = ['inline_keyboard' => $keyboardButtons]; $this->telegramService->sendMessage($this->chatId, $summary, $replyMarkup, 'MarkdownV2'); } /** * @param string $stateData * @param string $actionCallbackData * @return null */ public function handleConfirmationAction(string $stateData, string $actionCallbackData): void { if ($this->employeeId === null) { $this->telegramService->sendMessage($this->chatId, "ขออภัยค่ะ ไม่พบข้อมูลพนักงานของคุณ"); $this->clearUserState();return; } $stateWithoutPrefix = preg_replace('/^(la_|wfh_)/', '', $stateData); if ($actionCallbackData === 'req_action_cancel') { $message = $this->requestMode === 'wfh' ? "การขอ WFH ถูกยกเลิกแล้วค่ะ" : "การขอลาถูกยกเลิกแล้วค่ะ"; $this->telegramService->sendMessage($this->chatId, $message); $this->clearUserState(); return; } if ($actionCallbackData === 'req_action_confirm') { if (!str_starts_with($stateWithoutPrefix, 'confirm_')) { $this->telegramService->sendMessage($this->chatId, "เกิดข้อผิดพลาด: ไม่พบข้อมูลสำหรับยืนยัน (state: {$stateData}). ลองเริ่มใหม่ค่ะ /ลา หรือ /wfh"); $this->clearUserState();return; } $base64Data = substr($stateWithoutPrefix, strlen('confirm_')); $requestDetails = json_decode(base64_decode($base64Data), true); if (!$requestDetails || !isset($requestDetails['employee_id'])) { $this->telegramService->sendMessage($this->chatId, "เกิดข้อผิดพลาด: ข้อมูลยืนยันไม่ถูกต้อง. ลองเริ่มใหม่ค่ะ /ลา หรือ /wfh"); $this->clearUserState();return; } if ($requestDetails['employee_id'] !== $this->employeeId) { $this->telegramService->sendMessage($this->chatId, "เกิดข้อผิดพลาด: ข้อมูลยืนยันไม่ตรงกับผู้ใช้ปัจจุบัน"); $this->clearUserState();return; } $leaveRequestModel = new LeaveRequest(Database::getInstance()); $leaveRequestModel->employee_id = $requestDetails['employee_id']; $leaveRequestModel->leave_type_id = $requestDetails['leave_type_id']; $leaveRequestModel->start_date = $requestDetails['start_date']; $leaveRequestModel->end_date = $requestDetails['end_date']; $leaveRequestModel->reason = $requestDetails['reason']; $leaveRequestModel->status = 'PENDING'; if ($leaveRequestModel->create()) { $modeInDetails = $requestDetails['mode'] ?? 'leave'; // Fallback to 'leave' if mode not in details $newlyCreatedRequestId = $leaveRequestModel->id; // Get the ID of the newly created request $modeInDetails = $requestDetails['mode'] ?? 'leave'; $successMessage = $modeInDetails === 'wfh' ? "✅ คำขอ WFH ของคุณ (ID: {$newlyCreatedRequestId}) ถูกบันทึกเรียบร้อยแล้ว และกำลังรอการอนุมัติค่ะ" : "✅ คำขอลาของคุณ (ID: {$newlyCreatedRequestId}) ถูกบันทึกเรียบร้อยแล้ว และกำลังรอการอนุมัติค่ะ"; $this->telegramService->sendMessage($this->chatId, $successMessage); // --- Notify Manager --- $this->notifyManager($newlyCreatedRequestId, $requestDetails); // --- End Notify Manager --- } else { $modeInDetails = $requestDetails['mode'] ?? 'leave'; $failMessage = $modeInDetails === 'wfh' ? "❌ เกิดข้อผิดพลาดในการบันทึกคำขอ WFH" : "❌ เกิดข้อผิดพลาดในการบันทึกคำขอลา"; $this->telegramService->sendMessage($this->chatId, "{$failMessage} กรุณาลองใหม่อีกครั้ง หรือติดต่อผู้ดูแลระบบค่ะ"); error_log("Failed to create request for employee {$this->employeeId}. Mode: {$modeInDetails}. Data: ".json_encode($requestDetails)); } $this->clearUserState(); } } /** * @param int $leaveRequestId * @param array $requestDetails * @return null */ private function notifyManager(int $leaveRequestId, array $requestDetails): void { $employeeModel = new Employee(Database::getInstance()); // It's better to use $this->employeeId which is already loaded for the requester // $requester = $employeeModel->read($requestDetails['employee_id']); $requester = $employeeModel->read($this->employeeId); if (!$requester || !isset($requester->manager_id) || $requester->manager_id === null) { log_message("Cannot notify manager: Requester (ID: {$this->employeeId}) not found in DB or has no manager_id."); return; } $manager = $employeeModel->read($requester->manager_id); if (!$manager || !isset($manager->chat_user_id) || $manager->chat_user_id === null) { log_message("Cannot notify manager: Manager (ID: {$requester->manager_id}) not found or has no chat_user_id. Requester: {$this->employeeId}"); // Optionally, notify HR or admin that manager notification failed. // $this->telegramService->sendMessage($this->chatId, "(ระบบ: ไม่สามารถแจ้งเตือนหัวหน้าได้เนื่องจากข้อมูลหัวหน้าไม่ครบถ้วน)"); return; } // Ensure chat_user_id is treated as integer for Telegram API $managerChatId = (int) $manager->chat_user_id; if ($managerChatId === 0) { // Or any other invalid ID indication log_message("Manager (ID: {$manager->id}) has an invalid chat_user_id: {$manager->chat_user_id}. Cannot send notification."); return; } $leaveTypeModel = new LeaveType(Database::getInstance()); $leaveType = $leaveTypeModel->read($requestDetails['leave_type_id']); $leaveTypeName = ($leaveType && isset($leaveType->name)) ? $leaveType->name : 'N/A'; $requesterName = htmlspecialchars(($requester->first_name ?? 'พนักงาน')." ".($requester->last_name ?? '')); $startDateFormatted = (new DateTime($requestDetails['start_date']))->format('d/m/Y'); $endDateFormatted = (new DateTime($requestDetails['end_date']))->format('d/m/Y'); $reasonDisplay = "ไม่ได้ระบุ"; if (!empty($requestDetails['reason'])) { // Basic sanitization for message, not for MarkdownV2 here unless specified in sendMessage $reasonDisplay = htmlspecialchars($requestDetails['reason']); } $requestModeText = ($requestDetails['mode'] ?? 'leave') === 'wfh' ? "ขอ Work From Home" : "ขอลา"; $messageToManager = "เรียน คุณ ".htmlspecialchars($manager->first_name ?? 'หัวหน้า').",\n\n"; $messageToManager .= "คุณ {$requesterName} (ID: {$this->employeeId}) ได้ส่ง{$requestModeText}เข้ามาใหม่:\n"; $messageToManager .= "ประเภท: ".htmlspecialchars($leaveTypeName)."\n"; $messageToManager .= "วันที่: {$startDateFormatted} - {$endDateFormatted}\n"; $messageToManager .= (($requestDetails['mode'] ?? 'leave') === 'wfh' ? "หมายเหตุ: " : "เหตุผล: ").$reasonDisplay."\n"; $messageToManager .= "รหัสคำขอ: {$leaveRequestId}\n\n"; $messageToManager .= "กรุณาพิจารณาค่ะ"; $keyboardButtons = [ [['text' => '✅ อนุมัติ (Approve)', 'callback_data' => "mgrAction_approve_{$leaveRequestId}"]], [['text' => '❌ ไม่อนุมัติ (Reject)', 'callback_data' => "mgrAction_reject_{$leaveRequestId}"]] ]; $replyMarkup = ['inline_keyboard' => $keyboardButtons]; $sendResult = $this->telegramService->sendMessage($managerChatId, $messageToManager, $replyMarkup); if ($sendResult && isset($sendResult['ok']) && $sendResult['ok']) { log_message("Successfully sent notification to manager (ChatID: {$managerChatId}, ManagerEmpID: {$manager->id}) for request ID {$leaveRequestId}."); } else { log_message("Failed to send notification to manager (ChatID: {$managerChatId}, ManagerEmpID: {$manager->id}) for request ID {$leaveRequestId}. Response: ".json_encode($sendResult)); } } }