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) { // Validate input $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', ]; $request->validate($rules); // Get input data $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(); $start_date = Carbon::create($startDate)->setTimezone(env('TIME_ZONE')); $end_date = Carbon::create($endDate)->setTimezone(env('TIME_ZONE')); $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 các ticket của user trong thời gian request $userTickets = Ticket::where('user_id', $user->id) ->whereIn('status', ['WAITING', 'CONFIRMED']) ->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(); $userTicketListPeriod = []; if ($userTickets->count() > 0) { foreach ($userTickets as $ticket) { $userTicketListPeriod = array_merge($userTicketListPeriod, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period)); } } // Kiểm tra ticket tạo có trùng thời gian với các ticket cũ $periodStrings = []; $userTicketPeriodStrings = []; foreach ($dataListPeriod as $period) { if ($period['period'] == 'ALL') { $periodStrings[] = $period['date'] . '_S'; $periodStrings[] = $period['date'] . '_C'; continue; } $periodStrings[] = $period['date'] . '_' . $period['period']; } foreach ($userTicketListPeriod as $period) { if ($period['period'] == 'ALL') { $userTicketPeriodStrings[] = $period['date'] . '_S'; $userTicketPeriodStrings[] = $period['date'] . '_C'; continue; } $userTicketPeriodStrings[] = $period['date'] . '_' . $period['period']; } if (count(array_intersect($periodStrings, $userTicketPeriodStrings)) > 0) { return AbstractController::ResultError('Đã có ticket được tạo trong thời gian này, không thể tạo ticket mới!'); } // Kiểm tra khi type = ONLEAVE (nghỉ phép) if ($type === 'ONLEAVE' && !$isAccept) { // Lấy 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)); } } // 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à chưa 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 --- // Tạo ticket mới khi: // - Ticket được tạo không trùng ngày // - User có đủ phép // - User không đủ phép nhưng vẫn đồng ý tạo $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)->queue(new TicketMail($data)); } else { Log::error("Missing category data for ticket ID: {$ticket->id}. Mail not sent."); } } return response()->json(['data' => $ticket, 'status' => true]); } public function updateTicket(Request $request) { // Validate input $rules = [ 'ticket_id' => 'required|exists:tickets,id', 'status' => 'required|string|in:CONFIRMED,REFUSED' ]; $request->validate($rules); $ticket = Ticket::find($request->input('ticket_id')); if (!$ticket) { return AbstractController::ResultError("Ticket not found."); } $admin = auth('admins')->user(); // Delete related note, if status change to Refuse if ($request->status == "REFUSED") { $ticket->status = "REFUSED"; Notes::where('ticket_id', $ticket->id)->delete(); } $ticket->updated_by = $admin->name; $ticket->admin_note = $request->admin_note; // Clear Timekeeping cache $this->createOrUpdateRecordForCurrentMonth(Carbon::parse($ticket->start_date)->month, Carbon::parse($ticket->start_date)->year); $this->createOrUpdateRecordForCurrentMonth(Carbon::parse($ticket->end_date)->month, Carbon::parse($ticket->end_date)->year); $ticket->save(); return AbstractController::ResultSuccess($ticket, "Ticket updated successfully!"); } /** * 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, ?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, $isAccept); } // Đủ đ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, ?bool $isAccept = false): 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(); $monthIndex = 0; $onleaveTmp = 0; // Ngày phép trừ tạm (tính phép cho nhiều tháng) foreach ($requestMonths as $monthData) { if ($monthsInfoWaiting) { foreach ($monthsInfoWaiting as $monthInfo) { if ($monthInfo['month'] == $monthData['month'] && $monthInfo['year'] == $monthData['year']) { $remainingDaysInMonthIsUsed += $monthInfo['remaining_days_in_month_remaining']; } } } // Số ngày nghỉ trong tháng $onleaveDaysInMonth = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'ONLEAVE'); // Có phép $nopayDaysInMonth = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'LEAVE_WITHOUT_PAY'); // Không phép $usedDaysInMonth = $onleaveDaysInMonth + $nopayDaysInMonth; // Tổng // Tổng ngày nghỉ sẽ dùng trong tháng $willUsedDaysInMonth = $usedDaysInMonth + $monthData['days_requested']; // Ngày phép $onleaveDaysTotal = $this->getTotalLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], $isAccept); // Tổng phép của user $usedOnleaveDaysTotal = $this->getTotalLeaveDaysInMonthToMonth($user, $monthData['year'], $monthData['month']); // Phép đã dùng $remainingOnleaveDays = $onleaveDaysTotal - $usedOnleaveDaysTotal - $onleaveTmp; // Phép còn lại // Tổng ngày phép còn lại trong tháng $remainingOnleaveDaysInMonth = $remainingOnleaveDays - $remainingDaysInMonthIsUsed; // Log::debug( // "📊 Thống kê ngày phép:\n" . // " - Tháng: {$monthData['month']}\n" . // " - Tổng ngày nghỉ có phép trong tháng: $onleaveDaysInMonth\n" . // " - Tổng ngày nghỉ không phép trong tháng: $nopayDaysInMonth\n" . // " - Tổng ngày nghỉ đã dùng trong tháng: $usedDaysInMonth\n" . // " - Tổng ngày nghỉ sẽ dùng trong tháng: $willUsedDaysInMonth\n" . // " - Tổng ngày phép: $onleaveDaysTotal\n" . // " - Tổng ngày phép đã nghỉ: $usedOnleaveDaysTotal\n" . // " - Tổng ngày phép còn lại: $remainingOnleaveDays\n" . // " - Tổng ngày phép còn lại trong tháng: $remainingOnleaveDaysInMonth\n" // ); $month_data_status = 'ok'; $onleave_days_will_use = 0; // Ngày phép sẽ dùng trong tháng $nopay_days_will_use = 0; // Ngày ko phép sẽ dùng trong tháng // Ngày phép còn lại <= 0 (Hết phép) hoặc là nhân viên chưa chính thức if ($remainingOnleaveDaysInMonth <= 0 || !$user->is_permanent) { $hasInsufficientDays = true; $month_data_status = 'no_days_left'; $onleave_days_will_use = 0; $nopay_days_will_use = $monthData['days_requested']; // Message cảnh báo nghỉ ko phép $monthMessage = $this->buildMonthlyLeaveMessage( $monthIndex, $maxDaysPerMonth, $monthData, $remainingOnleaveDaysInMonth, $onleaveDaysInMonth, $nopayDaysInMonth, $onleave_days_will_use, $nopay_days_will_use ); $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; Log::debug("--- Hết phép trong tháng ---", [ "Phep" => $onleave_days_will_use, "Khong Phep" => $nopay_days_will_use ]); } // Ngày phép còn lại < ngày yêu cầu (Không đủ phép) else if ($remainingOnleaveDaysInMonth < $monthData['days_requested']) { // Vượt limit if ($willUsedDaysInMonth > $maxDaysPerMonth) { $hasInsufficientDays = true; $month_data_status = 'exceed_max_days'; // Phép còn lại > limit if ($remainingOnleaveDaysInMonth > $maxDaysPerMonth) { $onleave_days_will_use = $maxDaysPerMonth - $onleaveDaysInMonth; $nopay_days_will_use = $monthData['days_requested'] - $maxDaysPerMonth + $onleaveDaysInMonth; } // Phép còn lại < limit else { $onleave_days_will_use = $remainingOnleaveDaysInMonth; $nopay_days_will_use = $monthData['days_requested'] - $remainingOnleaveDaysInMonth; } Log::debug("--- Không đủ phép trong tháng, vượt quá limit ---", [ "Phep" => $onleave_days_will_use, "Khong Phep" => $nopay_days_will_use ]); } // Không vượt limit else { $hasInsufficientDays = true; $month_data_status = 'insufficient_days'; $onleave_days_will_use = $remainingOnleaveDaysInMonth; $nopay_days_will_use = $monthData['days_requested'] - $remainingOnleaveDaysInMonth; Log::debug("--- Không đủ phép trong tháng, ko vượt limit ---", [ "Phep" => $onleave_days_will_use, "Khong Phep" => $nopay_days_will_use ]); } // Message cảnh báo nghỉ ko phép $monthMessage = $this->buildMonthlyLeaveMessage( $monthIndex, $maxDaysPerMonth, $monthData, $remainingOnleaveDaysInMonth, $onleaveDaysInMonth, $nopayDaysInMonth, $onleave_days_will_use, $nopay_days_will_use ); $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; } // Ngày phép còn lại >= ngày yêu cầu (Đủ phép) else { // Vượt limit if ($willUsedDaysInMonth > $maxDaysPerMonth) { $hasInsufficientDays = true; $month_data_status = 'exceed_max_days'; $onleave_days_will_use = $maxDaysPerMonth - $onleaveDaysInMonth; $nopay_days_will_use = $willUsedDaysInMonth - $maxDaysPerMonth - $nopayDaysInMonth; Log::debug("--- Đủ phép, vượt limit ---", [ "Phep" => $onleave_days_will_use, "Khong Phep" => $nopay_days_will_use ]); } // Waiting ticket else if ($monthData['days_requested'] + $remainingDaysInMonthIsUsed > $maxDaysPerMonth) { if ($remainingDaysInMonthIsUsed > 0) { $onleave_days_will_use = $maxDaysPerMonth - $remainingDaysInMonthIsUsed; $nopay_days_will_use = $monthData['days_requested'] - $onleave_days_will_use; } else { $onleave_days_will_use = $maxDaysPerMonth; $nopay_days_will_use = $monthData['days_requested'] - $maxDaysPerMonth; } $hasInsufficientDays = true; $month_data_status = 'exceed_max_days'; Log::debug("--- Đủ phép, Waiting ticket ---", [ "Phep" => $onleave_days_will_use, "Khong Phep" => $nopay_days_will_use ]); } // Đủ phép else { $onleave_days_will_use = $monthData['days_requested']; $nopay_days_will_use = 0; Log::debug("--- Đủ phép ---", [ "Phep" => $onleave_days_will_use, "Khong Phep" => $nopay_days_will_use ]); } // Message cảnh báo nghỉ ko phép $monthMessage = $this->buildMonthlyLeaveMessage( $monthIndex, $maxDaysPerMonth, $monthData, $remainingOnleaveDaysInMonth, $onleaveDaysInMonth, $nopayDaysInMonth, $onleave_days_will_use, $nopay_days_will_use ); $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; $remainingOnleaveDaysInMonth = $monthData['days_requested']; } $month_data = [ 'year' => $monthData['year'], 'month' => $monthData['month'], 'total_leave_days_in_month' => $onleaveDaysTotal, // tổng số ngày phép 'total_leave_days_in_month_to_month' => $usedOnleaveDaysTotal, // tổng ngày nghỉ có phép đã nghỉ 'remaining_days_in_month' => $remainingOnleaveDays, // số ngày phép còn lại 'days_used' => $onleaveDaysInMonth, // tổng số ngày nghỉ có phép đã nghỉ ở tháng hiện tại 'days_used_without_pay' => $nopayDaysInMonth, // 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' => $remainingOnleaveDaysInMonth, 'days_will_use' => $onleave_days_will_use, //Số ngày phép sẽ sử dụng 'days_will_use_without_pay' => $nopay_days_will_use, //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; $monthIndex++; $onleaveTmp += $onleave_days_will_use; // Cộng ngày phép dùng tạm trong tháng } // 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, ?bool $isAccept = false): float { $leaveDaysInfo = LeaveDays::where('ld_user_id', $user->id) ->where('ld_year', $year) ->first(); $totalAllocated = 0; // Xử lý gửi ticket sau tháng hiện tại if ($leaveDaysInfo && $user->is_permanent) { $currentMonth = Carbon::now()->month; $totalAllocated = $leaveDaysInfo->ld_day_total; // Check có phải là nhân viên chính thức trong năm nay $isProbationInYear = false; if ($user->permanent_date && $user->permanent_date !== '0000-00-00') { $permenantYear = Carbon::parse($user->permanent_date)->year; if ($permenantYear === $year) { $isProbationInYear = true; } } // Nhân viên mới if ($isProbationInYear) { $permanentMonth = Carbon::parse($user->permanent_date)->month; if ($month > $currentMonth) { $permanentCategory = Category::where('c_type', 'PERMANENT_ONLEAVE')->where('c_code', "PERMANENT")->first(); $permanentDefault = (int) $permanentCategory->c_value; // Ngày phép khi thành nv chính thức $totalAllocated = $month - ($permanentMonth - $permanentDefault); } } // Nhân viên cũ else { if ($month > $currentMonth) { $totalAllocated = $month; } } } $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', ]; // 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]); } $dataMasterTypeNotes = CategoryController::getListMasterByType("REASON_NOTES"); $onleave = null; $leaveWithoutPay = null; $temporaryOnleave = 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; $temporaryOnleave = optional($dataMasterTypeNotes->where('c_code', 'TEMPORARY_ONLEAVE')->first())->c_code; } if ($onleave == null || $leaveWithoutPay == null || $temporaryOnleave == null) { return response()->json(['message' => "Data reason notes not found", 'status' => false]); } if ($action == "confirm") { $this->handleConfirmTicket($ticket, $admin, $admin_note, $onleave, $leaveWithoutPay, $temporaryOnleave); return response()->json(['message' => "confirmed", 'status' => true]); } if ($action == "refuse") { $this->handleRefuseTicket($ticket, $admin, $admin_note); return response()->json(['message' => "refused", 'status' => true]); } return response()->json(['message' => "failed", 'status' => false]); } 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') . '/404'); } $dataMasterTypeNotes = CategoryController::getListMasterByType("REASON_NOTES"); $onleave = null; $leaveWithoutPay = null; $temporaryOnleave = 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; $temporaryOnleave = optional($dataMasterTypeNotes->where('c_code', 'TEMPORARY_ONLEAVE')->first())->c_code; } // Không tìm được ngày phép, ko phép if ($onleave == null || $leaveWithoutPay == null || $temporaryOnleave == null) { return redirect()->to(config('app.client_url') . '/404'); } if ($action == "confirm") { $this->handleConfirmTicket($ticket, $admin, $admin_note, $onleave, $leaveWithoutPay, $temporaryOnleave); return redirect()->to(config('app.client_url') . '/tickets-management'); } if ($action == "refuse") { $this->handleRefuseTicket($ticket, $admin, $admin_note); return redirect()->to(config('app.client_url') . '/tickets-management'); } // Failed return redirect()->to(config('app.client_url') . '/tickets-management'); } private function handleConfirmTicket($ticket, $admin, $admin_note, $onleave, $leaveWithoutPay, $temporaryOnleave) { $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 ($ticket->type == "ONLEAVE") { $dataListPeriod = $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period); $balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod, null, true); // dd($balanceCheckResult, $dataListPeriod); $currentMonth = Carbon::now()->month; 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' => $month > $currentMonth ? $temporaryOnleave : $onleave, 'n_note' => $ticket->reason, 'ticket_id' => $ticket->id ]); Notes::create([ 'n_user_id' => $ticket->user_id, 'n_day' => $day, 'n_month' => $month, 'n_year' => $year, 'n_time_type' => 'C', 'n_reason' => $month > $currentMonth ? $temporaryOnleave : $leaveWithoutPay, 'n_note' => $ticket->reason, 'ticket_id' => $ticket->id ]); $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' => $month > $currentMonth ? $temporaryOnleave : $onleave, 'n_note' => $ticket->reason, 'ticket_id' => $ticket->id ]); $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' => $month > $currentMonth ? $temporaryOnleave : $leaveWithoutPay, 'n_note' => $ticket->reason, 'ticket_id' => $ticket->id ]); $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' => $month > $currentMonth ? $temporaryOnleave : $onleave, 'n_note' => $ticket->reason, 'ticket_id' => $ticket->id ]); } } // $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 // $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, 'ticket_id' => $ticket->id ]); //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(Carbon::parse($ticket->start_date)->month, Carbon::parse($ticket->start_date)->year); $this->createOrUpdateRecordForCurrentMonth(Carbon::parse($ticket->end_date)->month, Carbon::parse($ticket->end_date)->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)); } private function handleRefuseTicket($ticket, $admin, $admin_note) { $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); $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)); } 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; } private function buildMonthlyLeaveMessage( $index, $max, $monthData, $totalLeave, $usedLeave, $usedNoPay, $willUseLeave, $willUseNoPay ): string { $message = ""; if ($index === 0) { $showMonth = $monthData['month'] > Carbon::now()->month ? $monthData['month'] : Carbon::now()->month; $message .= "* Quy định: mỗi tháng được nghỉ tối đa {$max} ngày phép\n"; $message .= "- Bạn có: {$totalLeave} ngày phép (Tính tới tháng {$showMonth})\n\n"; } // Hiển thị cộng phép nếu gửi ticket trong tương lai $monthValue = $monthData['year'] . '-' . sprintf('%02d', $monthData['month']); $currentMonth = date('Y-m'); if ($monthValue > $currentMonth && $index !== 0) { $message .= "* Bạn được cộng 1 phép\n"; } // In mỗi tháng $message .= "Tháng {$monthData['month']}/{$monthData['year']}:\n"; if ($usedLeave > 0 || $usedNoPay > 0) { $message .= " - Bạn đã sử dụng: "; $usedParts = []; if ($usedLeave > 0) $usedParts[] = "{$usedLeave} phép"; if ($usedNoPay > 0) $usedParts[] = "{$usedNoPay} không phép"; $message .= implode(', ', $usedParts) . "\n"; } if ($willUseLeave > 0 || $willUseNoPay > 0) { $message .= " - Dự kiến bạn sẽ sử dụng: "; $usedParts = []; if ($willUseLeave > 0) $usedParts[] = "{$willUseLeave} phép"; if ($willUseNoPay > 0) $usedParts[] = "{$willUseNoPay} không phép"; $message .= implode(', ', $usedParts); } return $message; } /** * 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; } public function updateOldData(int $month, int $year) { LeaveDays::where('ld_year', $year) ->update(['ld_day_total' => $month]); $users = Admin::all(); foreach ($users as $user) { $leaveDay = LeaveDays::where('ld_user_id', $user->id) ->where('ld_year', $year) ->first(); $notes = Notes::where('n_user_id', $user->id) ->where('n_year', $year) ->where('n_reason', 'ONLEAVE') ->orderBy('n_month') ->orderBy('n_day') ->get() ->groupBy('n_month'); $onleaveDaysTotal = $leaveDay->ld_additional_day; $previousYearData = LeaveDays::where('ld_user_id', $user->id) ->where('ld_year', $year - 1) ->first(); $ld_additional_day = 0; $ld_note = ''; if ($previousYearData) { $ld_additional_day = $previousYearData->ld_day_total + $previousYearData->ld_additional_day; $totalLeaveDaysByMonth = Notes::join('categories', function ($join) { $join->on('notes.n_time_type', '=', 'categories.c_code') ->where('categories.c_type', 'TIME_TYPE'); }) ->select( DB::raw('notes.n_user_id as n_user_id'), DB::raw('notes.n_year as year'), DB::raw('SUM(categories.c_value) as leave_days') ) ->where('notes.n_year', $year - 1) ->where('notes.n_user_id', $user->id) ->where('notes.n_reason', 'ONLEAVE') ->groupBy(DB::raw('notes.n_year')) ->first(); if ($totalLeaveDaysByMonth) { $ld_additional_day = $ld_additional_day - $totalLeaveDaysByMonth->leave_days; if ($ld_additional_day < 0) { $ld_additional_day = 0; } } if ($ld_additional_day > 0) { $ld_note = "Cộng " . $ld_additional_day . " ngày phép tồn năm trước. \n"; } } for ($i = 1; $i <= $month; $i++) { // Giả lập cộng phép $onleaveDaysTotal++; // $tmpOnleaveDaysTotal = $onleaveDaysTotal; $onleaveDaysInMonth = 0; $nopayDaysInMonth = 0; if ($notes->has($i)) { foreach ($notes[$i] as $note) { $onleaveDaysInMonth += $note->n_time_type == 'ALL' ? 1.0 : 0.5; } if ($onleaveDaysInMonth > $onleaveDaysTotal) { $nopayDaysInMonth = $onleaveDaysInMonth - $onleaveDaysTotal; $onleaveDaysTotal = 0; } else { $onleaveDaysTotal -= $onleaveDaysInMonth; } // Xử lý cập nhật lại các note có phép thành không phép if ($nopayDaysInMonth > 0) { $revertNotes = $notes->get($i, collect())->reverse(); $nopayDaysUpdated = 0; foreach ($revertNotes as $note) { if ($note->n_time_type == 'ALL') { if ($nopayDaysInMonth - $nopayDaysUpdated == 0.5) { Notes::create([ 'n_user_id' => $user->id, 'n_day' => $note->n_day, 'n_month' => $note->n_month, 'n_year' => $note->n_year, 'n_time_type' => 'S', 'n_reason' => 'ONLEAVE', 'n_note' => $note->n_note, 'ticket_id' => $note->ticket_id ]); Notes::create([ 'n_user_id' => $user->id, 'n_day' => $note->n_day, 'n_month' => $note->n_month, 'n_year' => $note->n_year, 'n_time_type' => 'C', 'n_reason' => 'LEAVE_WITHOUT_PAY', 'n_note' => $note->n_note, 'ticket_id' => $note->ticket_id ]); $note->delete(); break; } $nopayDaysUpdated += 1.0; $note->update([ 'n_reason' => "LEAVE_WITHOUT_PAY" ]); } else { $nopayDaysUpdated += 0.5; $note->update([ 'n_reason' => "LEAVE_WITHOUT_PAY" ]); } if ($nopayDaysUpdated >= $nopayDaysInMonth) { break; } } } } // Log thông kê sau mỗi tháng // Log::debug( // "📊 Thống kê ngày phép Tháng {$i}:\n" . // " - Tổng phép đầu tháng: $tmpOnleaveDaysTotal\n" . // " - Có phép: $onleaveDaysInMonth\n" . // " - Không phép: $nopayDaysInMonth\n" . // " - Tổng phép cuối tháng: $onleaveDaysTotal\n" // ); } $leaveDay->ld_note = $ld_note; $leaveDay->save(); } } }