From 655d9cd4c6f8ea861340430b1d035ab90067e314 Mon Sep 17 00:00:00 2001 From: Truong Vo <41848815+vmtruong301296@users.noreply.github.com> Date: Mon, 16 Jun 2025 09:29:45 +0700 Subject: [PATCH] =?UTF-8?q?Hi=E1=BB=87u=20ch=E1=BB=89nh=20ng=C3=A0y=20ph?= =?UTF-8?q?=C3=A9p?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/Http/Controllers/TicketController.php | 177 ++++++++++++------ 1 file changed, 119 insertions(+), 58 deletions(-) diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php index 2e21060..4f38421 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) { @@ -240,21 +241,21 @@ class TicketController extends Controller // 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(function($query) use ($start_date, $end_date) { - $query->where(function($q) use ($start_date, $end_date) { + ->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()); - }); + ->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(); @@ -306,7 +307,7 @@ class TicketController extends Controller 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!'); } - + // Tạo thông báo về tickets waiting nếu có $waitingTicketsMessage = ''; if (!empty($dataListPeriodWaiting)) { @@ -331,7 +332,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); @@ -352,6 +353,62 @@ 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 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']; + } + } + + // 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 --- @@ -389,7 +446,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."); } @@ -405,11 +462,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 @@ -460,7 +517,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); @@ -491,7 +548,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']); @@ -501,16 +558,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; @@ -535,22 +592,21 @@ class TicketController extends Controller $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 @@ -627,7 +683,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) @@ -640,8 +696,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."); } @@ -770,8 +833,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) { @@ -889,27 +952,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