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')); $currentYear = $start_date->year; // --- Chỉ kiểm tra ngày phép khi loại là ONLEAVE --- if ($type === 'ONLEAVE' && !$isAccept) { // Get mảng ngày nghỉ và tính tổng số ngày yêu cầu $dataListPeriod = $this->getAllPeriodNew($start_date, $startPeriod, $end_date, $endPeriod); if (empty($dataListPeriod)) { return response()->json(['message' => 'Không thể tính toán khoảng thời gian nghỉ hợp lệ.', 'status' => false]); } // Kiểm tra số dư ngày phép $balanceCheckResult = $this->checkLeaveBalance($user, $currentYear, $dataListPeriod); // Nếu không đủ ngày phép, trả về thông báo và không tạo ticket if (!$balanceCheckResult['success']) { return response()->json($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( "email_template" => "email.notification_tickets", "email" => $user->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 int $year Năm kiểm tra * @param float $daysRequested Số ngày yêu cầu nghỉ * @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, int $year, ?array $dataListPeriod = null): array { // Tính tổng số ngày yêu cầu $daysRequested = $this->calculateTotalLeaveDays($dataListPeriod); // 1. Tính tổng ngày phép được cấp $totalAllocated = $this->getTotalAllocatedDays($user, $year); // 2. Tính số ngày đã nghỉ có phép trong năm $usedDays = $this->getUsedLeaveDays($user, $year); // 3. Tính số ngày còn lại $remainingDays = $totalAllocated - $usedDays; // 4. Kiểm tra giới hạn nghỉ phép theo tháng $monthsInfo = []; if (!empty($dataListPeriod)) { $monthlyCheckResult = $this->checkMonthlyLeaveLimit($user, $dataListPeriod); if (!$monthlyCheckResult['success']) { return $monthlyCheckResult; } $monthsInfo = $monthlyCheckResult['months_info']; if (!empty($monthsInfo)) { //Danh sách ngày nghỉ trong tháng dựa trên list ngày xin nghỉ } } // 5. Kiểm tra đủ ngày phép không if ($remainingDays < $daysRequested) { return $this->insufficientLeaveDaysResponse($user, $remainingDays, $daysRequested); } // 6. Kiểm tra giới hạn ngày liên tục if ($daysRequested > 3) { return $this->exceedMaxConsecutiveDaysResponse($user, $daysRequested); } // Đủ điều kiện return [ 'success' => true, 'message' => null, 'remaining_days' => $remainingDays, 'months_info' => $monthsInfo ]; } 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 { // Danh sách ngày nghỉ theo tháng $requestMonths = $this->groupLeaveRequestsByMonth($dataListPeriod); $monthsInfo = []; $hasInsufficientDays = false; $errorMessage = ''; $remainingDaysInMonthIsUsed = 0; foreach ($requestMonths as $monthKey => $monthData) { // Tính tổng số ngày nghỉ có phép trong tháng $usedDaysInMonth = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'ONLEAVE'); // Tính 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ính tổng giới hạn ngày nghỉ có phép tối đa trong tháng $maxDaysPerMonth = $this->getMaxLeaveDaysPerMonth(); // Tính tổng số ngày nghỉ trong tháng $totalDaysInMonth = $usedDaysInMonth + $usedDaysInMonthWithoutPay + $monthData['days_requested']; // Tính 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; $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 'remaining_days_in_month' => $remainingDaysInMonth, //ngày phép còn lại 'days_used' => $usedDaysInMonth, //tổng số ngày nghỉ có phép ở tháng hiện tại 'days_used_without_pay' => $usedDaysInMonthWithoutPay, //tổng số ngày nghỉ không phép ở tháng hiện tại 'days_requested' => $monthData['days_requested'], //số ngày yêu cầu nghỉ của tháng 'status' => 'ok', // mặc định là ok ]; $remainingDaysInMonthRemaining = $remainingDaysInMonth - $remainingDaysInMonthIsUsed; // 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']}\nBạn sẽ nộp: " . $monthData['days_requested'] . " ngày không phép."; $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; } 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']}: Số ngày phép còn lại: {$remainingDaysInMonthRemaining}, Số ngày yêu cầu: {$monthData['days_requested']}.\nBạ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; } // 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 . "\n\nBạn có chấp nhận không?", 'warning_type' => 'exceed_monthly_limit', 'months_info' => $monthsInfo ]; } return [ 'success' => true, 'message' => "Đủ ngày phép cho tất cả tháng 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; } } 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'); } private function insufficientLeaveDaysResponse($user, float $remainingDays, float $daysRequested): array { $daysNotEnough = $daysRequested - $remainingDays; Log::warning("Insufficient leave balance for user ID: {$user->id}. Remaining: {$remainingDays}, Requested: {$daysRequested}"); return [ 'success' => false, 'message' => "Bạn không đủ ngày phép. Số ngày phép còn lại: {$remainingDays}, Số ngày yêu cầu: {$daysRequested}. Bạn có chấp nhận nộp: {$daysNotEnough} ngày không phép không?", 'warning_type' => 'insufficient_leave_days', 'month_data' => [] ]; } private function exceedMaxConsecutiveDaysResponse($user, float $daysRequested): array { $noLeavePermissionDays = $daysRequested - 3; $message = "Bạn đã yêu cầu {$daysRequested} ngày nghỉ. Theo quy định, chỉ 3 ngày đầu là nghỉ có phép, {$noLeavePermissionDays} ngày còn lại sẽ là nghỉ không phép. Bạn có chấp nhận tiếp tục không?"; Log::info("User ID: {$user->id} requested {$daysRequested} days which exceeds the 3-day limit. {$noLeavePermissionDays} days will be marked as leave without permission."); return [ 'success' => true, 'message' => $message, 'require_acceptance' => true, 'warning_type' => 'exceed_max_days', 'max_leave_days' => 3, 'days_with_permission' => 3, 'days_without_permission' => $noLeavePermissionDays ]; } 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]); } $results = $this->getAllPeriod($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period); // $admin->id != user_id of ticket ---> continue // Confirm // Add records to the notes table like function Timekeeping.addNoteForUser() based on the $results array // Update updated_by and admin_note in tickets table // Send notification email to users // Refuse // Update updated_by and admin_note in tickets table $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); $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 ($action == "confirm") { foreach ($results 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 ]); if ($ticket->type == "WFH") { $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') ] ]); } } $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]); } 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 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 } foreach ($period as $date) { // 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; } }