CancelRequestHandler.php

12.62 KB
08/07/2025 10:47
PHP
CancelRequestHandler.php
<?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();
    }
}