diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php index 3cc2fbb..7bde71d 100644 --- a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php +++ b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php @@ -216,19 +216,20 @@ class TicketController extends Controller $reason = $request->input('reason'); $isAccept = $request->input('is_accept') ?? false; $user = auth('admins')->user(); // user create ticket - + $start_date = Carbon::create($startDate)->setTimezone(env('TIME_ZONE')); $end_date = Carbon::create($endDate)->setTimezone(env('TIME_ZONE')); + // Get mảng ngày nghỉ + $dataListPeriod = $this->getAllPeriodNew($start_date, $startPeriod, $end_date, $endPeriod); + if (empty($dataListPeriod)) { + return AbstractController::ResultError('Không thể tính toán khoảng thời gian nghỉ hợp lệ.'); + } + // --- Chỉ kiểm tra ngày phép khi loại là ONLEAVE --- if ($type === 'ONLEAVE' && !$isAccept) { - // Get mảng ngày nghỉ - $dataListPeriod = $this->getAllPeriodNew($start_date, $startPeriod, $end_date, $endPeriod); - if (empty($dataListPeriod)) { - return AbstractController::ResultError('Không thể tính toán khoảng thời gian nghỉ hợp lệ.'); - } // Lấy thông tin tickets nghỉ phép đang ở trạng thái WAITING - $ticketsWaiting = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->where('type', 'ONLEAVE') + $ticketsWaiting = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->whereIn('type', ['ONLEAVE']) ->get(); $dataListPeriodWaiting = []; if ($ticketsWaiting->count() > 0) { @@ -237,11 +238,34 @@ class TicketController extends Controller } } + $ticketsWaitingWFH = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->whereIn('type', ['WFH']) + ->get(); + $dataListPeriodWaitingWFH = []; + if ($ticketsWaitingWFH->count() > 0) { + foreach ($ticketsWaitingWFH as $ticket) { + $dataListPeriodWaitingWFH = array_merge($dataListPeriodWaitingWFH, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period)); + } + } + // Lấy thông tin tickets nghỉ phép đang ở trạng thái CONFIRMED - $ticketsConfirmed = Ticket::where('user_id', $user->id)->where('status', 'CONFIRMED') - ->whereIn('type', ['ONLEAVE']) - ->where(DB::raw('DATE(start_date)'), '>=', $start_date->toDateString()) - ->where(DB::raw('DATE(end_date)'), '<=', $end_date->toDateString()) + $ticketsConfirmed = Ticket::where('user_id', $user->id) + ->where('status', 'CONFIRMED') + ->whereIn('type', ['ONLEAVE', 'WFH']) + ->where(function ($query) use ($start_date, $end_date) { + $query->where(function ($q) use ($start_date, $end_date) { + // Trường hợp 1: start_date nằm trong khoảng + $q->whereBetween(DB::raw('DATE(start_date)'), [$start_date->toDateString(), $end_date->toDateString()]); + }) + ->orWhere(function ($q) use ($start_date, $end_date) { + // Trường hợp 2: end_date nằm trong khoảng + $q->whereBetween(DB::raw('DATE(end_date)'), [$start_date->toDateString(), $end_date->toDateString()]); + }) + ->orWhere(function ($q) use ($start_date, $end_date) { + // Trường hợp 3: Khoảng thời gian được chọn nằm trong khoảng của ticket + $q->where(DB::raw('DATE(start_date)'), '<=', $start_date->toDateString()) + ->where(DB::raw('DATE(end_date)'), '>=', $end_date->toDateString()); + }); + }) ->get(); $dataListPeriodConfirmed = []; @@ -250,10 +274,11 @@ class TicketController extends Controller $dataListPeriodConfirmed = array_merge($dataListPeriodConfirmed, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period)); } } - + // dd($dataListPeriodConfirmed,$ticketsConfirmed,$start_date->toDateString(),$end_date->toDateString()); // Chuyển đổi mảng đa chiều thành mảng chuỗi để có thể so sánh $periodStrings = []; $waitingPeriodStrings = []; + $waitingPeriodStringsWFH = []; $confirmedPeriodStrings = []; foreach ($dataListPeriod as $period) { @@ -274,6 +299,15 @@ class TicketController extends Controller } } + foreach ($dataListPeriodWaitingWFH as $period) { + if ($period['period'] == 'ALL') { + $waitingPeriodStringsWFH[] = $period['date'] . '_S'; + $waitingPeriodStringsWFH[] = $period['date'] . '_C'; + } else { + $waitingPeriodStringsWFH[] = $period['date'] . '_' . $period['period']; + } + } + foreach ($dataListPeriodConfirmed as $period) { if ($period['period'] == 'ALL') { $confirmedPeriodStrings[] = $period['date'] . '_S'; @@ -288,6 +322,11 @@ class TicketController extends Controller return AbstractController::ResultError('Đã có ticket đang chờ duyệt trong thời gian này, không thể tạo ticket mới!'); } + // Kiểm tra xem có sự trùng lặp giữa request mới và tickets đang chờ duyệt WFH + if (count(array_intersect($periodStrings, $waitingPeriodStringsWFH)) > 0) { + return AbstractController::ResultError('Đã có ticket đang chờ duyệt trong thời gian này, không thể tạo ticket mới!'); + } + // Kiểm tra xem có sự trùng lặp giữa request mới và tickets đã được duyệt if (count(array_intersect($periodStrings, $confirmedPeriodStrings)) > 0) { return AbstractController::ResultError('Đã có ticket được duyệt trong thời gian này, không thể tạo ticket mới!'); @@ -317,7 +356,7 @@ class TicketController extends Controller $monthsInfoWaiting = $balanceCheckResultWaiting['months_info']; if ($balanceCheckResultWaiting['success']) { $waitingTicketsMessage .= "------------------------------------------------"; - }else{ + } else { $waitingTicketsMessage .= $balanceCheckResultWaiting['message'] . "\n------------------------------------------------"; } $balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod, $monthsInfoWaiting); @@ -338,6 +377,85 @@ class TicketController extends Controller return AbstractController::ResultError("Không thỏa mãn điều kiện ngày phép", $balanceCheckResult); } + } else if ($type === 'WFH') { + // Lấy thông tin tickets nghỉ phép đang ở trạng thái WAITING + $ticketsWaiting = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->whereIn('type', ['WFH', 'ONLEAVE']) + ->get(); + $dataListPeriodWaiting = []; + if ($ticketsWaiting->count() > 0) { + foreach ($ticketsWaiting as $ticket) { + $dataListPeriodWaiting = array_merge($dataListPeriodWaiting, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period)); + } + } + + // Lấy thông tin tickets nghỉ phép đang ở trạng thái CONFIRMED + $ticketsConfirmed = Ticket::where('user_id', $user->id) + ->where('status', 'CONFIRMED') + ->whereIn('type', ['ONLEAVE', 'WFH']) + ->where(function ($query) use ($start_date, $end_date) { + $query->where(function ($q) use ($start_date, $end_date) { + // Trường hợp 1: start_date nằm trong khoảng + $q->whereBetween(DB::raw('DATE(start_date)'), [$start_date->toDateString(), $end_date->toDateString()]); + }) + ->orWhere(function ($q) use ($start_date, $end_date) { + // Trường hợp 2: end_date nằm trong khoảng + $q->whereBetween(DB::raw('DATE(end_date)'), [$start_date->toDateString(), $end_date->toDateString()]); + }) + ->orWhere(function ($q) use ($start_date, $end_date) { + // Trường hợp 3: Khoảng thời gian được chọn nằm trong khoảng của ticket + $q->where(DB::raw('DATE(start_date)'), '<=', $start_date->toDateString()) + ->where(DB::raw('DATE(end_date)'), '>=', $end_date->toDateString()); + }); + }) + ->get(); + + $dataListPeriodConfirmed = []; + if ($ticketsConfirmed->count() > 0) { + foreach ($ticketsConfirmed as $ticket) { + $dataListPeriodConfirmed = array_merge($dataListPeriodConfirmed, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period)); + } + } + // dd($dataListPeriodConfirmed,$ticketsConfirmed,$start_date->toDateString(),$end_date->toDateString()); + // Chuyển đổi mảng đa chiều thành mảng chuỗi để có thể so sánh + $periodStrings = []; + $waitingPeriodStrings = []; + $confirmedPeriodStrings = []; + + foreach ($dataListPeriod as $period) { + if ($period['period'] == 'ALL') { + $periodStrings[] = $period['date'] . '_S'; + $periodStrings[] = $period['date'] . '_C'; + } else { + $periodStrings[] = $period['date'] . '_' . $period['period']; + } + } + + foreach ($dataListPeriodConfirmed as $period) { + if ($period['period'] == 'ALL') { + $confirmedPeriodStrings[] = $period['date'] . '_S'; + $confirmedPeriodStrings[] = $period['date'] . '_C'; + } else { + $confirmedPeriodStrings[] = $period['date'] . '_' . $period['period']; + } + } + + foreach ($dataListPeriodWaiting as $period) { + if ($period['period'] == 'ALL') { + $waitingPeriodStrings[] = $period['date'] . '_S'; + $waitingPeriodStrings[] = $period['date'] . '_C'; + } else { + $waitingPeriodStrings[] = $period['date'] . '_' . $period['period']; + } + } + // Kiểm tra xem có sự trùng lặp giữa request mới và tickets đang chờ duyệt + if (count(array_intersect($periodStrings, $waitingPeriodStrings)) > 0) { + return AbstractController::ResultError('Đã có ticket đang chờ duyệt trong thời gian này, không thể tạo ticket mới!'); + } + + // Kiểm tra xem có sự trùng lặp giữa request mới và tickets đã được duyệt + if (count(array_intersect($periodStrings, $confirmedPeriodStrings)) > 0) { + return AbstractController::ResultError('Đã có ticket được duyệt trong thời gian này, không thể tạo ticket mới!'); + } } // --- Kết thúc kiểm tra --- @@ -377,7 +495,7 @@ class TicketController extends Controller ); // Thêm kiểm tra null trước khi gửi mail if ($dataMasterStartPeriod && $dataMasterEndPeriod && $dataMasterType) { - Mail::to($value->email)->send(new TicketMail($data)); + Mail::to($value->email)->send(new TicketMail($data)); } else { Log::error("Missing category data for ticket ID: {$ticket->id}. Mail not sent."); } @@ -393,11 +511,11 @@ class TicketController extends Controller * @param array|null $dataListPeriod Danh sách các ngày xin nghỉ [['date' => 'Y-m-d', 'period' => 'ALL|S|C'], ...] * @return array Kết quả kiểm tra ['success' => bool, 'message' => string|null, ...] */ - private function checkLeaveBalance($user, ?array $dataListPeriod = null, ?array $monthsInfoWaiting = null): array + private function checkLeaveBalance($user, ?array $dataListPeriod = null, ?array $monthsInfoWaiting = null, ?bool $isAccept = false): array { // Kiểm tra giới hạn nghỉ phép theo tháng if (!empty($dataListPeriod)) { - return $this->checkMonthlyLeaveLimit($user, $dataListPeriod, $monthsInfoWaiting); + return $this->checkMonthlyLeaveLimit($user, $dataListPeriod, $monthsInfoWaiting, $isAccept); } // Đủ điều kiện @@ -448,7 +566,7 @@ class TicketController extends Controller return $maxDaysPerMonth; } - private function checkMonthlyLeaveLimit($user, array $dataListPeriod, ?array $monthsInfoWaiting = null): array + private function checkMonthlyLeaveLimit($user, array $dataListPeriod, ?array $monthsInfoWaiting = null, ?bool $isAccept = false): array { // Danh sách ngày nghỉ theo tháng $requestMonths = $this->groupLeaveRequestsByMonth($dataListPeriod); @@ -479,7 +597,7 @@ class TicketController extends Controller $totalDaysInMonth = $usedDaysInMonth + $usedDaysInMonthWithoutPay + $monthData['days_requested']; // Tổng phép có trong tháng - $totalLeaveDaysInMonth = $this->getTotalLeaveDaysInMonth($user, $monthData['year'], $monthData['month']); + $totalLeaveDaysInMonth = $this->getTotalLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], $isAccept); // Tính tổng số ngày nghỉ có phép đến tháng hiện tại $totalLeaveDaysInMonthToMonth = $this->getTotalLeaveDaysInMonthToMonth($user, $monthData['year'], $monthData['month']); @@ -489,16 +607,16 @@ class TicketController extends Controller $remainingDaysInMonthRemaining = $remainingDaysInMonth - $remainingDaysInMonthIsUsed; // if ($monthsInfoWaiting) { - // dd( - // "Ngày phép còn lại trong tháng: " . $remainingDaysInMonthRemaining, - // "Ngày phép còn lại: " . $remainingDaysInMonth, - // "Ngày phép đã sử dụng: " . $remainingDaysInMonthIsUsed, - // "Ngày phép yêu cầu: " . $monthData['days_requested'], - // "Tổng ngày nghỉ trong tháng: " . $totalDaysInMonth, - // "Ngày phép đã sử dụng: " . $usedDaysInMonth, - // ); + // dd( + // "Ngày phép còn lại trong tháng: " . $remainingDaysInMonthRemaining, + // "Ngày phép còn lại: " . $remainingDaysInMonth, + // "Ngày phép đã sử dụng: " . $remainingDaysInMonthIsUsed, + // "Ngày phép yêu cầu: " . $monthData['days_requested'], + // "Tổng ngày nghỉ trong tháng: " . $totalDaysInMonth, + // "Ngày phép đã sử dụng: " . $usedDaysInMonth, + // ); // } - + $month_data_status = 'ok'; $days_will_use = 0; $days_will_use_without_pay = 0; @@ -512,33 +630,44 @@ class TicketController extends Controller $days_will_use = 0; $days_will_use_without_pay = $monthData['days_requested']; } else if ($remainingDaysInMonthRemaining < $monthData['days_requested']) { // không đủ ngày phép - $hasInsufficientDays = true; - $month_data_status = 'insufficient_days'; - $daysNotEnough = $monthData['days_requested'] - $remainingDaysInMonthRemaining; - $monthMessage = "* Tháng {$monthData['month']}/{$monthData['year']}: \n - Số ngày phép còn lại: {$remainingDaysInMonthRemaining}, Số ngày yêu cầu: {$monthData['days_requested']}.\n - Bạn sẽ sử dụng {$remainingDaysInMonthRemaining} ngày phép và {$daysNotEnough} ngày không phép."; - $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; - $remainingDaysInMonthIsUsed = $remainingDaysInMonth; // lấy số ngày phép còn lại của tháng đó + if ( + $remainingDaysInMonthRemaining >= $maxDaysPerMonth + ) { + $hasInsufficientDays = true; + $month_data_status = 'exceed_max_days'; + $daysNotEnough = $monthData['days_requested'] - $maxDaysPerMonth; + $monthMessage = "* Theo quy định ngày phép tối đa mỗi tháng là {$maxDaysPerMonth} ngày. \nTháng {$monthData['month']}/{$monthData['year']}: \n - Bạn đã sử dụng {$usedDaysInMonth} ngày phép, còn lại {$remainingDaysInMonthRemaining} ngày phép.\n - Bạn sẽ sử dụng {$maxDaysPerMonth} ngày phép và {$daysNotEnough} ngày không phép."; + $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; + $days_will_use = $maxDaysPerMonth; + $days_will_use_without_pay = $monthData['days_requested'] - $maxDaysPerMonth; + } else { + $hasInsufficientDays = true; + $month_data_status = 'insufficient_days'; + $daysNotEnough = $monthData['days_requested'] - $remainingDaysInMonthRemaining; + $monthMessage = "* Tháng {$monthData['month']}/{$monthData['year']}: \n - Số ngày phép còn lại: {$remainingDaysInMonthRemaining}, Số ngày yêu cầu: {$monthData['days_requested']}.\n - Bạn sẽ sử dụng {$remainingDaysInMonthRemaining} ngày phép và {$daysNotEnough} ngày không phép."; + $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; + $remainingDaysInMonthIsUsed = $remainingDaysInMonth; // lấy số ngày phép còn lại của tháng đó - $days_will_use = $remainingDaysInMonthRemaining; - $days_will_use_without_pay = $daysNotEnough; + $days_will_use = $remainingDaysInMonthRemaining; + $days_will_use_without_pay = $daysNotEnough; + } } else if ( $remainingDaysInMonthRemaining >= $monthData['days_requested'] - // || $remainingDaysInMonthIsUsed + $monthData['days_requested'] > $maxDaysPerMonth ) { // Đủ ngày phép ở tháng đó // 1. Check thêm rule 1 tháng chỉ được nghỉ tối đa $maxDaysPerMonth ngày có phép, ngày vượt sẽ là ngày không phép - if($totalDaysInMonth > $maxDaysPerMonth){ + if ($totalDaysInMonth > $maxDaysPerMonth) { $daysWithoutPermission = $totalDaysInMonth - $maxDaysPerMonth; $daysWillUse = $maxDaysPerMonth - $usedDaysInMonth; // số ngày phép sẽ sử dụng $hasInsufficientDays = true; $month_data_status = 'exceed_max_days'; - + $monthMessage = "* Theo quy định ngày phép tối đa mỗi tháng là {$maxDaysPerMonth} ngày. \nTháng {$monthData['month']}/{$monthData['year']}: \n - Bạn đã sử dụng {$usedDaysInMonth} ngày phép, còn lại {$remainingDaysInMonthRemaining} ngày phép.\n - Bạn sẽ sử dụng " . $daysWillUse . " ngày phép và {$daysWithoutPermission} ngày không phép."; $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; $days_will_use = $daysWillUse; $days_will_use_without_pay = $daysWithoutPermission; - }else if ($monthData['days_requested'] + $remainingDaysInMonthIsUsed > $maxDaysPerMonth) { + } else if ($monthData['days_requested'] + $remainingDaysInMonthIsUsed > $maxDaysPerMonth) { if ($remainingDaysInMonthIsUsed > 0) { $daysWillUse = $maxDaysPerMonth - $remainingDaysInMonthIsUsed; // số ngày phép sẽ sử dụng @@ -615,7 +744,7 @@ class TicketController extends Controller ->sum('categories.c_value'); } - private function getTotalLeaveDaysInMonth($user, int $year, int $month): float + private function getTotalLeaveDaysInMonth($user, int $year, int $month, ?bool $isAccept = false): float { $leaveDaysInfo = LeaveDays::where('ld_user_id', $user->id) ->where('ld_year', $year) @@ -628,8 +757,15 @@ class TicketController extends Controller // } else { $totalAllocated = $leaveDaysInfo->ld_day_total; // } - // $totalAllocated = $month; //(+ tạm để check) + + // Nếu là duyệt ticket thì sẽ cộng thêm số ngày phép còn lại của tháng hiện tại + if ($isAccept) { + if ($month >= $totalAllocated) { + $totalAllocated += $month - $totalAllocated; + } + } // bên hàm duyệt ticket sẽ check lại để + 1 ngày trước job để đảm bảo đủ ngày phép + } else { Log::warning("No LeaveDays record found for user ID: {$user->id}, year: {$year}. Assuming 0 allocated days."); } @@ -758,8 +894,8 @@ class TicketController extends Controller if ($action == "confirm") { if ($ticket->type == "ONLEAVE") { $dataListPeriod = $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period); - $balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod); - // dd($balanceCheckResult,$dataListPeriod); + $balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod, null, true); + // dd($balanceCheckResult, $dataListPeriod); if ($balanceCheckResult['success'] == false) { if ($balanceCheckResult['months_info']) { foreach ($balanceCheckResult['months_info'] as $monthInfo) { @@ -877,27 +1013,25 @@ class TicketController extends Controller Log::warning("User ID: {$ticket->user_id} has used more leave days ({$totalUsedLeaveDays}) than allocated ({$totalAllocatedDays})"); // Cập nhật cột ld_day_total với số ngày đã làm tròn - if ($roundedExcessDays > 0) { - $leaveDaysInfo->ld_day_total += $roundedExcessDays; - $leaveDaysInfo->save(); + $leaveDaysInfo->ld_day_total += $roundedExcessDays; + $leaveDaysInfo->save(); - Log::info("Updated leave days for User ID: {$ticket->user_id}. Added {$roundedExcessDays} days (rounded from {$excessDays})"); - } + Log::info("Updated leave days for User ID: {$ticket->user_id}. Added {$roundedExcessDays} days (rounded from {$excessDays})"); } } } else if ($ticket->type == "WFH") { $dataListPeriod = $this->getAllPeriod($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period); foreach ($dataListPeriod as $result) { - list($year, $month, $day) = explode('-', $result['date']); - Notes::create([ - 'n_user_id' => $ticket->user_id, - 'n_day' => $day, - 'n_month' => $month, - 'n_year' => $year, - 'n_time_type' => $result['period'], - 'n_reason' => $ticket->type, - 'n_note' => $ticket->reason - ]); + list($year, $month, $day) = explode('-', $result['date']); + Notes::create([ + 'n_user_id' => $ticket->user_id, + 'n_day' => $day, + 'n_month' => $month, + 'n_year' => $year, + 'n_time_type' => $result['period'], + 'n_reason' => $ticket->type, + 'n_note' => $ticket->reason + ]); //WFH - start tracking