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"
/>