<?php
namespace App\Handlers;
use App\Core\Database;
use App\Models\Employee;
use App\Models\LeaveRequest;
use App\Models\LeaveType;
use App\Services\TelegramService;
use DateTime;
class LeaveApplicationHandler
{
private TelegramService $telegramService;
private int $chatId;
private int $userId; // Telegram User ID
private ?int $employeeId = null; // System's employee ID
private string $requestMode; // 'leave' or 'wfh' or specific alias
/**
* @param TelegramService $telegramService
* @param int $chatId
* @param int $userId
* @param string $requestMode
*/
public function __construct(TelegramService $telegramService, int $chatId, int $userId, string $requestMode = 'leave')
{
$this->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));
}
}
}