diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php index 7bde71d..df0e629 100644 --- a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php +++ b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php @@ -2,9 +2,7 @@ namespace Modules\Admin\app\Http\Controllers; -use App\Helper\Cache\CurrentMonthTimekeeping; use App\Http\Controllers\Controller; -use App\Mail\ContactMail; use App\Mail\TicketMail; use App\Models\Notes; use App\Traits\AnalyzeData; @@ -22,7 +20,6 @@ use Modules\Admin\app\Models\Ticket; use Modules\Admin\app\Models\Tracking; use Illuminate\Support\Facades\Log; use App\Models\LeaveDays; -use Illuminate\Http\JsonResponse; use App\Models\Admin as UserModel; class TicketController extends Controller @@ -194,7 +191,7 @@ class TicketController extends Controller public function createTicket(Request $request) { - // Define validation rules + // Validate input $rules = [ 'start_date' => 'required|date', 'start_period' => 'required|string', @@ -202,12 +199,9 @@ class TicketController extends Controller 'end_period' => 'required|string', 'type' => 'required|string', ]; - - // Validate the request $request->validate($rules); - // return $request; - // Get data from request + // Get input data $startDate = $request->input('start_date'); $startPeriod = $request->input('start_period'); $endDate = $request->input('end_date'); @@ -215,21 +209,72 @@ class TicketController extends Controller $type = $request->input('type'); $reason = $request->input('reason'); $isAccept = $request->input('is_accept') ?? false; - $user = auth('admins')->user(); // user create ticket + $user = auth('admins')->user(); $start_date = Carbon::create($startDate)->setTimezone(env('TIME_ZONE')); $end_date = Carbon::create($endDate)->setTimezone(env('TIME_ZONE')); - // Get mảng ngày nghỉ $dataListPeriod = $this->getAllPeriodNew($start_date, $startPeriod, $end_date, $endPeriod); if (empty($dataListPeriod)) { return AbstractController::ResultError('Không thể tính toán khoảng thời gian nghỉ hợp lệ.'); } - // --- Chỉ kiểm tra ngày phép khi loại là ONLEAVE --- + // 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 thông tin tickets nghỉ phép đang ở trạng thái WAITING - $ticketsWaiting = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->whereIn('type', ['ONLEAVE']) + // 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) { @@ -238,100 +283,6 @@ class TicketController extends Controller } } - $ticketsWaitingWFH = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->whereIn('type', ['WFH']) - ->get(); - $dataListPeriodWaitingWFH = []; - if ($ticketsWaitingWFH->count() > 0) { - foreach ($ticketsWaitingWFH as $ticket) { - $dataListPeriodWaitingWFH = array_merge($dataListPeriodWaitingWFH, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period)); - } - } - - // Lấy thông tin tickets nghỉ phép đang ở trạng thái CONFIRMED - $ticketsConfirmed = Ticket::where('user_id', $user->id) - ->where('status', 'CONFIRMED') - ->whereIn('type', ['ONLEAVE', 'WFH']) - ->where(function ($query) use ($start_date, $end_date) { - $query->where(function ($q) use ($start_date, $end_date) { - // Trường hợp 1: start_date nằm trong khoảng - $q->whereBetween(DB::raw('DATE(start_date)'), [$start_date->toDateString(), $end_date->toDateString()]); - }) - ->orWhere(function ($q) use ($start_date, $end_date) { - // Trường hợp 2: end_date nằm trong khoảng - $q->whereBetween(DB::raw('DATE(end_date)'), [$start_date->toDateString(), $end_date->toDateString()]); - }) - ->orWhere(function ($q) use ($start_date, $end_date) { - // Trường hợp 3: Khoảng thời gian được chọn nằm trong khoảng của ticket - $q->where(DB::raw('DATE(start_date)'), '<=', $start_date->toDateString()) - ->where(DB::raw('DATE(end_date)'), '>=', $end_date->toDateString()); - }); - }) - ->get(); - - $dataListPeriodConfirmed = []; - if ($ticketsConfirmed->count() > 0) { - foreach ($ticketsConfirmed as $ticket) { - $dataListPeriodConfirmed = array_merge($dataListPeriodConfirmed, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period)); - } - } - // dd($dataListPeriodConfirmed,$ticketsConfirmed,$start_date->toDateString(),$end_date->toDateString()); - // Chuyển đổi mảng đa chiều thành mảng chuỗi để có thể so sánh - $periodStrings = []; - $waitingPeriodStrings = []; - $waitingPeriodStringsWFH = []; - $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 ($dataListPeriodWaitingWFH as $period) { - if ($period['period'] == 'ALL') { - $waitingPeriodStringsWFH[] = $period['date'] . '_S'; - $waitingPeriodStringsWFH[] = $period['date'] . '_C'; - } else { - $waitingPeriodStringsWFH[] = $period['date'] . '_' . $period['period']; - } - } - - foreach ($dataListPeriodConfirmed as $period) { - if ($period['period'] == 'ALL') { - $confirmedPeriodStrings[] = $period['date'] . '_S'; - $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 đang chờ duyệt WFH - if (count(array_intersect($periodStrings, $waitingPeriodStringsWFH)) > 0) { - return AbstractController::ResultError('Đã có ticket đang chờ duyệt trong thời gian này, không thể tạo ticket mới!'); - } - - // Kiểm tra xem có sự trùng lặp giữa request mới và tickets đã được duyệt - if (count(array_intersect($periodStrings, $confirmedPeriodStrings)) > 0) { - return AbstractController::ResultError('Đã có ticket được duyệt trong thời gian này, không thể tạo ticket mới!'); - } - // Tạo thông báo về tickets waiting nếu có $waitingTicketsMessage = ''; if (!empty($dataListPeriodWaiting)) { @@ -365,7 +316,7 @@ class TicketController extends Controller $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 + // 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)) { @@ -377,89 +328,13 @@ class TicketController extends Controller return AbstractController::ResultError("Không thỏa mãn điều kiện ngày phép", $balanceCheckResult); } - } else if ($type === 'WFH') { - // Lấy thông tin tickets nghỉ phép đang ở trạng thái WAITING - $ticketsWaiting = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->whereIn('type', ['WFH', 'ONLEAVE']) - ->get(); - $dataListPeriodWaiting = []; - if ($ticketsWaiting->count() > 0) { - foreach ($ticketsWaiting as $ticket) { - $dataListPeriodWaiting = array_merge($dataListPeriodWaiting, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period)); - } - } - - // Lấy thông tin tickets nghỉ phép đang ở trạng thái CONFIRMED - $ticketsConfirmed = Ticket::where('user_id', $user->id) - ->where('status', 'CONFIRMED') - ->whereIn('type', ['ONLEAVE', 'WFH']) - ->where(function ($query) use ($start_date, $end_date) { - $query->where(function ($q) use ($start_date, $end_date) { - // Trường hợp 1: start_date nằm trong khoảng - $q->whereBetween(DB::raw('DATE(start_date)'), [$start_date->toDateString(), $end_date->toDateString()]); - }) - ->orWhere(function ($q) use ($start_date, $end_date) { - // Trường hợp 2: end_date nằm trong khoảng - $q->whereBetween(DB::raw('DATE(end_date)'), [$start_date->toDateString(), $end_date->toDateString()]); - }) - ->orWhere(function ($q) use ($start_date, $end_date) { - // Trường hợp 3: Khoảng thời gian được chọn nằm trong khoảng của ticket - $q->where(DB::raw('DATE(start_date)'), '<=', $start_date->toDateString()) - ->where(DB::raw('DATE(end_date)'), '>=', $end_date->toDateString()); - }); - }) - ->get(); - - $dataListPeriodConfirmed = []; - if ($ticketsConfirmed->count() > 0) { - foreach ($ticketsConfirmed as $ticket) { - $dataListPeriodConfirmed = array_merge($dataListPeriodConfirmed, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period)); - } - } - // dd($dataListPeriodConfirmed,$ticketsConfirmed,$start_date->toDateString(),$end_date->toDateString()); - // Chuyển đổi mảng đa chiều thành mảng chuỗi để có thể so sánh - $periodStrings = []; - $waitingPeriodStrings = []; - $confirmedPeriodStrings = []; - - foreach ($dataListPeriod as $period) { - if ($period['period'] == 'ALL') { - $periodStrings[] = $period['date'] . '_S'; - $periodStrings[] = $period['date'] . '_C'; - } else { - $periodStrings[] = $period['date'] . '_' . $period['period']; - } - } - - foreach ($dataListPeriodConfirmed as $period) { - if ($period['period'] == 'ALL') { - $confirmedPeriodStrings[] = $period['date'] . '_S'; - $confirmedPeriodStrings[] = $period['date'] . '_C'; - } else { - $confirmedPeriodStrings[] = $period['date'] . '_' . $period['period']; - } - } - - foreach ($dataListPeriodWaiting as $period) { - if ($period['period'] == 'ALL') { - $waitingPeriodStrings[] = $period['date'] . '_S'; - $waitingPeriodStrings[] = $period['date'] . '_C'; - } else { - $waitingPeriodStrings[] = $period['date'] . '_' . $period['period']; - } - } - // Kiểm tra xem có sự trùng lặp giữa request mới và tickets đang chờ duyệt - if (count(array_intersect($periodStrings, $waitingPeriodStrings)) > 0) { - return AbstractController::ResultError('Đã có ticket đang chờ duyệt trong thời gian này, không thể tạo ticket mới!'); - } - - // Kiểm tra xem có sự trùng lặp giữa request mới và tickets đã được duyệt - if (count(array_intersect($periodStrings, $confirmedPeriodStrings)) > 0) { - return AbstractController::ResultError('Đã có ticket được duyệt trong thời gian này, không thể tạo ticket mới!'); - } } // --- Kết thúc kiểm tra --- - // Nếu đủ ngày phép (hoặc loại ticket không phải ONLEAVE), tiếp tục tạo ticket + // 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, @@ -495,7 +370,7 @@ class TicketController extends Controller ); // Thêm kiểm tra null trước khi gửi mail if ($dataMasterStartPeriod && $dataMasterEndPeriod && $dataMasterType) { - Mail::to($value->email)->send(new TicketMail($data)); + Mail::to($value->email)->queue(new TicketMail($data)); } else { Log::error("Missing category data for ticket ID: {$ticket->id}. Mail not sent."); } @@ -504,6 +379,34 @@ class TicketController extends Controller 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; + $ticket->save(); + + return AbstractController::ResultSuccess($ticket, "Ticket updated successfully!"); + } + /** * Kiểm tra số dư ngày phép của người dùng. * @@ -577,142 +480,196 @@ class TicketController extends Controller // 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 $monthKey => $monthData) { + foreach ($requestMonths as $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'); + // 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 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 ngày nghỉ sẽ dùng trong tháng + $willUsedDaysInMonth = $usedDaysInMonth + $monthData['days_requested']; - // Tổng phép có trong tháng - $totalLeaveDaysInMonth = $this->getTotalLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], $isAccept); + // 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í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']); + // Tổng ngày phép còn lại trong tháng + $remainingOnleaveDaysInMonth = $remainingOnleaveDays - $remainingDaysInMonthIsUsed; - //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, - // ); - // } + // 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'; - $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 + $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'; - $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."; + $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; - $days_will_use = 0; - $days_will_use_without_pay = $monthData['days_requested']; - } else if ($remainingDaysInMonthRemaining < $monthData['days_requested']) { // không đủ ngày phép - if ( - $remainingDaysInMonthRemaining >= $maxDaysPerMonth - ) { + 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 ($remainingOnleaveDaysInMonth >= $maxDaysPerMonth) { $hasInsufficientDays = true; $month_data_status = 'exceed_max_days'; - $daysNotEnough = $monthData['days_requested'] - $maxDaysPerMonth; - $monthMessage = "* Theo quy định ngày phép tối đa mỗi tháng là {$maxDaysPerMonth} ngày. \nTháng {$monthData['month']}/{$monthData['year']}: \n - Bạn đã sử dụng {$usedDaysInMonth} ngày phép, còn lại {$remainingDaysInMonthRemaining} ngày phép.\n - Bạn sẽ sử dụng {$maxDaysPerMonth} ngày phép và {$daysNotEnough} ngày không phép."; - $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; - $days_will_use = $maxDaysPerMonth; - $days_will_use_without_pay = $monthData['days_requested'] - $maxDaysPerMonth; - } else { + $onleave_days_will_use = $maxDaysPerMonth - $onleaveDaysInMonth; + $nopay_days_will_use = $monthData['days_requested'] - $maxDaysPerMonth + $onleaveDaysInMonth; + + 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'; - $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 đó + $onleave_days_will_use = $remainingOnleaveDaysInMonth; + $nopay_days_will_use = $monthData['days_requested'] - $remainingOnleaveDaysInMonth; - $days_will_use = $remainingDaysInMonthRemaining; - $days_will_use_without_pay = $daysNotEnough; + Log::debug("--- Không đủ phép trong tháng, ko vượt limit ---", [ + "Phep" => $onleave_days_will_use, + "Khong Phep" => $nopay_days_will_use + ]); } - } else if ( - $remainingDaysInMonthRemaining >= $monthData['days_requested'] - ) { // Đủ 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 + // 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; - $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) { + 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) { - - $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."; + $onleave_days_will_use = $maxDaysPerMonth - $remainingDaysInMonthIsUsed; + $nopay_days_will_use = $monthData['days_requested'] - $onleave_days_will_use; } 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."; + $onleave_days_will_use = $maxDaysPerMonth; + $nopay_days_will_use = $monthData['days_requested'] - $maxDaysPerMonth; } $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; + Log::debug("--- Đủ phép, Waiting ticket ---", [ + "Phep" => $onleave_days_will_use, + "Khong Phep" => $nopay_days_will_use + ]); } - $remainingDaysInMonthRemaining = $monthData['days_requested']; - } else { - $days_will_use = $monthData['days_requested']; - $days_will_use_without_pay = 0; + // Đủ 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' => $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 + '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 [ @@ -739,7 +696,7 @@ class TicketController extends Controller }) ->where('n_user_id', $user->id) ->where('n_year', $year) - ->where('n_month', "<=", $month) + // ->where('n_month', "<=", $month) ->where('n_reason', 'ONLEAVE') ->sum('categories.c_value'); } @@ -752,20 +709,35 @@ class TicketController extends Controller $totalAllocated = 0; if ($leaveDaysInfo) { - // if ($leaveDaysInfo->ld_day_total > $month) { - // $totalAllocated = $month; - // } else { + $currentMonth = Carbon::now()->month; $totalAllocated = $leaveDaysInfo->ld_day_total; - // } - // Nếu là duyệt ticket thì sẽ cộng thêm số ngày phép còn lại của tháng hiện tại - if ($isAccept) { - if ($month >= $totalAllocated) { - $totalAllocated += $month - $totalAllocated; + // 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; } } - // bên hàm duyệt ticket sẽ check lại để + 1 ngày trước job để đảm bảo đủ ngày phép + // 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; + } + } } else { Log::warning("No LeaveDays record found for user ID: {$user->id}, year: {$year}. Assuming 0 allocated days."); } @@ -837,11 +809,9 @@ class TicketController extends Controller public function handleTicket(Request $request) { - $rules = [ 'ticket_id' => 'required', 'action' => 'required', - // 'admin_note' => 'required' ]; // Validate the request @@ -856,21 +826,6 @@ class TicketController extends Controller 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; @@ -882,248 +837,25 @@ class TicketController extends Controller $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, null, true); - // 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 - $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)); - + $this->handleConfirmTicket($ticket, $admin, $admin_note, $onleave, $leaveWithoutPay); 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)); + $this->handleRefuseTicket($ticket, $admin, $admin_note); 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', @@ -1142,10 +874,40 @@ class TicketController extends Controller 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; + + 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; + } + + // Không tìm được ngày phép, ko phép + if ($onleave == null || $leaveWithoutPay == null) { + return redirect()->to(config('app.client_url') . '/404'); + } + + if ($action == "confirm") { + $this->handleConfirmTicket($ticket, $admin, $admin_note, $onleave, $leaveWithoutPay); return redirect()->to(config('app.client_url') . '/tickets-management'); } - // Send notification email to users + 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) + { $startDate = $ticket->start_date; //Start day $startPeriod = $ticket->start_period; //The session begins $endDate = $ticket->end_date; //End date @@ -1156,159 +918,146 @@ class TicketController extends Controller $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 ($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); + 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']; + }); - 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; - $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 + 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, + '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' => $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' => $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' => $leaveWithoutPay, + 'n_note' => $ticket->reason, + 'ticket_id' => $ticket->id + ]); + $daysWillUseWithoutPay -= $use; } - } - } - } 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})"); + // Nếu cả hai đều hết thì thôi, không tạo nữa } } } - } else if ($ticket->type == "WFH") { - $dataListPeriod = $this->getAllPeriod($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period); + } 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, + '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, @@ -1317,98 +1066,105 @@ class TicketController extends Controller 'n_year' => $year, 'n_time_type' => $result['period'], 'n_reason' => $ticket->type, - 'n_note' => $ticket->reason + 'n_note' => $ticket->reason, + 'ticket_id' => $ticket->id ]); - //WFH - start tracking - $type = $result['period']; - $date = Carbon::create($year, $month, $day)->setTimezone(env('TIME_ZONE')); + //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); + //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 + 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(); + $ticket['updated_by'] = $admin->name; + $ticket['admin_note'] = $admin_note; + $ticket['status'] = 'CONFIRMED'; + $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)); + $this->createOrUpdateRecordForCurrentMonth($month, $year); - // Refuse Success - return redirect()->to(config('app.client_url') . '/tickets-management'); - } - - // Failed - return redirect()->to(config('app.client_url') . '/tickets-management'); + // 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) @@ -1582,6 +1338,51 @@ class TicketController extends Controller return $results; } + private function buildMonthlyLeaveMessage( + $index, + $max, + $monthData, + $totalLeave, + $usedLeave, + $usedNoPay, + $willUseLeave, + $willUseNoPay + ): string { + $message = ""; + + if ($index === 0) { + $message .= "* Quy định: mỗi tháng được nghỉ tối đa {$max} ngày phép\n"; + $message .= "- Bạn đang có: {$totalLeave} ngày phép\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 .= " - 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. diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php index b33b165..1b33037 100644 --- a/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php +++ b/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php @@ -17,6 +17,7 @@ use Modules\Admin\app\Models\MonthlyTimekeeping; use Modules\Admin\app\Models\Tracking; use Maatwebsite\Excel\Facades\Excel; use App\Exports\TimekeepingExport; +use Modules\Admin\app\Models\Ticket; class TimekeepingController extends Controller { @@ -177,78 +178,92 @@ class TimekeepingController extends Controller $year = $request->year; $note = Notes::find($id); - if ($note) { - $n_month = $note->n_month; - $n_year = $note->n_year; - - if ($note->n_reason == "ONLEAVE") { - // Get note reason ONLEAVE by $n_month, $n_year not include $note->id & include $note->n_user_id - // $onleave = Notes::getNotesByMonthAndYearAndUserId($n_month, $n_year, $note->n_user_id, $note->id); - - // Get note reason LEAVE_WITHOUT_PAY by $n_month, $n_year & include $note->n_user_id - $leaveWithoutPay = Notes::getNotesByMonthAndYearAndUserIdAndReason($n_month, $n_year, $note->n_user_id, 'LEAVE_WITHOUT_PAY'); - - if (count($leaveWithoutPay) > 0) { - $deletedValue = ($note->n_time_type === 'ALL') ? 1.0 : 0.5; - $needUpdate = $deletedValue; - // dd($needUpdate, $leaveWithoutPay); - foreach ($leaveWithoutPay as $lwNote) { - if ($needUpdate <= 0) break; - - if ($lwNote->n_time_type === 'ALL') { - if ($needUpdate == 1.0) { - // Chuyển cả note ALL thành phép - $lwNote->update(['n_reason' => 'ONLEAVE']); - $needUpdate = 0; - break; - } else { // $needUpdate == 0.5 - // Tách ALL thành 2 note S và C, chuyển S thành phép, C giữ không phép - Notes::create([ - 'n_user_id' => $lwNote->n_user_id, - 'n_day' => $lwNote->n_day, - 'n_month' => $lwNote->n_month, - 'n_year' => $lwNote->n_year, - 'n_time_type' => 'S', - 'n_reason' => 'ONLEAVE', - 'n_note' => $lwNote->n_note - ]); - Notes::create([ - 'n_user_id' => $lwNote->n_user_id, - 'n_day' => $lwNote->n_day, - 'n_month' => $lwNote->n_month, - 'n_year' => $lwNote->n_year, - 'n_time_type' => 'C', - 'n_reason' => 'LEAVE_WITHOUT_PAY', - 'n_note' => $lwNote->n_note - ]); - $lwNote->delete(); - $needUpdate = 0; - break; - } - } else { - // Nếu $lwNote->n_time_type == 'S' hoặc 'C' => 0.5 - if ($needUpdate == 1.0) { - // Chuyển cả note ALL thành phép - $lwNote->update(['n_reason' => 'ONLEAVE']); - $needUpdate -= 0.5; - } else { // $needUpdate == 0.5 - // S hoặc C, chỉ cần chuyển đúng 0.5 ngày - $lwNote->update(['n_reason' => 'ONLEAVE']); - $needUpdate = 0; - break; - } - } - } - } else { - // Khi note phép và k tồn tại nghỉ không phép => phép + dồn cho tháng sau - } - } - $note->delete(); - $this->createOrUpdateRecordForCurrentMonth($month, $year); - return response()->json(['message' => 'Delete success', 'status' => true]); + if (!$note) { + return response()->json(['message' => 'Note not found', 'status' => false]); } - return response()->json(['message' => 'Delete fail', 'status' => false]); + $ticket = Ticket::find($note->ticket_id); + if (!$ticket) { + return response()->json(['message' => 'Ticket not found, can not delete note', 'status' => false]); + } + + $admin = auth('admins')->user(); + $ticket->updated_by = $admin->name; + $ticket->status = "REFUSED"; + $ticket->save(); + + Notes::where('ticket_id', $ticket->id)->delete(); + $this->createOrUpdateRecordForCurrentMonth($month, $year); + return response()->json(['message' => 'Delete success', 'status' => true]); + + // $n_month = $note->n_month; + // $n_year = $note->n_year; + + // if ($note->n_reason == "ONLEAVE") { + // // Get note reason ONLEAVE by $n_month, $n_year not include $note->id & include $note->n_user_id + // // $onleave = Notes::getNotesByMonthAndYearAndUserId($n_month, $n_year, $note->n_user_id, $note->id); + + // // Get note reason LEAVE_WITHOUT_PAY by $n_month, $n_year & include $note->n_user_id + // $leaveWithoutPay = Notes::getNotesByMonthAndYearAndUserIdAndReason($n_month, $n_year, $note->n_user_id, 'LEAVE_WITHOUT_PAY'); + + // if (count($leaveWithoutPay) > 0) { + // $deletedValue = ($note->n_time_type === 'ALL') ? 1.0 : 0.5; + // $needUpdate = $deletedValue; + // // dd($needUpdate, $leaveWithoutPay); + // foreach ($leaveWithoutPay as $lwNote) { + // if ($needUpdate <= 0) break; + + // if ($lwNote->n_time_type === 'ALL') { + // if ($needUpdate == 1.0) { + // // Chuyển cả note ALL thành phép + // $lwNote->update(['n_reason' => 'ONLEAVE']); + // $needUpdate = 0; + // break; + // } else { // $needUpdate == 0.5 + // // Tách ALL thành 2 note S và C, chuyển S thành phép, C giữ không phép + // Notes::create([ + // 'n_user_id' => $lwNote->n_user_id, + // 'n_day' => $lwNote->n_day, + // 'n_month' => $lwNote->n_month, + // 'n_year' => $lwNote->n_year, + // 'n_time_type' => 'S', + // 'n_reason' => 'ONLEAVE', + // 'n_note' => $lwNote->n_note + // ]); + // Notes::create([ + // 'n_user_id' => $lwNote->n_user_id, + // 'n_day' => $lwNote->n_day, + // 'n_month' => $lwNote->n_month, + // 'n_year' => $lwNote->n_year, + // 'n_time_type' => 'C', + // 'n_reason' => 'LEAVE_WITHOUT_PAY', + // 'n_note' => $lwNote->n_note + // ]); + // $lwNote->delete(); + // $needUpdate = 0; + // break; + // } + // } else { + // // Nếu $lwNote->n_time_type == 'S' hoặc 'C' => 0.5 + // if ($needUpdate == 1.0) { + // // Chuyển cả note ALL thành phép + // $lwNote->update(['n_reason' => 'ONLEAVE']); + // $needUpdate -= 0.5; + // } else { // $needUpdate == 0.5 + // // S hoặc C, chỉ cần chuyển đúng 0.5 ngày + // $lwNote->update(['n_reason' => 'ONLEAVE']); + // $needUpdate = 0; + // break; + // } + // } + // } + // } else { + // // Khi note phép và k tồn tại nghỉ không phép => phép + dồn cho tháng sau + // } + // } + // $note->delete(); + // $this->createOrUpdateRecordForCurrentMonth($month, $year); + // return response()->json(['message' => 'Delete success', 'status' => true]); } public function export(Request $request) diff --git a/BACKEND/Modules/Admin/routes/api.php b/BACKEND/Modules/Admin/routes/api.php index ae1b8db..fe3832e 100755 --- a/BACKEND/Modules/Admin/routes/api.php +++ b/BACKEND/Modules/Admin/routes/api.php @@ -164,6 +164,7 @@ Route::middleware('api') ], function () { Route::get('/all', [TicketController::class, 'getAll'])->middleware('check.permission:admin.hr'); Route::get('/getByUserId', [TicketController::class, 'getByUserId'])->middleware('check.permission:admin.hr.staff'); + Route::post('/update', [TicketController::class, 'updateTicket'])->middleware('check.permission:admin.hr'); Route::post('/create', [TicketController::class, 'createTicket'])->middleware('check.permission:admin.hr.staff'); Route::get('/delete', [TicketController::class, 'deleteTicket'])->middleware('check.permission:admin.hr.staff'); Route::post('/handle-ticket', [TicketController::class, 'handleTicket'])->middleware('check.permission:admin'); diff --git a/BACKEND/Modules/Auth/app/Http/Controllers/UserController.php b/BACKEND/Modules/Auth/app/Http/Controllers/UserController.php index bb1e898..ff84f4d 100755 --- a/BACKEND/Modules/Auth/app/Http/Controllers/UserController.php +++ b/BACKEND/Modules/Auth/app/Http/Controllers/UserController.php @@ -3,13 +3,15 @@ namespace Modules\Auth\app\Http\Controllers; use App\Http\Controllers\Controller; +use App\Models\LeaveDays; use App\Traits\IsAPI; +use Carbon\Carbon; use Illuminate\Http\Request; -use Illuminate\Http\Response; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Storage; use Modules\Auth\app\Models\User; use Illuminate\Support\Str; +use Modules\Admin\app\Models\Category; use SimpleSoftwareIO\QrCode\Facades\QrCode; class UserController extends Controller @@ -34,9 +36,36 @@ class UserController extends Controller ]); if ($request->has('id')) { - $payload = $request->only(['name', 'email', 'permission']); + $payload = $request->only(['name', 'email', 'permission', 'is_permanent']); $user = User::find($request->id); + // Không cho chuyển từ chính thức thành lại thử việc + if (!$request->is_permanent && $user->is_permanent) { + return response()->json(['status' => false, 'message' => 'You cannot change an employee from permanent to probationary.']); + } + + // Thêm ngày phép khi thành nhân viên chính thức + if ($request->is_permanent && !$user->is_permanent) { + $userLeaveDay = LeaveDays::where('ld_user_id', $user->id) + ->where('ld_year', Carbon::now()->year) + ->first(); + + if ($userLeaveDay) { + $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 + $userLeaveDay->ld_day_total = $permanentDefault; + + $newNote = "Cộng ngày phép cho nhân viên chính thức"; // Thêm ghi chú + if (!empty($userLeaveDay->ld_note)) { + $userLeaveDay->ld_note = $userLeaveDay->ld_note . "\n" . $newNote; + } else { + $userLeaveDay->ld_note = $newNote; + } + $userLeaveDay->save(); + } + } + + $payload['permanent_date'] = Carbon::now()->toDateString(); $user->update($payload); return response()->json(['data' => $user, 'status' => true, 'message' => 'Update successful']); } else { @@ -44,7 +73,19 @@ class UserController extends Controller 'name' => $request->name, 'email' => $request->email, 'password' => bcrypt('Work@1234'), - 'permission' => $request->permission + 'permission' => $request->permission, + 'is_permanent' => false + ]); + + // Khởi tạo LeaveDays cho nhân viên mới + LeaveDays::insert([ + 'ld_user_id' => $user->id, + 'ld_day_total' => 0, + 'ld_year' => Carbon::now()->year, + 'ld_additional_day' => 0, + 'ld_note' => '', + 'created_at' => now(), + 'updated_at' => now(), ]); $user_res = [ @@ -98,8 +139,6 @@ class UserController extends Controller return response()->json(['data' => ['user' => $user_res, 'gitea' => "dev", 'zulip' => "dev"], 'status' => true, 'message' => 'Create successful']); } } - - return response()->json(['status' => false, 'message' => 'Process fail']); } public function delete(Request $request) diff --git a/BACKEND/Modules/Auth/app/Models/User.php b/BACKEND/Modules/Auth/app/Models/User.php index 0fc45ba..4347eca 100755 --- a/BACKEND/Modules/Auth/app/Models/User.php +++ b/BACKEND/Modules/Auth/app/Models/User.php @@ -25,7 +25,9 @@ class User extends Authenticatable implements JWTSubject 'name', 'email', 'password', - 'permission' + 'permission', + 'is_permanent', + 'permanent_date' ]; /** diff --git a/BACKEND/app/Console/Kernel.php b/BACKEND/app/Console/Kernel.php index d8fa86b..f063914 100755 --- a/BACKEND/app/Console/Kernel.php +++ b/BACKEND/app/Console/Kernel.php @@ -25,7 +25,7 @@ class Kernel extends ConsoleKernel // ->dailyAt('18:00'); // Chạy command vào ngày 31/12 lúc 23:59:59 mỗi năm - // $schedule->command('initialize:leavedays')->yearlyOn(12, 31, '23:59:59'); + $schedule->command('initialize:leavedays')->yearlyOn(12, 31, '23:59:59'); $schedule->command('leave:deduct')->yearlyOn(3, 31, '23:59:59'); // Chạy buổi sáng lúc 12:00 diff --git a/BACKEND/app/Jobs/AddMonthlyLeaveDays.php b/BACKEND/app/Jobs/AddMonthlyLeaveDays.php index 4c58b9f..38fd483 100644 --- a/BACKEND/app/Jobs/AddMonthlyLeaveDays.php +++ b/BACKEND/app/Jobs/AddMonthlyLeaveDays.php @@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use Modules\Admin\app\Models\Category; class AddMonthlyLeaveDays implements ShouldQueue { @@ -18,6 +19,8 @@ class AddMonthlyLeaveDays implements ShouldQueue protected $month; protected $year; + private const ONLEAVE_PER_MONTH = 1; // Ngày phép cộng mỗi tháng + public function __construct($month = null, $year = null) { $this->month = $month ?? Carbon::now()->month; @@ -29,6 +32,11 @@ class AddMonthlyLeaveDays implements ShouldQueue $users = User::get(); foreach ($users as $user) { + // Nếu là nhân viên chưa chính thức, ko cộng phép + if (!$user->is_permanent) { + continue; + } + $leaveDay = LeaveDays::where('ld_user_id', $user->id) ->where('ld_year', $this->year) ->first(); @@ -46,11 +54,36 @@ class AddMonthlyLeaveDays implements ShouldQueue ]); $leaveDay->save(); } else { - // Kiểm tra nếu số ngày phép hiện tại nhỏ hơn tháng hiện tại + // Check có phải là nhân viên chính thức trong năm nay (Nhân viên mới) + if ($user->permanent_date && $user->permanent_date !== '0000-00-00') { + $permenantYear = Carbon::parse($user->permanent_date)->year; + + if ($permenantYear === $this->year) { + $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 + + $permanentMonth = Carbon::parse($user->permanent_date)->month; + if ($this->month > $leaveDay->ld_day_total - ($permanentDefault - $permanentMonth)) { + $leaveDay->ld_day_total += self::ONLEAVE_PER_MONTH; + + // Xử lý ghi chú + $newNote = "Cập nhật ngày phép đến tháng " . $this->month; + if (!empty($leaveDay->ld_note)) { + // Nếu đã có ghi chú, thêm ghi chú mới vào và xuống dòng + $leaveDay->ld_note = $leaveDay->ld_note . "\n" . $newNote; + } else { + // Nếu chưa có ghi chú, gán ghi chú mới + $leaveDay->ld_note = $newNote; + } + $leaveDay->save(); + } + } + } + + // Kiểm tra nếu số ngày phép hiện tại nhỏ hơn tháng hiện tại (Nhân viên cũ) if ($leaveDay->ld_day_total < $this->month) { - // Cập nhật số ngày phép bằng với tháng hiện tại - $oldDays = $leaveDay->ld_day_total; - $leaveDay->ld_day_total = $this->month; + // Cộng mỗi tháng 1 ngày phép cho nhân viên + $leaveDay->ld_day_total += self::ONLEAVE_PER_MONTH; // Xử lý ghi chú $newNote = "Cập nhật ngày phép đến tháng " . $this->month; diff --git a/BACKEND/app/Jobs/DeductLeaveDays.php b/BACKEND/app/Jobs/DeductLeaveDays.php index 2d4bd4a..b5f9571 100644 --- a/BACKEND/app/Jobs/DeductLeaveDays.php +++ b/BACKEND/app/Jobs/DeductLeaveDays.php @@ -10,7 +10,6 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\DB; use Carbon\Carbon; class DeductLeaveDays implements ShouldQueue @@ -42,38 +41,19 @@ class DeductLeaveDays implements ShouldQueue continue; } - $totalLeaveDaysByMonth = Notes::join('categories', function ($join) { + // Lấy tổng ngày nghỉ phép 3 tháng đầu trong năm + $usedOnleaveDaysTotal = 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', $this->year) - ->where('notes.n_user_id', $user->id) - ->where('notes.n_reason', 'ONLEAVE') - ->groupBy(DB::raw('notes.n_year')) - ->first(); - - if ($totalLeaveDaysByMonth) { - //Nếu ngày phép thừa năm trước chưa sử dụng hết => cập nhật lại ngày đó (Ngày tồn đọng - ngày sử dụng) - if ($existingData->ld_additional_day > $totalLeaveDaysByMonth->leave_days) { - LeaveDays::where('ld_year', $this->year) - ->where('ld_user_id', $user->id) - ->update([ - 'ld_additional_day' => $totalLeaveDaysByMonth->leave_days, - ]); - } - } else { - //Nếu không sử dụng ngày nghỉ còn lại ở năm rồi thì xóa => theo luật ld - LeaveDays::where('ld_year', $this->year) - ->where('ld_user_id', $user->id) - ->update([ - 'ld_additional_day' => "0", - ]); - } + ->where('n_user_id', $user->id) + ->where('n_year', $this->year) + ->where('n_month', "<=", 3) + ->where('n_reason', 'ONLEAVE') + ->sum('categories.c_value'); + + $existingData->ld_additional_day = $usedOnleaveDaysTotal ?? 0; + $existingData->save(); } } } diff --git a/BACKEND/app/Jobs/InitializeLeaveDays.php b/BACKEND/app/Jobs/InitializeLeaveDays.php index 1d7569f..6ba8114 100644 --- a/BACKEND/app/Jobs/InitializeLeaveDays.php +++ b/BACKEND/app/Jobs/InitializeLeaveDays.php @@ -34,7 +34,8 @@ class InitializeLeaveDays implements ShouldQueue public function handle(): void { $users = User::get(); - $ld_day_total = 12; + $ld_day_total = Carbon::now()->month; // Khởi tạo phép hiện có bằng tháng hiện tại + foreach ($users as $user) { // Kiểm tra xem dữ liệu của user này đã tồn tại cho năm hiện tại chưa $existingData = LeaveDays::where('ld_user_id', $user->id) @@ -82,7 +83,7 @@ class InitializeLeaveDays implements ShouldQueue // Tạo dữ liệu cho năm hiện tại LeaveDays::insert([ 'ld_user_id' => $user->id, - 'ld_day_total' => $ld_day_total, + 'ld_day_total' => $user->is_permanent ? $ld_day_total : 0, // Nếu là nhân viên mới, ko cấp phép 'ld_year' => $this->year, 'ld_additional_day' => $ld_additional_day, 'ld_note' => $ld_note, diff --git a/BACKEND/app/Models/Notes.php b/BACKEND/app/Models/Notes.php index 659fcda..f1a90a7 100644 --- a/BACKEND/app/Models/Notes.php +++ b/BACKEND/app/Models/Notes.php @@ -18,6 +18,7 @@ class Notes extends Model 'n_time_type', 'n_reason', 'n_note', + 'ticket_id' ]; /** diff --git a/BACKEND/database/migrations/2025_06_20_050728_add_ticket_relation_notes_table.php b/BACKEND/database/migrations/2025_06_20_050728_add_ticket_relation_notes_table.php new file mode 100644 index 0000000..1a9b528 --- /dev/null +++ b/BACKEND/database/migrations/2025_06_20_050728_add_ticket_relation_notes_table.php @@ -0,0 +1,26 @@ +foreignId('ticket_id') + ->nullable() + ->constrained('tickets') + ->onDelete('cascade'); + }); + } + + public function down(): void + { + Schema::table('notes', function (Blueprint $table) { + $table->dropForeign(['ticket_id']); + $table->dropColumn('ticket_id'); + }); + } +}; diff --git a/BACKEND/database/migrations/2025_06_23_098764_add_permanent_users_table.php b/BACKEND/database/migrations/2025_06_23_098764_add_permanent_users_table.php new file mode 100644 index 0000000..d761819 --- /dev/null +++ b/BACKEND/database/migrations/2025_06_23_098764_add_permanent_users_table.php @@ -0,0 +1,31 @@ +boolean('is_permanent')->default(true); + $table->date('permanent_date'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('users', function (Blueprint $table) { + $table->dropColumn('is_permanent'); + $table->dropColumn('permanent_date'); + }); + } +}; diff --git a/BACKEND/database/migrations/2025_06_24_025714_add_onleave_permanent_categories_table.php b/BACKEND/database/migrations/2025_06_24_025714_add_onleave_permanent_categories_table.php new file mode 100644 index 0000000..8aaa45f --- /dev/null +++ b/BACKEND/database/migrations/2025_06_24_025714_add_onleave_permanent_categories_table.php @@ -0,0 +1,35 @@ +insert([ + [ + 'c_code' => 'PERMANENT', + 'c_name' => 'Phép cộng nhân viên chính thức', + 'c_type' => 'PERMANENT_ONLEAVE', + 'c_value' => 3, + 'c_active' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ], + ]); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + DB::table('categories')->where('c_code', 'PERMANENT')->delete(); + } +}; diff --git a/BACKEND/resources/views/email/notification_tickets.blade.php b/BACKEND/resources/views/email/notification_tickets.blade.php index 08a39e7..f9c4d13 100644 --- a/BACKEND/resources/views/email/notification_tickets.blade.php +++ b/BACKEND/resources/views/email/notification_tickets.blade.php @@ -1,4 +1,3 @@ - @@ -37,199 +36,167 @@
-
© 2024 APAC Tech. + + |
+