<?php
namespace App\Handlers;
use App\Core\Database;
use App\Models\Employee;
use App\Models\LeaveRequest;
use App\Services\TelegramService;
use DateTime;
// For formatting dates
class CancelRequestHandler
{
private TelegramService $telegramService;
private int $chatId;
private int $userId; // Telegram User ID
private ?int $employeeId = null; // System's employee ID
/**
* @param TelegramService $telegramService
* @param int $chatId
* @param int $userId
*/
public function __construct(TelegramService $telegramService, int $chatId, int $userId)
{
$this->telegramService = $telegramService;
$this->chatId = $chatId;
$this->userId = $userId;
$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("CancelRequestHandler: Employee ID {$this->employeeId} loaded for Telegram user ID {$this->userId}");
} else {
log_message("CancelRequestHandler: No employee record found for Telegram user ID {$this->userId}.");
}
}
// Helper to clear state
private function clearUserState(): void
{
$stateFile = __DIR__."/../../logs/user_{$this->userId}_chat_{$this->chatId}_state.txt";
if (file_exists($stateFile)) {
// Only clear states related to cancellation if a specific cancel state exists
$currentState = trim(file_get_contents($stateFile));
if (str_starts_with($currentState, 'cancelReq_confirm_')) {
if (unlink($stateFile)) {
log_message("State cleared by CancelRequestHandler for user {$this->userId} chat {$this->chatId}: {$stateFile}");
} else {
log_message("Failed to clear state by CancelRequestHandler for user {$this->userId} chat {$this->chatId}: {$stateFile}");
}
}
}
}
// Helper to save state for cancel confirmation
/**
* @param int $requestId
*/
private function saveCancellationConfirmState(int $requestId): void
{
$state = "cancelReq_confirm_{$requestId}";
$stateFilePath = __DIR__."/../../logs/user_{$this->userId}_chat_{$this->chatId}_state.txt";
if (file_put_contents($stateFilePath, $state) === false) {
log_message("Failed to save state by CancelRequestHandler to {$stateFilePath}: {$state}");
} else {
log_message("State saved by CancelRequestHandler to {$stateFilePath}: {$state}");
}
}
/**
* @return null
*/
public function startCancellationProcess(): void
{
if ($this->employeeId === null) {
$this->telegramService->sendMessage($this->chatId, "ขออภัยค่ะ ไม่พบข้อมูลพนักงานของคุณในระบบ โปรดติดต่อ HR ค่ะ");
return;
}
// Clear any previous state of this user for a clean start for this operation.
// This is a global clear, might be too broad if user was in another flow.
// For a command like /cancelrequest, it's probably fine.
$globalStateFile = __DIR__."/../../logs/user_{$this->userId}_chat_{$this->chatId}_state.txt";
if (file_exists($globalStateFile)) {
unlink($globalStateFile);
log_message("Global state cleared by startCancellationProcess for user {$this->userId} chat {$this->chatId}");
}
$leaveRequestModel = new LeaveRequest(Database::getInstance());
$pendingRequests = $leaveRequestModel->getPendingRequestsByEmployee($this->employeeId);
if (empty($pendingRequests)) {
$this->telegramService->sendMessage($this->chatId, "คุณไม่ม่ีคำขอลา/WFH ที่อยู่ในสถานะ 'รออนุมัติ' ที่สามารถยกเลิกได้ค่ะ");
return;
}
$messageText = "รายการคำขอที่รออนุมัติของคุณ (เลือกเพื่อยกเลิก):\n";
$keyboardButtons = [];
foreach ($pendingRequests as $request) {
// $request is a LeaveRequest object, $request->leaveType should be LeaveType object
$leaveTypeName = ($request->leaveType && $request->leaveType->name) ? htmlspecialchars($request->leaveType->name) : 'N/A';
$startDate = ($request->start_date) ? (new DateTime($request->start_date))->format('d/m/Y') : 'N/A';
$endDate = ($request->end_date) ? (new DateTime($request->end_date))->format('d/m/Y') : 'N/A';
$buttonText = "ID: {$request->id} - {$leaveTypeName} ({$startDate} - {$endDate})";
$keyboardButtons[] = [['text' => $buttonText, 'callback_data' => "cancelReq_select_{$request->id}"]];
}
if (empty($keyboardButtons)) {
$this->telegramService->sendMessage($this->chatId, "ไม่สามารถสร้างรายการคำขอของคุณได้ค่ะ");
return;
}
$replyMarkup = ['inline_keyboard' => $keyboardButtons];
$this->telegramService->sendMessage($this->chatId, $messageText, $replyMarkup);
}
/**
* @param string $callbackData
* @return null
*/
public function handleCancellationSelection(string $callbackData): void
{
// cancelReq_select_{requestId}
if ($this->employeeId === null) {$this->telegramService->sendMessage($this->chatId, "ขออภัยค่ะ ไม่พบข้อมูลพนักงาน");return;}
$parts = explode('_', $callbackData);
$requestId = (int) end($parts);
$leaveRequestModel = new LeaveRequest(Database::getInstance());
$requestToCancel = $leaveRequestModel->read($requestId);
if (!$requestToCancel || $requestToCancel->employee_id !== $this->employeeId) {
$this->telegramService->sendMessage($this->chatId, "ไม่พบคำขอที่คุณเลือก หรือคุณไม่มีสิทธิ์ยกเลิกคำขอนี้ค่ะ");
return;
}
if ($requestToCancel->status !== 'PENDING') {
$this->telegramService->sendMessage($this->chatId, "คำขอ ID: {$requestId} ไม่อยู่ในสถานะ 'รออนุมัติ' จึงไม่สามารถยกเลิกผ่านระบบนี้ได้ค่ะ (สถานะปัจจุบัน: {$requestToCancel->status})");
return;
}
$leaveTypeName = ($requestToCancel->leaveType && $requestToCancel->leaveType->name) ? $requestToCancel->leaveType->name : 'N/A';
$startDate = ($requestToCancel->start_date) ? (new DateTime($requestToCancel->start_date))->format('d/m/Y') : 'N/A';
$endDate = ($requestToCancel->end_date) ? (new DateTime($requestToCancel->end_date))->format('d/m/Y') : 'N/A';
$reason = $requestToCancel->reason;
$escapeMarkdown = function ($text) {
if ($text === null) {
return "";
}
return preg_replace('/([_*\[\]()~`>#\+\-=|{}.!\\\\])/', '\\\\$1', $text);
};
$confirmMessage = "คุณต้องการยกเลิกคำขอลา/WFH ต่อไปนี้ใช่หรือไม่?\n\n";
$confirmMessage .= "*ID:* {$escapeMarkdown((string) $requestToCancel->id)}\n";
$confirmMessage .= "*ประเภท:* {$escapeMarkdown($leaveTypeName)}\n";
$confirmMessage .= "*วันที่:* {$escapeMarkdown($startDate)} \\- {$escapeMarkdown($endDate)}\n";
if ($reason) {
$confirmMessage .= "*เหตุผล:* {$escapeMarkdown($reason)}\n";
}
$this->saveCancellationConfirmState($requestId);
$keyboardButtons = [
[['text' => '✅ ใช่, ยืนยันการยกเลิก', 'callback_data' => "cancelReq_confirm_{$requestId}"]],
[['text' => '❌ ไม่, กลับไปก่อน', 'callback_data' => 'cancelReq_abort']]
];
$replyMarkup = ['inline_keyboard' => $keyboardButtons];
$this->telegramService->sendMessage($this->chatId, $confirmMessage, $replyMarkup, 'MarkdownV2');
}
/**
* @param string $stateData
* @param string $actionCallbackData
* @return null
*/
public function handleCancellationConfirmation(string $stateData, string $actionCallbackData): void
{
if ($this->employeeId === null) {
$this->telegramService->sendMessage($this->chatId, "ขออภัยค่ะ ไม่พบข้อมูลพนักงาน");
$this->clearUserState();return;
}
if ($actionCallbackData === 'cancelReq_abort') {
$this->telegramService->sendMessage($this->chatId, "การยกเลิกถูกระงับค่ะ คุณสามารถเลือกคำขออื่นเพื่อยกเลิก หรือใช้คำสั่งอื่นได้ค่ะ");
$this->clearUserState();
// $this->startCancellationProcess(); // Optionally re-show list
return;
}
$actionParts = explode('_', $actionCallbackData); // e.g. cancelReq_confirm_123
$requestIdFromAction = (int) end($actionParts);
$stateParts = explode('_', $stateData); // e.g. cancelReq_confirm_123
$requestIdFromState = (int) end($stateParts);
if (!str_starts_with($actionCallbackData, "cancelReq_confirm_") ||
!str_starts_with($stateData, "cancelReq_confirm_") ||
$requestIdFromAction !== $requestIdFromState) {
$this->telegramService->sendMessage($this->chatId, "ข้อมูลการยืนยันไม่ตรงกันค่ะ กรุณาลองใหม่");
log_message("Mismatched confirmation data. Action: {$actionCallbackData}, State: {$stateData}");
$this->clearUserState();
return;
}
$requestId = $requestIdFromAction;
$leaveRequestModel = new LeaveRequest(Database::getInstance());
// It's crucial that read() re-fetches and populates the object fully, including its current status.
$requestToCancel = $leaveRequestModel->read($requestId);
if (!$requestToCancel || !isset($requestToCancel->employee_id) || $requestToCancel->employee_id !== $this->employeeId) {
$this->telegramService->sendMessage($this->chatId, "ไม่พบคำขอ (ID: {$requestId}) หรือคุณไม่มีสิทธิ์ยกเลิกคำขอนี้ค่ะ");
$this->clearUserState();
return;
}
if ($requestToCancel->status !== 'PENDING') {
$this->telegramService->sendMessage($this->chatId, "คำขอ ID: {$requestId} ไม่อยู่ในสถานะ 'รออนุมัติ' แล้วค่ะ (สถานะปัจจุบัน: {$requestToCancel->status})");
$this->clearUserState();
return;
}
// Use the existing updateStatus method of the loaded $requestToCancel object
// This requires $requestToCancel to be the object instance from read($requestId)
$requestToCancel->db = Database::getInstance(); // Ensure db is set if read() doesn't return $this
// Or, better, ensure read() returns the main object instance
// For now, let's re-instantiate for the update to be safe if read() returns a generic obj/array
$updateModel = new LeaveRequest(Database::getInstance());
$updateModel->id = $requestId; // Set the ID of the request to update
if ($updateModel->updateStatus('CANCELLED', $this->employeeId)) {
$this->telegramService->sendMessage($this->chatId, "✅ คำขอลา/WFH ID: {$requestId} ของคุณถูกยกเลิกเรียบร้อยแล้วค่ะ");
} else {
$this->telegramService->sendMessage($this->chatId, "❌ เกิดข้อผิดพลาดในการยกเลิกคำขอ ID: {$requestId} กรุณาลองใหม่อีกครั้ง หรือติดต่อผู้ดูแลค่ะ");
error_log("Failed to cancel leave request ID {$requestId} for employee {$this->employeeId}.");
}
$this->clearUserState();
}
}