From 7dc31bf75be6eaad5ed0fb72b021e359abf97f6d Mon Sep 17 00:00:00 2001 From: Truong Vo <41848815+vmtruong301296@users.noreply.github.com> Date: Wed, 7 May 2025 11:33:52 +0700 Subject: [PATCH] =?UTF-8?q?B=E1=BB=95=20sung=20x=C3=B3a=20ng=C3=A0y=20ph?= =?UTF-8?q?=C3=A9p?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Http/Controllers/CategoryController.php | 7 +- .../Controllers/LeaveManagementController.php | 2 +- .../app/Http/Controllers/TicketController.php | 267 +++++++++++++++--- .../Controllers/TimekeepingController.php | 71 ++++- BACKEND/app/Models/Notes.php | 24 +- ...5_05_07_023335_update_leave_categories.php | 52 ++++ ..._024806_add_limit_leave_month_category.php | 40 +++ ...49_add_saturday_work_schedule_category.php | 40 +++ ...2025_05_07_092903_add_leave_categories.php | 60 ++++ .../pages/LeaveManagement/LeaveManagement.tsx | 28 +- 10 files changed, 529 insertions(+), 62 deletions(-) create mode 100644 BACKEND/database/migrations/2025_05_07_023335_update_leave_categories.php create mode 100644 BACKEND/database/migrations/2025_05_07_024806_add_limit_leave_month_category.php create mode 100644 BACKEND/database/migrations/2025_05_07_024949_add_saturday_work_schedule_category.php create mode 100644 BACKEND/database/migrations/2025_05_07_092903_add_leave_categories.php diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/CategoryController.php b/BACKEND/Modules/Admin/app/Http/Controllers/CategoryController.php index 58c767c..0150d30 100644 --- a/BACKEND/Modules/Admin/app/Http/Controllers/CategoryController.php +++ b/BACKEND/Modules/Admin/app/Http/Controllers/CategoryController.php @@ -14,7 +14,7 @@ class CategoryController extends Controller * @param Request $request The HTTP request object. * @return \Illuminate\Http\JsonResponse The JSON response containing the list of master data. */ - public function getListMaster(Request $request) + public static function getListMaster(Request $request) { $data = Category::where('c_type', '=', $request->type)->where('c_active', '=', 1)->select('id', 'c_code', 'c_name', 'c_value', 'c_type')->get(); return AbstractController::ResultSuccess($data); @@ -24,4 +24,9 @@ class CategoryController extends Controller $data = Category::where('c_type', '=', $type)->where('c_code', '=', $code)->where('c_active', '=', 1)->select('id', 'c_code', 'c_name', 'c_value', 'c_type')->first(); return $data; } + public static function getListMasterByType($type) + { + $data = Category::where('c_type', '=', $type)->where('c_active', '=', 1)->select('id', 'c_code', 'c_name', 'c_value', 'c_type')->get(); + return $data; + } } diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/LeaveManagementController.php b/BACKEND/Modules/Admin/app/Http/Controllers/LeaveManagementController.php index e3eaf52..2aca157 100644 --- a/BACKEND/Modules/Admin/app/Http/Controllers/LeaveManagementController.php +++ b/BACKEND/Modules/Admin/app/Http/Controllers/LeaveManagementController.php @@ -35,7 +35,7 @@ class LeaveManagementController extends Controller }) ->leftJoin("categories as reason", function ($join) { $join->on('n_reason', '=', 'reason.c_code'); - $join->on('reason.c_type', DB::raw("CONCAT('REASON')")); + $join->on('reason.c_type', DB::raw("CONCAT('REASON_NOTES')")); }) ->select( DB::raw('notes.n_user_id as n_user_id'), diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php index 96556de..84afd88 100644 --- a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php +++ b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php @@ -105,7 +105,6 @@ class TicketController extends Controller ->paginate($request->get('per_page'))->toArray(), ['status' => true] ); - return response()->json($responseData); } @@ -228,7 +227,7 @@ class TicketController extends Controller if (empty($dataListPeriod)) { return AbstractController::ResultError('Không thể tính toán khoảng thời gian nghỉ hợp lệ.'); } - // Lây thông tin tickets đang ở trạng thái WAITING + // Lấy thông tin tickets nghỉ phép đang ở trạng thái WAITING $ticketsWaiting = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->where('type', 'ONLEAVE') ->get(); $dataListPeriodWaiting = []; @@ -238,9 +237,9 @@ class TicketController extends Controller } } - // Lấy thông tin tickets đang ở trạng thái CONFIRMED + // 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', 'LEAVE_WITHOUT_PAY']) + ->whereIn('type', ['ONLEAVE']) ->where(DB::raw('DATE(start_date)'), '>=', $start_date->toDateString()) ->where(DB::raw('DATE(end_date)'), '<=', $end_date->toDateString()) ->get(); @@ -288,7 +287,7 @@ class TicketController extends Controller 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!'); @@ -298,7 +297,6 @@ class TicketController extends Controller $waitingTicketsMessage = ''; if (!empty($dataListPeriodWaiting)) { // Kiểm tra số dư ngày phép cho tickets waiting - $totalWaitingDays = $this->calculateTotalLeaveDays($dataListPeriodWaiting); $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 @@ -466,8 +464,12 @@ class TicketController extends Controller $usedDaysInMonthWithoutPay = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'LEAVE_WITHOUT_PAY'); // Tính tổng giới hạn ngày nghỉ có phép tối đa trong tháng + $maxDaysPerMonth = $this->getMaxLeaveDaysPerMonth(); + $days_will_use = 0; + $days_will_use_without_pay = 0; + // Tính tổng số ngày nghỉ trong tháng $totalDaysInMonth = $usedDaysInMonth + $usedDaysInMonthWithoutPay + $monthData['days_requested']; @@ -487,14 +489,19 @@ class TicketController extends Controller $month_data['status'] = 'no_days_left'; $monthMessage = "* Hiện tại bạn đã hết phép nghỉ trong tháng {$monthData['month']}/{$monthData['year']}\n - Bạn sẽ nộp: " . $monthData['days_requested'] . " ngày không phép."; $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; + + $days_will_use = 0; + $days_will_use_without_pay = $monthData['days_requested']; } else if ($remainingDaysInMonthRemaining < $monthData['days_requested']) { // không đủ ngày phép $hasInsufficientDays = true; $month_data['status'] = 'insufficient_days'; $daysNotEnough = $monthData['days_requested'] - $remainingDaysInMonthRemaining; $monthMessage = "* Tháng {$monthData['month']}/{$monthData['year']}: \n - Số ngày phép còn lại: {$remainingDaysInMonthRemaining}, Số ngày yêu cầu: {$monthData['days_requested']}.\n - Bạn sẽ sử dụng {$remainingDaysInMonthRemaining} ngày phép và {$daysNotEnough} ngày không phép."; $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; - $remainingDaysInMonthIsUsed = $remainingDaysInMonth; // lấy số ngày phép còn lại của tháng đó + + $days_will_use = $remainingDaysInMonthRemaining; + $days_will_use_without_pay = $daysNotEnough; } else if ($remainingDaysInMonthRemaining >= $monthData['days_requested']) { // Đủ 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 @@ -504,20 +511,31 @@ class TicketController extends Controller $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."; $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage; + + $days_will_use = $maxDaysPerMonth; + $days_will_use_without_pay = $daysWithoutPermission; + } else { + $days_will_use = $monthData['days_requested']; + $days_will_use_without_pay = 0; } $remainingDaysInMonthRemaining = $monthData['days_requested']; + } else { + $days_will_use = $monthData['days_requested']; + $days_will_use_without_pay = 0; } $month_data = [ 'year' => $monthData['year'], 'month' => $monthData['month'], 'total_leave_days_in_month' => $totalLeaveDaysInMonth, //tổng số ngày phép - 'total_leave_days_in_month_to_month' => $totalLeaveDaysInMonthToMonth, // tổng ngày nghỉ có phép - 'remaining_days_in_month' => $remainingDaysInMonth, //ngày phép còn lại - 'days_used' => $usedDaysInMonth, //tổng số ngày nghỉ có phép ở tháng hiện tại - 'days_used_without_pay' => $usedDaysInMonthWithoutPay, //tổng số ngày nghỉ không phép ở tháng hiện tại + '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' => 'ok', // mặc định là ok ]; @@ -660,18 +678,12 @@ class TicketController extends Controller if (!$ticket || $ticket->status !== "WAITING") { return response()->json(['message' => "Ticket not found", 'status' => false]); } - $results = $this->getAllPeriod($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period); - - // $admin->id != user_id of ticket ---> continue // Confirm - // Add records to the notes table like function Timekeeping.addNoteForUser() based on the $results array - + // Update updated_by and admin_note in tickets table + // Refuse // Update updated_by and admin_note in tickets table // Send notification email to users - - // Refuse - // Update updated_by and admin_note in tickets table $startDate = $ticket->start_date; //Start day $startPeriod = $ticket->start_period; //The session begins $endDate = $ticket->end_date; //End date @@ -682,25 +694,171 @@ 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 ($action == "confirm") { - foreach ($results as $result) { - list($year, $month, $day) = explode('-', $result['date']); - Notes::create([ - 'n_user_id' => $ticket->user_id, - 'n_day' => $day, - 'n_month' => $month, - 'n_year' => $year, - 'n_time_type' => $result['period'], - 'n_reason' => $ticket->type, - 'n_note' => $ticket->reason - ]); + if ($onleave == null || $leaveWithoutPay == null) { + return response()->json(['message' => "Data reason notes not found", 'status' => false]); + } - if ($ticket->type == "WFH") { + if ($action == "confirm") { + if ($ticket->type == "ONLEAVE") { + $dataListPeriod = $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period); + $balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod); + // dd($balanceCheckResult,$dataListPeriod); + if ($balanceCheckResult['success'] == false) { + if ($balanceCheckResult['months_info']) { + foreach ($balanceCheckResult['months_info'] as $monthInfo) { + // Lọc các ngày thuộc đúng tháng/năm này + $daysInMonth = array_filter($dataListPeriod, function ($item) use ($monthInfo) { + $date = \Carbon\Carbon::parse($item['date']); + return $date->year == $monthInfo['year'] && $date->month == $monthInfo['month']; + }); + + $daysWillUse = $monthInfo['days_will_use'] ?? 0; + $daysWillUseWithoutPay = $monthInfo['days_will_use_without_pay'] ?? 0; + // dd($daysWillUse,$daysWillUseWithoutPay,$daysInMonth); + foreach ($daysInMonth as $item) { + list($year, $month, $day) = explode('-', $item['date']); + $period = $item['period']; + $value = ($period === 'ALL') ? 1.0 : 0.5; + + if ($period === 'ALL' && $daysWillUse == 0.5) { + // Chỉ còn 0.5 phép, chia thành 2 bản ghi: 1 phép, 1 không phép + // Ưu tiên phép cho buổi sáng (S), không phép cho buổi chiều (C) + Notes::create([ + 'n_user_id' => $ticket->user_id, + 'n_day' => $day, + 'n_month' => $month, + 'n_year' => $year, + 'n_time_type' => 'S', + 'n_reason' => $onleave, + 'n_note' => $ticket->reason + ]); + Notes::create([ + 'n_user_id' => $ticket->user_id, + 'n_day' => $day, + 'n_month' => $month, + 'n_year' => $year, + 'n_time_type' => 'C', + 'n_reason' => $leaveWithoutPay, + 'n_note' => $ticket->reason + ]); + $daysWillUse = 0; + $daysWillUseWithoutPay -= 0.5; + } elseif ($daysWillUse > 0) { + // Dùng ngày phép trước + $use = min($daysWillUse, $value); + Notes::create([ + 'n_user_id' => $ticket->user_id, + 'n_day' => $day, + 'n_month' => $month, + 'n_year' => $year, + 'n_time_type' => $period, + 'n_reason' => $onleave, + 'n_note' => $ticket->reason + ]); + $daysWillUse -= $use; + } elseif ($daysWillUseWithoutPay > 0) { + // Hết phép, chuyển sang không phép + $use = min($daysWillUseWithoutPay, $value); + Notes::create([ + 'n_user_id' => $ticket->user_id, + 'n_day' => $day, + 'n_month' => $month, + 'n_year' => $year, + 'n_time_type' => $period, + 'n_reason' => $leaveWithoutPay, + 'n_note' => $ticket->reason + ]); + $daysWillUseWithoutPay -= $use; + } + // Nếu cả hai đều hết thì thôi, không tạo nữa + } + } + } + } else { + //Đủ phép + foreach ($dataListPeriod as $result) { + list($year, $month, $day) = explode('-', $result['date']); + Notes::create([ + 'n_user_id' => $ticket->user_id, + 'n_day' => $day, + 'n_month' => $month, + 'n_year' => $year, + 'n_time_type' => $result['period'], + 'n_reason' => $onleave, // có phép + 'n_note' => $ticket->reason + ]); + } + } + + $yearCheck = Carbon::parse($endDate)->year; + // Check giá trị ld_day_total của bảng leave_days thuộc user id đó với giá trị của list item note trong bảng notes của user id đó + $leaveDaysInfo = LeaveDays::where('ld_user_id', $ticket->user_id) + ->where('ld_year', $yearCheck) + ->first(); + if ($leaveDaysInfo) { + // Tính tổng số ngày nghỉ có phép đã sử dụng trong năm + $totalUsedLeaveDays = Notes::join('categories', function ($join) { + $join->on('notes.n_time_type', '=', 'categories.c_code') + ->where('categories.c_type', 'TIME_TYPE'); + }) + ->where('n_user_id', $ticket->user_id) + ->where('n_year', $yearCheck) + ->where('n_reason', 'ONLEAVE') + ->sum('categories.c_value'); + + // Tính tổng số ngày phép được cấp + $totalAllocatedDays = $leaveDaysInfo->ld_day_total + + $leaveDaysInfo->ld_additional_day + + $leaveDaysInfo->ld_special_leave_day; + + // Tính số ngày vượt quá và làm tròn lên + $excessDays = $totalUsedLeaveDays - $totalAllocatedDays; + $roundedExcessDays = ceil($excessDays); // Làm tròn lên số nguyên gần nhất + + // Kiểm tra nếu số ngày đã sử dụng vượt quá số ngày được cấp + if ($roundedExcessDays > 0) { + Log::warning("User ID: {$ticket->user_id} has used more leave days ({$totalUsedLeaveDays}) than allocated ({$totalAllocatedDays})"); + + // Cập nhật cột ld_day_total với số ngày đã làm tròn + if ($roundedExcessDays > 0) { + $leaveDaysInfo->ld_day_total += $roundedExcessDays; + $leaveDaysInfo->save(); + + Log::info("Updated leave days for User ID: {$ticket->user_id}. Added {$roundedExcessDays} days (rounded from {$excessDays})"); + } + } + } + } else if ($ticket->type == "WFH") { + $dataListPeriod = $this->getAllPeriod($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period); + foreach ($dataListPeriod as $result) { + list($year, $month, $day) = explode('-', $result['date']); + Notes::create([ + 'n_user_id' => $ticket->user_id, + 'n_day' => $day, + 'n_month' => $month, + 'n_year' => $year, + 'n_time_type' => $result['period'], + 'n_reason' => $ticket->type, + 'n_note' => $ticket->reason + ]); + + + //WFH - start tracking $type = $result['period']; $date = Carbon::create($year, $month, $day)->setTimezone(env('TIME_ZONE')); @@ -730,6 +888,7 @@ class TicketController extends Controller 'created_at' => $end->setTimezone('UTC') ] ]); + //WFH - end tracking } } @@ -814,6 +973,13 @@ class TicketController extends Controller // 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(); @@ -825,26 +991,33 @@ class TicketController extends Controller 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 if the current day is a Saturday - if ($date->dayOfWeek === Carbon::SATURDAY) { + // 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); - if ($latest_schedule) { - $weeksDifference = $latestScheduleDate->startOfDay()->diffInWeeks($date->copy()->startOfDay()); - $isSaturdayWorkDay = ($weeksDifference % 2 === 0); + // echo $date->toDateString() . ' - ' . ($isSaturdayWorkDay ? 'Làm việc' : 'Nghỉ') . "
"; + } - // echo $date->toDateString() . ' - ' . ($isSaturdayWorkDay ? 'Làm việc' : 'Nghỉ') . "
"; + if ($isSaturdayWorkDay) { + $results[] = ['date' => $date->toDateString(), 'period' => "S"]; + } + + continue; } - - if ($isSaturdayWorkDay) { - $results[] = ['date' => $date->toDateString(), 'period' => "S"]; + // Skip Sundays entirely + else if ($date->dayOfWeek === Carbon::SUNDAY) { + continue; } - - continue; - } - // Skip Sundays entirely - else if ($date->dayOfWeek === Carbon::SUNDAY) { - continue; } if ($date->isSameDay($startDate)) { diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php index cddc744..b33b165 100644 --- a/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php +++ b/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php @@ -152,8 +152,6 @@ class TimekeepingController extends Controller return response()->json(['status' => true, 'message' => 'Add successfully']); } - - public function updateCacheMonth(Request $request) { $month = $request->month; @@ -180,6 +178,71 @@ class TimekeepingController extends Controller $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]); @@ -206,10 +269,10 @@ class TimekeepingController extends Controller } // Lọc chỉ lấy user có permission bao gồm staff - $staffData = array_filter($responseData['data'], function($user) { + $staffData = array_filter($responseData['data'], function ($user) { return isset($user['user']['permission']) && strpos($user['user']['permission'], 'staff') !== false; }); - + $currentDate = date('d_His'); return Excel::download( new TimekeepingExport( diff --git a/BACKEND/app/Models/Notes.php b/BACKEND/app/Models/Notes.php index 57e4e49..659fcda 100644 --- a/BACKEND/app/Models/Notes.php +++ b/BACKEND/app/Models/Notes.php @@ -11,7 +11,13 @@ class Notes extends Model use HasFactory; protected $fillable = [ - 'n_user_id', 'n_day', 'n_month', 'n_year', 'n_time_type', 'n_reason', 'n_note', + 'n_user_id', + 'n_day', + 'n_month', + 'n_year', + 'n_time_type', + 'n_reason', + 'n_note', ]; /** @@ -25,7 +31,7 @@ class Notes extends Model { return self::leftJoin("categories as reason", function ($join) { $join->on('n_reason', '=', 'reason.c_code'); - $join->on('reason.c_type', DB::raw("CONCAT('REASON')")); + $join->on('reason.c_type', DB::raw("CONCAT('REASON_NOTES')")); }) ->leftJoin("categories as timeTypes", function ($join) { $join->on('n_time_type', '=', 'timeTypes.c_code'); @@ -47,4 +53,18 @@ class Notes extends Model ) ->get(); } + + public static function getNotesByMonthAndYearAndUserId($month, $year, $userId, $idNote) + { + return self::where('n_reason', 'ONLEAVE')->where('n_month', $month)->where('n_year', $year) + ->where('n_user_id', $userId) + ->where('id', '!=', $idNote)->get(); + } + + public static function getNotesByMonthAndYearAndUserIdAndReason($month, $year, $userId, $reason) + { + return self::where('n_reason', $reason)->where('n_month', $month)->where('n_year', $year) + ->where('n_user_id', $userId) + ->orderBy('n_day', 'asc')->orderBy('n_time_type', 'desc')->get(); + } } diff --git a/BACKEND/database/migrations/2025_05_07_023335_update_leave_categories.php b/BACKEND/database/migrations/2025_05_07_023335_update_leave_categories.php new file mode 100644 index 0000000..ddb1b92 --- /dev/null +++ b/BACKEND/database/migrations/2025_05_07_023335_update_leave_categories.php @@ -0,0 +1,52 @@ +where('c_type', 'REASON') + ->where('c_code', 'LEAVE_WITHOUT_PAY') + ->delete(); + + // Cập nhật tên "Nghỉ phép năm" thành "Nghỉ phép" + DB::table('categories') + ->where('c_name', 'Nghỉ phép năm') + ->update(['c_name' => 'Nghỉ phép']); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + // Khôi phục item đã xóa + DB::table('categories')->insert([ + 'c_code' => 'LEAVE_WITHOUT_PAY', + 'c_name' => 'Không phép', + 'c_type' => 'REASON', + 'c_value' => "", + 'c_active' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ]); + + // Khôi phục tên cũ + DB::table('categories') + ->where('c_name', 'Nghỉ phép') + ->update(['c_name' => 'Nghỉ phép năm']); + } +} diff --git a/BACKEND/database/migrations/2025_05_07_024806_add_limit_leave_month_category.php b/BACKEND/database/migrations/2025_05_07_024806_add_limit_leave_month_category.php new file mode 100644 index 0000000..91cb49f --- /dev/null +++ b/BACKEND/database/migrations/2025_05_07_024806_add_limit_leave_month_category.php @@ -0,0 +1,40 @@ +insert([ + 'c_code' => 'LIMIT', + 'c_name' => 'Giới hạn số ngày nghỉ có phép/tháng', + 'c_type' => 'LIMIT_LEAVE_MONTH', + 'c_value' => '3', + 'c_active' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + DB::table('categories') + ->where('c_code', 'LIMIT') + ->where('c_type', 'LIMIT_LEAVE_MONTH') + ->delete(); + } +} diff --git a/BACKEND/database/migrations/2025_05_07_024949_add_saturday_work_schedule_category.php b/BACKEND/database/migrations/2025_05_07_024949_add_saturday_work_schedule_category.php new file mode 100644 index 0000000..c0e51c8 --- /dev/null +++ b/BACKEND/database/migrations/2025_05_07_024949_add_saturday_work_schedule_category.php @@ -0,0 +1,40 @@ +insert([ + 'c_code' => '10-05-2025', + 'c_name' => 'Ngày bắt đầu làm việc thứ 7 trong năm', + 'c_type' => 'SATURDAY_WORK_SCHEDULE', + 'c_value' => '2025', + 'c_active' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + DB::table('categories') + ->where('c_code', '10-05-2025') + ->where('c_type', 'SATURDAY_WORK_SCHEDULE') + ->delete(); + } +} diff --git a/BACKEND/database/migrations/2025_05_07_092903_add_leave_categories.php b/BACKEND/database/migrations/2025_05_07_092903_add_leave_categories.php new file mode 100644 index 0000000..30ffb7f --- /dev/null +++ b/BACKEND/database/migrations/2025_05_07_092903_add_leave_categories.php @@ -0,0 +1,60 @@ +insert([ + [ + 'c_code' => 'LEAVE_WITHOUT_PAY', + 'c_name' => 'Không phép', + 'c_type' => 'REASON_NOTES', + 'c_value' => "", + 'c_active' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'c_code' => 'WFH', + 'c_name' => 'Work From Home', + 'c_type' => 'REASON_NOTES', + 'c_value' => "", + 'c_active' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ], + [ + 'c_code' => 'ONLEAVE', + 'c_name' => 'Nghỉ phép', + 'c_type' => 'REASON_NOTES', + 'c_value' => "", + 'c_active' => 1, + 'created_at' => now(), + 'updated_at' => now(), + ], + ]); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + DB::table('categories') + ->whereIn('c_code', ['LEAVE_WITHOUT_PAY', 'WFH', 'ONLEAVE']) + ->where('c_type', 'REASON_NOTES') + ->delete(); + } +} diff --git a/FRONTEND/src/pages/LeaveManagement/LeaveManagement.tsx b/FRONTEND/src/pages/LeaveManagement/LeaveManagement.tsx index 7621a54..6ffc596 100644 --- a/FRONTEND/src/pages/LeaveManagement/LeaveManagement.tsx +++ b/FRONTEND/src/pages/LeaveManagement/LeaveManagement.tsx @@ -114,6 +114,10 @@ const LeaveManagement = () => { const [dataTimeType, setDataTimeType] = useState([]) const [dataReason, setDataReason] = useState([]) + const now = new Date() + const currentMonth = now.getMonth() + 1 // getMonth() trả về 0-11 + const currentYear = now.getFullYear() + const getListMasterByType = async (type: string) => { try { const params = { @@ -361,8 +365,8 @@ const LeaveManagement = () => { }) } }} - label={'Total Leave'} - placeholder="Input total leave days" + label={'Phép năm'} + placeholder="Nhập số ngày phép năm" /> { }) } }} - label={'Day additional leave'} - placeholder="Input additional leave days" + label={'Phép năm cũ'} + placeholder="Nhập số ngày phép năm cũ" /> { }) } }} - label={'Day special leave'} - placeholder="Input special leave days" + label={'Phép đặc biệt'} + placeholder="Nhập số ngày phép đặc biệt" />