orderByRequest($tickets, $request); // Filter $this->filterRequest( builder: $tickets, request: $request, filterKeys: [ 'type' => self::F_TEXT, 'reason' => self::F_TEXT, 'updated_by' => self::F_TEXT, 'start_date' => [ 'type' => self::F_THAN_EQ_DATETIME, 'column' => 'tickets.start_date' ], 'end_date' => [ 'type' => self::F_THAN_EQ_DATETIME, 'column' => 'tickets.end_date' ], ] ); $this->searchRequest( builder: $tickets, value: $request->get('search'), fields: [ 'users.name', "start_date", "startPeriod.c_name", "end_date", "endPeriod.c_name", "typeReason.c_name", 'status', "reason", ] ); $responseData = array_merge( $tickets ->join('users', 'tickets.user_id', '=', 'users.id') ->leftJoin("categories as startPeriod", function ($join) { $join->on('start_period', '=', 'startPeriod.c_code'); $join->on('startPeriod.c_type', DB::raw("CONCAT('TIME_TYPE')")); }) ->leftJoin("categories as endPeriod", function ($join) { $join->on('end_period', '=', 'endPeriod.c_code'); $join->on('endPeriod.c_type', DB::raw("CONCAT('TIME_TYPE')")); }) ->leftJoin("categories as typeReason", function ($join) { $join->on('type', '=', 'typeReason.c_code'); $join->on('typeReason.c_type', DB::raw("CONCAT('REASON')")); }) ->leftJoin("categories as statusTickets", function ($join) { $join->on('status', '=', 'statusTickets.c_code'); $join->on('statusTickets.c_type', DB::raw("CONCAT('STATUS_TICKETS')")); }) ->where('user_id', auth('admins')->user()->id)->orderBy('tickets.created_at', 'desc') ->select( 'users.name as user_name', 'users.email', 'tickets.*', 'startPeriod.c_name as startPeriodName', 'endPeriod.c_name as endPeriodName', 'typeReason.c_name as typeReasonName', 'statusTickets.c_name as statusTicketsName', ) ->paginate($request->get('per_page'))->toArray(), ['status' => true] ); return response()->json($responseData); } public function getAll(Request $request) { $tickets = new Ticket; // Order by $this->orderByRequest($tickets, $request); // Filter $this->filterRequest( builder: $tickets, request: $request, filterKeys: [ 'type' => self::F_TEXT, 'reason' => self::F_TEXT, 'updated_by' => self::F_TEXT, 'start_date' => [ 'type' => self::F_THAN_EQ_DATETIME, 'column' => 'tickets.start_date' ], 'end_date' => [ 'type' => self::F_THAN_EQ_DATETIME, 'column' => 'tickets.end_date' ], ] ); $this->searchRequest( builder: $tickets, value: $request->get('search'), fields: [ 'users.name', "start_date", "startPeriod.c_name", "end_date", "endPeriod.c_name", "typeReason.c_name", 'status', "reason", "admin_note" ] ); if ($request->typeReason != '') { $tickets = $tickets->where('tickets.type', '=', $request->typeReason); } if ($request->statusFilter != '') { $tickets = $tickets->where('tickets.status', '=', $request->statusFilter); } $responseData = array_merge( $tickets ->join('users', 'tickets.user_id', '=', 'users.id') ->leftJoin("categories as startPeriod", function ($join) { $join->on('start_period', '=', 'startPeriod.c_code'); $join->on('startPeriod.c_type', DB::raw("CONCAT('TIME_TYPE')")); }) ->leftJoin("categories as endPeriod", function ($join) { $join->on('end_period', '=', 'endPeriod.c_code'); $join->on('endPeriod.c_type', DB::raw("CONCAT('TIME_TYPE')")); }) ->leftJoin("categories as typeReason", function ($join) { $join->on('type', '=', 'typeReason.c_code'); $join->on('typeReason.c_type', DB::raw("CONCAT('REASON')")); }) ->leftJoin("categories as statusTickets", function ($join) { $join->on('status', '=', 'statusTickets.c_code'); $join->on('statusTickets.c_type', DB::raw("CONCAT('STATUS_TICKETS')")); }) ->select( 'users.name as user_name', 'users.email', 'tickets.*', 'startPeriod.c_name as startPeriodName', 'endPeriod.c_name as endPeriodName', 'typeReason.c_name as typeReasonName', 'statusTickets.c_name as statusTicketsName', ) ->orderBy('tickets.created_at', 'desc')->paginate($request->get('per_page'))->toArray(), ['status' => true] ); return response()->json($responseData); } public function createTicket(Request $request) { // Define validation rules $rules = [ 'start_date' => 'required|date', 'start_period' => 'required|string', 'end_date' => 'required|date|after_or_equal:start_date', 'end_period' => 'required|string', 'type' => 'required|string', ]; // Validate the request $request->validate($rules); // return $request; // Get data from request $startDate = $request->input('start_date'); $startPeriod = $request->input('start_period'); $endDate = $request->input('end_date'); $endPeriod = $request->input('end_period'); $type = $request->input('type'); $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')); // --- 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') ->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']) ->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)); } } // 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 ($dataListPeriodWaiting as $period) { if ($period['period'] == 'ALL') { $waitingPeriodStrings[] = $period['date'] . '_S'; $waitingPeriodStrings[] = $period['date'] . '_C'; } else { $waitingPeriodStrings[] = $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 đ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!'); } // Tạo thông báo về tickets waiting nếu có $waitingTicketsMessage = ''; if (!empty($dataListPeriodWaiting)) { // Kiểm tra số dư ngày phép cho tickets waiting $waitingTicketsMessage = "Bạn đang có " . $ticketsWaiting->count() . " yêu cầu nghỉ phép chưa được duyệt"; // Nếu muốn thêm chi tiết từng ticket waiting if ($ticketsWaiting->count() > 0) { $waitingTicketsMessage .= ":\n"; foreach ($ticketsWaiting as $ticket) { $startDateFormat = Carbon::parse($ticket->start_date)->format('d/m/Y'); $endDateFormat = Carbon::parse($ticket->end_date)->format('d/m/Y'); $waitingTicketsMessage .= "- " . $ticket->startPeriodName . " (" . $startDateFormat . ") - " . $ticket->endPeriodName . " (" . $endDateFormat . ")\n"; } } } $balanceCheckResultWaiting = $this->checkLeaveBalance($user, $dataListPeriodWaiting); // dd($balanceCheckResultWaiting,$dataListPeriodWaiting,$user); if ($balanceCheckResultWaiting['months_info']) { $monthsInfoWaiting = $balanceCheckResultWaiting['months_info']; if ($balanceCheckResultWaiting['success']) { $waitingTicketsMessage .= "------------------------------------------------"; }else{ $waitingTicketsMessage .= $balanceCheckResultWaiting['message'] . "\n------------------------------------------------"; } $balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod, $monthsInfoWaiting); // dd($balanceCheckResult, $waitingTicketsMessage); } else { $balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod); } // dd($balanceCheckResult); // Nếu không đủ ngày phép, trả về thông báo và không tạo ticket if (!$balanceCheckResult['success']) { $finalMessage = $waitingTicketsMessage; if (!empty($finalMessage)) { $finalMessage .= "\n\n"; } $finalMessage .= $balanceCheckResult['message']; $balanceCheckResult['message'] = $finalMessage . "\n\nBạn có chấp nhận không?\n"; $balanceCheckResult['waitingTicketMessage'] = $waitingTicketsMessage; return AbstractController::ResultError("Không thỏa mãn điều kiện ngày phép", $balanceCheckResult); } } // --- Kết thúc kiểm tra --- // Nếu đủ ngày phép (hoặc loại ticket không phải ONLEAVE), tiếp tục tạo ticket $ticket = Ticket::create([ 'start_date' => $start_date->toDateString(), 'start_period' => $startPeriod, 'end_date' => $end_date->toDateString(), 'end_period' => $endPeriod, 'type' => $type, 'status' => 'WAITING', 'reason' => $reason, 'user_id' => $user->id ]); // Send notification email to admin (list) $dataMasterStartPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $startPeriod); $dataMasterEndPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $endPeriod); $dataMasterType = CategoryController::getListMasterByCodeAndType("REASON", $type); $formattedStartDate = Carbon::createFromFormat('Y-m-d', $startDate)->format('d/m/Y'); $formattedEndDate = Carbon::createFromFormat('Y-m-d', $endDate)->format('d/m/Y'); $admins = Admin::where('permission', 'like', '%admin%')->get(); foreach ($admins as $key => $value) { $data = array( "ticket_id" => $ticket->id, "email_template" => "email.notification_tickets", "email" => $user->email, "admin_email" => $value->email, "name" => $user->name, "date" => optional($dataMasterStartPeriod)->c_name . " (" . $formattedStartDate . ") - " . optional($dataMasterEndPeriod)->c_name . " (" . $formattedEndDate . ")", "type" => optional($dataMasterType)->c_name, "note" => $reason, "link" => "/tickets-management", //link đến page admin "subject" => "[Ticket request] Ticket From " . $user->name ); // Thêm kiểm tra null trước khi gửi mail if ($dataMasterStartPeriod && $dataMasterEndPeriod && $dataMasterType) { Mail::to($value->email)->send(new TicketMail($data)); } else { Log::error("Missing category data for ticket ID: {$ticket->id}. Mail not sent."); } } return response()->json(['data' => $ticket, 'status' => true]); } /** * Kiểm tra số dư ngày phép của người dùng. * * @param UserModel $user Người dùng tạo ticket * @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 { // Kiểm tra giới hạn nghỉ phép theo tháng if (!empty($dataListPeriod)) { return $this->checkMonthlyLeaveLimit($user, $dataListPeriod, $monthsInfoWaiting); } // Đủ điều kiện return [ 'success' => true, 'message' => null, 'months_info' => [] ]; } private function getTotalAllocatedDays($user, int $year): float { $leaveDaysInfo = LeaveDays::where('ld_user_id', $user->id) ->where('ld_year', $year) ->first(); $totalAllocated = 0; if ($leaveDaysInfo) { $totalAllocated = $leaveDaysInfo->ld_day_total + $leaveDaysInfo->ld_additional_day + $leaveDaysInfo->ld_special_leave_day; } else { Log::warning("No LeaveDays record found for user ID: {$user->id}, year: {$year}. Assuming 0 allocated days."); } return $totalAllocated; } private function getUsedLeaveDays($user, int $year): float { return Notes::join('categories', function ($join) { $join->on('notes.n_time_type', '=', 'categories.c_code') ->where('categories.c_type', 'TIME_TYPE'); }) ->where('n_user_id', $user->id) ->where('n_year', $year) ->where('n_reason', 'ONLEAVE') ->sum('categories.c_value'); } //Tính tổng giới hạn ngày nghỉ có phép tối đa trong tháng private function getMaxLeaveDaysPerMonth(): int { $limitLeaveMonth = Category::where('c_type', 'LIMIT_LEAVE_MONTH')->where('c_code', "LIMIT")->first(); if ($limitLeaveMonth) { $maxDaysPerMonth = (int)$limitLeaveMonth->c_value; } else { $maxDaysPerMonth = 3; // default nếu k có setting } return $maxDaysPerMonth; } private function checkMonthlyLeaveLimit($user, array $dataListPeriod, ?array $monthsInfoWaiting = null): array { // Danh sách ngày nghỉ theo tháng $requestMonths = $this->groupLeaveRequestsByMonth($dataListPeriod); $monthsInfo = []; $hasInsufficientDays = false; $errorMessage = ''; $remainingDaysInMonthIsUsed = 0; // Tổng giới hạn ngày nghỉ có phép tối đa trong tháng $maxDaysPerMonth = $this->getMaxLeaveDaysPerMonth(); foreach ($requestMonths as $monthKey => $monthData) { if ($monthsInfoWaiting) { // dd($requestMonths, $monthsInfoWaiting); foreach ($monthsInfoWaiting as $monthInfo) { if ($monthInfo['month'] == $monthData['month'] && $monthInfo['year'] == $monthData['year']) { $remainingDaysInMonthIsUsed += $monthInfo['remaining_days_in_month_remaining']; // dd($remainingDaysInMonthIsUsed); } } } // Tổng số ngày nghỉ có phép trong tháng $usedDaysInMonth = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'ONLEAVE'); // Tổng số ngày nghỉ không phép trong tháng $usedDaysInMonthWithoutPay = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'LEAVE_WITHOUT_PAY'); // Tổng số ngày nghỉ trong tháng = tổng ngày nghỉ có phép + tổng ngày nghỉ không phép + tổng ngày yêu cầu $totalDaysInMonth = $usedDaysInMonth + $usedDaysInMonthWithoutPay + $monthData['days_requested']; // Tổng phép có trong tháng $totalLeaveDaysInMonth = $this->getTotalLeaveDaysInMonth($user, $monthData['year'], $monthData['month']); // 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']); //Ngày phép còn lại trong tháng $remainingDaysInMonth = $totalLeaveDaysInMonth - $totalLeaveDaysInMonthToMonth; $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, // ); // } $month_data_status = 'ok'; $days_will_use = 0; $days_will_use_without_pay = 0; // Xử lý các trường hợp thiếu ngày phép if ($remainingDaysInMonthRemaining <= 0) { //hết phép $hasInsufficientDays = true; $month_data_status = 'no_days_left'; $monthMessage = "* Hiện tại bạn đã hết phép nghỉ trong tháng {$monthData['month']}/{$monthData['year']}\n - Bạn sẽ nộp: " . $monthData['days_requested'] . " ngày không phép."; $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; $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 đó $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){ $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) { if ($remainingDaysInMonthIsUsed > 0) { $daysWillUse = $maxDaysPerMonth - $remainingDaysInMonthIsUsed; // số ngày phép sẽ sử dụng $daysWithoutPermission = $monthData['days_requested'] - $daysWillUse; $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 {$remainingDaysInMonthIsUsed} 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."; } else { $daysWithoutPermission = $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 - 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 {$maxDaysPerMonth} ngày phép và {$daysWithoutPermission} ngày không phép."; } $hasInsufficientDays = true; $month_data_status = 'exceed_max_days'; $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; $days_will_use = $maxDaysPerMonth; $days_will_use_without_pay = $daysWithoutPermission; } else { $days_will_use = $monthData['days_requested']; $days_will_use_without_pay = 0; } $remainingDaysInMonthRemaining = $monthData['days_requested']; } else { $days_will_use = $monthData['days_requested']; $days_will_use_without_pay = 0; } $month_data = [ 'year' => $monthData['year'], 'month' => $monthData['month'], 'total_leave_days_in_month' => $totalLeaveDaysInMonth, //tổng số ngày phép 'total_leave_days_in_month_to_month' => $totalLeaveDaysInMonthToMonth, //tổng ngày nghỉ có phép đã nghỉ 'remaining_days_in_month' => $remainingDaysInMonth, //số ngày phép còn lại 'days_used' => $usedDaysInMonth, //tổng số ngày nghỉ có phép đã nghỉ ở tháng hiện tại 'days_used_without_pay' => $usedDaysInMonthWithoutPay, //tổng số ngày nghỉ không phép đã nghỉ ở tháng hiện tại 'days_requested' => $monthData['days_requested'], //số ngày yêu cầu nghỉ của tháng 'remaining_days_in_month_remaining' => $remainingDaysInMonthRemaining, 'days_will_use' => $days_will_use, //Số ngày phép sẽ sử dụng 'days_will_use_without_pay' => $days_will_use_without_pay, //Số ngày không phép sẽ sử dụng 'status' => $month_data_status, // mặc định là ok ]; // Thêm thông tin tháng vào mảng kết quả $monthsInfo[] = $month_data; } // Trả về kết quả tổng hợp if ($hasInsufficientDays) { return [ 'success' => false, 'message' => $errorMessage, 'warning_type' => 'exceed_monthly_limit', 'months_info' => $monthsInfo ]; } return [ 'success' => true, 'message' => "Đủ ngày phép cho yêu cầu.", 'months_info' => $monthsInfo ]; } //Tính tổng số ngày nghỉ có phép đến tháng hiện tại private function getTotalLeaveDaysInMonthToMonth($user, int $year, int $month): float { return Notes::join('categories', function ($join) { $join->on('notes.n_time_type', '=', 'categories.c_code') ->where('categories.c_type', 'TIME_TYPE'); }) ->where('n_user_id', $user->id) ->where('n_year', $year) ->where('n_month', "<=", $month) ->where('n_reason', 'ONLEAVE') ->sum('categories.c_value'); } private function getTotalLeaveDaysInMonth($user, int $year, int $month): float { $leaveDaysInfo = LeaveDays::where('ld_user_id', $user->id) ->where('ld_year', $year) ->first(); $totalAllocated = 0; if ($leaveDaysInfo) { // if ($leaveDaysInfo->ld_day_total > $month) { // $totalAllocated = $month; // } else { $totalAllocated = $leaveDaysInfo->ld_day_total; // } // $totalAllocated = $month; //(+ tạm để check) // 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."); } $totalAllocated = $totalAllocated + $leaveDaysInfo->ld_additional_day + $leaveDaysInfo->ld_special_leave_day; return $totalAllocated; } private function groupLeaveRequestsByMonth(array $dataListPeriod): array { $requestMonths = []; foreach ($dataListPeriod as $periodData) { $date = Carbon::parse($periodData['date']); $monthKey = $date->format('Y-m'); // YYYY-MM if (!isset($requestMonths[$monthKey])) { $requestMonths[$monthKey] = [ 'year' => $date->year, 'month' => $date->month, 'days_requested' => 0, 'days_used' => 0 ]; } // Tính số ngày yêu cầu trong tháng $dayValue = ($periodData['period'] === 'ALL') ? 1.0 : 0.5; $requestMonths[$monthKey]['days_requested'] += $dayValue; } return $requestMonths; } private function getUsedLeaveDaysInMonth($user, int $year, int $month, string $reason): float { return Notes::join('categories', function ($join) { $join->on('notes.n_time_type', '=', 'categories.c_code') ->where('categories.c_type', 'TIME_TYPE'); }) ->where('n_user_id', $user->id) ->where('n_year', $year) ->where('n_month', $month) ->where('n_reason', $reason) ->sum('categories.c_value'); } public function deleteTicket(Request $request) { $rules = [ 'ticket_id' => 'required' ]; // Validate the request $request->validate($rules); $user = auth('admins')->user(); $ticket_id = $request->input('ticket_id'); $ticket = Ticket::find($ticket_id); if ($ticket) { // $user->id == user_id of ticket ---> delete if ($ticket->user_id == $user->id) { $ticket->delete(); return response()->json(['message' => 'delete success', 'status' => true]); } else { return response()->json(['message' => 'You are committing an act of vandalism', 'status' => false]); } } return response()->json(['message' => 'Delete fail', 'status' => false]); } public function handleTicket(Request $request) { $rules = [ 'ticket_id' => 'required', 'action' => 'required', // 'admin_note' => 'required' ]; // Validate the request $request->validate($rules); $ticket_id = $request->input('ticket_id'); $admin_note = $request->input('admin_note'); $action = $request->input('action'); // 'confirm' or 'refuse' $admin = auth('admins')->user(); $ticket = Ticket::find($ticket_id); if (!$ticket || $ticket->status !== "WAITING") { return response()->json(['message' => "Ticket not found", 'status' => false]); } // Confirm // Update updated_by and admin_note in tickets table // Refuse // Update updated_by and admin_note in tickets table // Send notification email to users $startDate = $ticket->start_date; //Start day $startPeriod = $ticket->start_period; //The session begins $endDate = $ticket->end_date; //End date $endPeriod = $ticket->end_period; //Session ends $type = $ticket->type; $dataMasterStartPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $startPeriod); $dataMasterEndPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $endPeriod); $dataMasterType = CategoryController::getListMasterByCodeAndType("REASON", $type); $dataMasterTypeNotes = CategoryController::getListMasterByType("REASON_NOTES"); $onleave = null; $leaveWithoutPay = null; if ($dataMasterTypeNotes) { // get nghỉ phép, nghỉ không phép $onleave = optional($dataMasterTypeNotes->where('c_code', 'ONLEAVE')->first())->c_code; $leaveWithoutPay = optional($dataMasterTypeNotes->where('c_code', 'LEAVE_WITHOUT_PAY')->first())->c_code; } $formattedStartDate = Carbon::createFromFormat('Y-m-d', $startDate)->format('d/m/Y'); $formattedEndDate = Carbon::createFromFormat('Y-m-d', $endDate)->format('d/m/Y'); $user = Admin::find($ticket->user_id); if ($onleave == null || $leaveWithoutPay == null) { return response()->json(['message' => "Data reason notes not found", 'status' => false]); } 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); if ($balanceCheckResult['success'] == false) { if ($balanceCheckResult['months_info']) { foreach ($balanceCheckResult['months_info'] as $monthInfo) { // Lọc các ngày thuộc đúng tháng/năm này $daysInMonth = array_filter($dataListPeriod, function ($item) use ($monthInfo) { $date = \Carbon\Carbon::parse($item['date']); return $date->year == $monthInfo['year'] && $date->month == $monthInfo['month']; }); $daysWillUse = $monthInfo['days_will_use'] ?? 0; $daysWillUseWithoutPay = $monthInfo['days_will_use_without_pay'] ?? 0; // dd($daysWillUse,$daysWillUseWithoutPay,$daysInMonth); foreach ($daysInMonth as $item) { list($year, $month, $day) = explode('-', $item['date']); $period = $item['period']; $value = ($period === 'ALL') ? 1.0 : 0.5; if ($period === 'ALL' && $daysWillUse == 0.5) { // Chỉ còn 0.5 phép, chia thành 2 bản ghi: 1 phép, 1 không phép // Ưu tiên phép cho buổi sáng (S), không phép cho buổi chiều (C) Notes::create([ 'n_user_id' => $ticket->user_id, 'n_day' => $day, 'n_month' => $month, 'n_year' => $year, 'n_time_type' => 'S', 'n_reason' => $onleave, 'n_note' => $ticket->reason ]); Notes::create([ 'n_user_id' => $ticket->user_id, 'n_day' => $day, 'n_month' => $month, 'n_year' => $year, 'n_time_type' => 'C', 'n_reason' => $leaveWithoutPay, 'n_note' => $ticket->reason ]); $daysWillUse = 0; $daysWillUseWithoutPay -= 0.5; } elseif ($daysWillUse > 0) { // Dùng ngày phép trước $use = min($daysWillUse, $value); Notes::create([ 'n_user_id' => $ticket->user_id, 'n_day' => $day, 'n_month' => $month, 'n_year' => $year, 'n_time_type' => $period, 'n_reason' => $onleave, 'n_note' => $ticket->reason ]); $daysWillUse -= $use; } elseif ($daysWillUseWithoutPay > 0) { // Hết phép, chuyển sang không phép $use = min($daysWillUseWithoutPay, $value); Notes::create([ 'n_user_id' => $ticket->user_id, 'n_day' => $day, 'n_month' => $month, 'n_year' => $year, 'n_time_type' => $period, 'n_reason' => $leaveWithoutPay, 'n_note' => $ticket->reason ]); $daysWillUseWithoutPay -= $use; } // Nếu cả hai đều hết thì thôi, không tạo nữa } } } } else { //Đủ phép 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' => $onleave, // có phép 'n_note' => $ticket->reason ]); } } $yearCheck = Carbon::parse($endDate)->year; // Check giá trị ld_day_total của bảng leave_days thuộc user id đó với giá trị của list item note trong bảng notes của user id đó $leaveDaysInfo = LeaveDays::where('ld_user_id', $ticket->user_id) ->where('ld_year', $yearCheck) ->first(); if ($leaveDaysInfo) { // Tính tổng số ngày nghỉ có phép đã sử dụng trong năm $totalUsedLeaveDays = Notes::join('categories', function ($join) { $join->on('notes.n_time_type', '=', 'categories.c_code') ->where('categories.c_type', 'TIME_TYPE'); }) ->where('n_user_id', $ticket->user_id) ->where('n_year', $yearCheck) ->where('n_reason', 'ONLEAVE') ->sum('categories.c_value'); // Tính tổng số ngày phép được cấp $totalAllocatedDays = $leaveDaysInfo->ld_day_total + $leaveDaysInfo->ld_additional_day + $leaveDaysInfo->ld_special_leave_day; // Tính số ngày vượt quá và làm tròn lên $excessDays = $totalUsedLeaveDays - $totalAllocatedDays; $roundedExcessDays = ceil($excessDays); // Làm tròn lên số nguyên gần nhất // Kiểm tra nếu số ngày đã sử dụng vượt quá số ngày được cấp if ($roundedExcessDays > 0) { 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(); 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 ]); //WFH - start tracking $type = $result['period']; $date = Carbon::create($year, $month, $day)->setTimezone(env('TIME_ZONE')); //Default: ALL $start = $date->copy()->setTime(7, 30, 0); $end = $date->copy()->setTime(17, 0, 0); if ($type == 'S') { $end = $date->copy()->setTime(11, 30, 0); } else if ($type == 'C') { $start = $date->copy()->setTime(11, 30, 0); } Tracking::insert([ [ 'name' => $user->name, 'user_id' => $user->id, 'status' => 'check in', 'time_string' => $start->format('Y-m-d H:i:s'), 'created_at' => $start->setTimezone('UTC') ], [ 'name' => $user->name, 'user_id' => $user->id, 'status' => 'check out', 'time_string' => $end->format('Y-m-d H:i:s'), 'created_at' => $end->setTimezone('UTC') ] ]); //WFH - end tracking } } $ticket['updated_by'] = $admin->name; $ticket['admin_note'] = $admin_note; $ticket['status'] = 'CONFIRMED'; $ticket->save(); $this->createOrUpdateRecordForCurrentMonth($month, $year); // Send notification email to users $data = array( "email_template" => "email.notification_tickets_user", "user_name" => $user->name, "email" => $user->email, "name" => $admin->name, //name admin duyệt "date" => $dataMasterStartPeriod->c_name . " (" . $formattedStartDate . ") - " . $dataMasterEndPeriod->c_name . " (" . $formattedEndDate . ")", "type" => $dataMasterType->c_name, "note" => $ticket->reason, "admin_note" => $admin_note, "link" => "/tickets", //link đến page admin "status" => "confirmed", "subject" => "[Ticket response] Ticket From " . $admin->name ); Mail::to($user->email)->send(new TicketMail($data)); return response()->json(['message' => "confirmed", 'status' => true]); } if ($action == "refuse") { $ticket['updated_by'] = $admin->name; $ticket['admin_note'] = $admin_note; $ticket['status'] = 'REFUSED'; $ticket->save(); $data = array( "email_template" => "email.notification_tickets_user", "user_name" => $user->name, "email" => $user->email, "name" => $admin->name, //name admin duyệt "date" => $dataMasterStartPeriod->c_name . " (" . $formattedStartDate . ") - " . $dataMasterEndPeriod->c_name . " (" . $formattedEndDate . ")", "type" => $dataMasterType->c_name, "note" => $ticket->reason, "admin_note" => $admin_note, "link" => "/tickets", //link đến page admin "status" => "refused", "subject" => "[Ticket response] Ticket From " . $admin->name ); Mail::to($user->email)->send(new TicketMail($data)); return response()->json(['message' => "refused", 'status' => true]); } return response()->json(['message' => "failed", 'status' => false]); } // Handle Logic same as HandleTicket, but need email to identify admin and redirect to Management page public function handleTicketEmail(Request $request) { $rules = [ 'ticket_id' => 'required', 'action' => 'required', 'admin_email' => 'required' // Need Admin Email ]; // Validate the request $request->validate($rules); $ticket_id = $request->input('ticket_id'); $admin_note = $request->input('admin_note'); $admin_email = $request->input('admin_email'); $action = $request->input('action'); // 'confirm' or 'refuse' $admin = Admin::where('email', $admin_email)->first(); // Get admin by email not token $ticket = Ticket::find($ticket_id); if (!$ticket || $ticket->status !== "WAITING") { // No ticket found or already confirmed or refused return redirect()->to(config('app.client_url') . '/tickets-management'); } // Send notification email to users $startDate = $ticket->start_date; //Start day $startPeriod = $ticket->start_period; //The session begins $endDate = $ticket->end_date; //End date $endPeriod = $ticket->end_period; //Session ends $type = $ticket->type; $dataMasterStartPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $startPeriod); $dataMasterEndPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $endPeriod); $dataMasterType = CategoryController::getListMasterByCodeAndType("REASON", $type); $dataMasterTypeNotes = CategoryController::getListMasterByType("REASON_NOTES"); $onleave = null; $leaveWithoutPay = null; if ($dataMasterTypeNotes) { // get nghỉ phép, nghỉ không phép $onleave = optional($dataMasterTypeNotes->where('c_code', 'ONLEAVE')->first())->c_code; $leaveWithoutPay = optional($dataMasterTypeNotes->where('c_code', 'LEAVE_WITHOUT_PAY')->first())->c_code; } $formattedStartDate = Carbon::createFromFormat('Y-m-d', $startDate)->format('d/m/Y'); $formattedEndDate = Carbon::createFromFormat('Y-m-d', $endDate)->format('d/m/Y'); $user = Admin::find($ticket->user_id); if ($onleave == null || $leaveWithoutPay == null) { // Data reason notes not found return redirect()->to(config('app.client_url') . '/tickets-management'); } 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); if ($balanceCheckResult['success'] == false) { if ($balanceCheckResult['months_info']) { foreach ($balanceCheckResult['months_info'] as $monthInfo) { // Lọc các ngày thuộc đúng tháng/năm này $daysInMonth = array_filter($dataListPeriod, function ($item) use ($monthInfo) { $date = \Carbon\Carbon::parse($item['date']); return $date->year == $monthInfo['year'] && $date->month == $monthInfo['month']; }); $daysWillUse = $monthInfo['days_will_use'] ?? 0; $daysWillUseWithoutPay = $monthInfo['days_will_use_without_pay'] ?? 0; // dd($daysWillUse,$daysWillUseWithoutPay,$daysInMonth); foreach ($daysInMonth as $item) { list($year, $month, $day) = explode('-', $item['date']); $period = $item['period']; $value = ($period === 'ALL') ? 1.0 : 0.5; if ($period === 'ALL' && $daysWillUse == 0.5) { // Chỉ còn 0.5 phép, chia thành 2 bản ghi: 1 phép, 1 không phép // Ưu tiên phép cho buổi sáng (S), không phép cho buổi chiều (C) Notes::create([ 'n_user_id' => $ticket->user_id, 'n_day' => $day, 'n_month' => $month, 'n_year' => $year, 'n_time_type' => 'S', 'n_reason' => $onleave, 'n_note' => $ticket->reason ]); Notes::create([ 'n_user_id' => $ticket->user_id, 'n_day' => $day, 'n_month' => $month, 'n_year' => $year, 'n_time_type' => 'C', 'n_reason' => $leaveWithoutPay, 'n_note' => $ticket->reason ]); $daysWillUse = 0; $daysWillUseWithoutPay -= 0.5; } elseif ($daysWillUse > 0) { // Dùng ngày phép trước $use = min($daysWillUse, $value); Notes::create([ 'n_user_id' => $ticket->user_id, 'n_day' => $day, 'n_month' => $month, 'n_year' => $year, 'n_time_type' => $period, 'n_reason' => $onleave, 'n_note' => $ticket->reason ]); $daysWillUse -= $use; } elseif ($daysWillUseWithoutPay > 0) { // Hết phép, chuyển sang không phép $use = min($daysWillUseWithoutPay, $value); Notes::create([ 'n_user_id' => $ticket->user_id, 'n_day' => $day, 'n_month' => $month, 'n_year' => $year, 'n_time_type' => $period, 'n_reason' => $leaveWithoutPay, 'n_note' => $ticket->reason ]); $daysWillUseWithoutPay -= $use; } // Nếu cả hai đều hết thì thôi, không tạo nữa } } } } else { //Đủ phép 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' => $onleave, // có phép 'n_note' => $ticket->reason ]); } } $yearCheck = Carbon::parse($endDate)->year; // Check giá trị ld_day_total của bảng leave_days thuộc user id đó với giá trị của list item note trong bảng notes của user id đó $leaveDaysInfo = LeaveDays::where('ld_user_id', $ticket->user_id) ->where('ld_year', $yearCheck) ->first(); if ($leaveDaysInfo) { // Tính tổng số ngày nghỉ có phép đã sử dụng trong năm $totalUsedLeaveDays = Notes::join('categories', function ($join) { $join->on('notes.n_time_type', '=', 'categories.c_code') ->where('categories.c_type', 'TIME_TYPE'); }) ->where('n_user_id', $ticket->user_id) ->where('n_year', $yearCheck) ->where('n_reason', 'ONLEAVE') ->sum('categories.c_value'); // Tính tổng số ngày phép được cấp $totalAllocatedDays = $leaveDaysInfo->ld_day_total + $leaveDaysInfo->ld_additional_day + $leaveDaysInfo->ld_special_leave_day; // Tính số ngày vượt quá và làm tròn lên $excessDays = $totalUsedLeaveDays - $totalAllocatedDays; $roundedExcessDays = ceil($excessDays); // Làm tròn lên số nguyên gần nhất // Kiểm tra nếu số ngày đã sử dụng vượt quá số ngày được cấp if ($roundedExcessDays > 0) { 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(); 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 ]); //WFH - start tracking $type = $result['period']; $date = Carbon::create($year, $month, $day)->setTimezone(env('TIME_ZONE')); //Default: ALL $start = $date->copy()->setTime(7, 30, 0); $end = $date->copy()->setTime(17, 0, 0); if ($type == 'S') { $end = $date->copy()->setTime(11, 30, 0); } else if ($type == 'C') { $start = $date->copy()->setTime(11, 30, 0); } Tracking::insert([ [ 'name' => $user->name, 'user_id' => $user->id, 'status' => 'check in', 'time_string' => $start->format('Y-m-d H:i:s'), 'created_at' => $start->setTimezone('UTC') ], [ 'name' => $user->name, 'user_id' => $user->id, 'status' => 'check out', 'time_string' => $end->format('Y-m-d H:i:s'), 'created_at' => $end->setTimezone('UTC') ] ]); //WFH - end tracking } } $ticket['updated_by'] = $admin->name; $ticket['admin_note'] = $admin_note; $ticket['status'] = 'CONFIRMED'; $ticket->save(); $this->createOrUpdateRecordForCurrentMonth($month, $year); // Send notification email to users $data = array( "email_template" => "email.notification_tickets_user", "user_name" => $user->name, "email" => $user->email, "name" => $admin->name, //name admin duyệt "date" => $dataMasterStartPeriod->c_name . " (" . $formattedStartDate . ") - " . $dataMasterEndPeriod->c_name . " (" . $formattedEndDate . ")", "type" => $dataMasterType->c_name, "note" => $ticket->reason, "admin_note" => $admin_note, "link" => "/tickets", //link đến page admin "status" => "confirmed", "subject" => "[Ticket response] Ticket From " . $admin->name ); Mail::to($user->email)->send(new TicketMail($data)); // Confirm Success return redirect()->to(config('app.client_url') . '/tickets-management'); } if ($action == "refuse") { $ticket['updated_by'] = $admin->name; $ticket['admin_note'] = $admin_note; $ticket['status'] = 'REFUSED'; $ticket->save(); $data = array( "email_template" => "email.notification_tickets_user", "user_name" => $user->name, "email" => $user->email, "name" => $admin->name, //name admin duyệt "date" => $dataMasterStartPeriod->c_name . " (" . $formattedStartDate . ") - " . $dataMasterEndPeriod->c_name . " (" . $formattedEndDate . ")", "type" => $dataMasterType->c_name, "note" => $ticket->reason, "admin_note" => $admin_note, "link" => "/tickets", //link đến page admin "status" => "refused", "subject" => "[Ticket response] Ticket From " . $admin->name ); Mail::to($user->email)->send(new TicketMail($data)); // Refuse Success return redirect()->to(config('app.client_url') . '/tickets-management'); } // Failed return redirect()->to(config('app.client_url') . '/tickets-management'); } private function getAllPeriodNew($startDate, $startPeriod, $endDate, $endPeriod) { // Đảm bảo $startDate và $endDate là đối tượng Carbon if (!($startDate instanceof Carbon)) { $startDate = Carbon::parse($startDate); } if (!($endDate instanceof Carbon)) { $endDate = Carbon::parse($endDate); } // Create an array to contain the results $results = []; // Use CarbonPeriod to create a period from the start date to the end date $period = CarbonPeriod::create($startDate, $endDate); $time_type = Category::where('c_type', 'TIME_TYPE')->get()->keyBy('c_code'); $morning = $time_type->get('S'); $afternoon = $time_type->get('C'); $all_day = $time_type->get('ALL'); // Get all Saturday work schedules and sort them by date in descending order $saturday_work_schedules = Category::where('c_type', 'SATURDAY_WORK_SCHEDULE') ->get() ->sortByDesc(function ($item) { // Parse the date string from c_code to a Carbon instance for proper comparison return Carbon::createFromFormat('d-m-Y', $item->c_code); }); // get day work special $day_work_special = Category::where('c_type', 'DAY_WORK_SPECIAL') ->get() ->sortByDesc(function ($item) { return Carbon::createFromFormat('d-m-Y', $item->c_code); }); // Get the most recent schedule date (first item after sorting) $latest_schedule = $saturday_work_schedules->first(); $latestScheduleDate = Carbon::createFromFormat('d-m-Y', $latest_schedule->c_code); if (!$morning || !$afternoon || !$all_day) { // Handle error: TIME_TYPE categories not found Log::error("TIME_TYPE categories (S, C, ALL) not found in database."); return []; // Return empty or throw exception } $special_dates = []; foreach ($day_work_special as $item) { $special_dates[] = Carbon::createFromFormat('d-m-Y', $item->c_code)->toDateString(); } foreach ($period as $date) { // Check phải ngày thứ 7 đặc biệt thì tính như ngày bình thường if (in_array($date->toDateString(), $special_dates)) { } else { // Check if the current day is a Saturday if ($date->dayOfWeek === Carbon::SATURDAY) { if ($latest_schedule) { $weeksDifference = $latestScheduleDate->startOfDay()->diffInWeeks($date->copy()->startOfDay()); $isSaturdayWorkDay = ($weeksDifference % 2 === 0); // echo $date->toDateString() . ' - ' . ($isSaturdayWorkDay ? 'Làm việc' : 'Nghỉ') . "
"; } if ($isSaturdayWorkDay) { $results[] = ['date' => $date->toDateString(), 'period' => "S"]; } continue; } // Skip Sundays entirely else if ($date->dayOfWeek === Carbon::SUNDAY) { continue; } } if ($date->isSameDay($startDate)) { //If the start date is morning, add afternoon if ($startDate->isSameDay($endDate)) { // Nghỉ trong cùng 1 ngày if ($startPeriod == $endPeriod) { // Cùng 1 buổi (S hoặc C) $results[] = ['date' => $date->toDateString(), 'period' => $startPeriod]; } else { // Khác buổi (S đến C) -> cả ngày $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code]; } } else { // Ngày bắt đầu khác ngày kết thúc if ($startPeriod == $morning->c_code) { // Bắt đầu từ sáng -> tính cả ngày $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code]; } else { // Bắt đầu từ chiều -> tính buổi chiều $results[] = ['date' => $date->toDateString(), 'period' => $startPeriod]; // Là $afternoon->c_code } } } elseif ($date->isSameDay($endDate)) { // Ngày kết thúc (khác ngày bắt đầu) if ($endPeriod == $afternoon->c_code) { // Kết thúc vào buổi chiều -> tính cả ngày $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code]; } else { // Kết thúc vào buổi sáng -> tính buổi sáng $results[] = ['date' => $date->toDateString(), 'period' => $endPeriod]; // Là $morning->c_code } } else { // Những ngày ở giữa $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code]; } } // Returns results return $results; } private function getAllPeriod($startDate, $startPeriod, $endDate, $endPeriod) { //Create an array to contain the results $results = []; //Use CarbonPeriod to create a period from the start date to the end date $period = CarbonPeriod::create($startDate, $endDate); $time_type = Category::where('c_type', 'TIME_TYPE')->get(); $morning = null; $afternoon = null; $all_day = null; foreach ($time_type as $item) { switch ($item->c_code) { case 'S': $morning = $item; break; case 'C': $afternoon = $item; break; case 'ALL': $all_day = $item; break; } } foreach ($period as $date) { //If it is the start date if ($date->isSameDay($startDate)) { //Add start session //If the start date is morning, add afternoon if ($startDate == $endDate) { if ($startPeriod == $endPeriod) { $results[] = ['date' => $date->toDateString(), 'period' => $startPeriod]; } else { $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code]; } } else { if ($startPeriod == $morning->c_code) { $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code]; } else { $results[] = ['date' => $date->toDateString(), 'period' => $startPeriod]; } } } elseif ($date->isSameDay($endDate)) { //If it is the end date //If the end session is afternoon, add morning first if ($endPeriod == $afternoon->c_code) { $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code]; } else { $results[] = ['date' => $date->toDateString(), 'period' => $endPeriod]; } } else { //Add both sessions for days between intervals $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code]; // $results[] = ['date' => $date->toDateString(), 'period' => 'afternoon']; } } //Returns results return $results; } /** * Tính tổng số ngày nghỉ từ mảng các khoảng thời gian. * 'ALL' = 1 ngày, 'S'/'C' = 0.5 ngày. * * @param array $dataListPeriod Mảng các khoảng thời gian nghỉ [['date' => 'Y-m-d', 'period' => 'ALL|S|C'], ...] * @return float Tổng số ngày nghỉ */ private function calculateTotalLeaveDays(array $dataListPeriod): float { $totalDays = 0.0; foreach ($dataListPeriod as $periodData) { if (isset($periodData['period'])) { switch ($periodData['period']) { case 'ALL': $totalDays += 1.0; break; case 'S': // Buổi sáng case 'C': // Buổi chiều $totalDays += 0.5; break; // Có thể thêm default case để xử lý lỗi nếu cần } } } return $totalDays; } }