Bổ sung xóa ngày phép

This commit is contained in:
Truong Vo 2025-05-07 11:33:52 +07:00
parent ca766fc293
commit 7dc31bf75b
10 changed files with 529 additions and 62 deletions

View File

@ -14,7 +14,7 @@ class CategoryController extends Controller
* @param Request $request The HTTP request object. * @param Request $request The HTTP request object.
* @return \Illuminate\Http\JsonResponse The JSON response containing the list of master data. * @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(); $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); 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(); $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; 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;
}
} }

View File

@ -35,7 +35,7 @@ class LeaveManagementController extends Controller
}) })
->leftJoin("categories as reason", function ($join) { ->leftJoin("categories as reason", function ($join) {
$join->on('n_reason', '=', 'reason.c_code'); $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( ->select(
DB::raw('notes.n_user_id as n_user_id'), DB::raw('notes.n_user_id as n_user_id'),

View File

@ -105,7 +105,6 @@ class TicketController extends Controller
->paginate($request->get('per_page'))->toArray(), ->paginate($request->get('per_page'))->toArray(),
['status' => true] ['status' => true]
); );
return response()->json($responseData); return response()->json($responseData);
} }
@ -228,7 +227,7 @@ class TicketController extends Controller
if (empty($dataListPeriod)) { if (empty($dataListPeriod)) {
return AbstractController::ResultError('Không thể tính toán khoảng thời gian nghỉ hợp lệ.'); 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') $ticketsWaiting = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->where('type', 'ONLEAVE')
->get(); ->get();
$dataListPeriodWaiting = []; $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') $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(start_date)'), '>=', $start_date->toDateString())
->where(DB::raw('DATE(end_date)'), '<=', $end_date->toDateString()) ->where(DB::raw('DATE(end_date)'), '<=', $end_date->toDateString())
->get(); ->get();
@ -288,7 +287,7 @@ class TicketController extends Controller
if (count(array_intersect($periodStrings, $waitingPeriodStrings)) > 0) { 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!'); 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 // 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) { 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!'); 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 = ''; $waitingTicketsMessage = '';
if (!empty($dataListPeriodWaiting)) { if (!empty($dataListPeriodWaiting)) {
// Kiểm tra số dư ngày phép cho tickets waiting // 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"; $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 // 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'); $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 // Tính tổng giới hạn ngày nghỉ có phép tối đa trong tháng
$maxDaysPerMonth = $this->getMaxLeaveDaysPerMonth(); $maxDaysPerMonth = $this->getMaxLeaveDaysPerMonth();
$days_will_use = 0;
$days_will_use_without_pay = 0;
// Tính tổng số ngày nghỉ trong tháng // Tính tổng số ngày nghỉ trong tháng
$totalDaysInMonth = $usedDaysInMonth + $usedDaysInMonthWithoutPay + $monthData['days_requested']; $totalDaysInMonth = $usedDaysInMonth + $usedDaysInMonthWithoutPay + $monthData['days_requested'];
@ -487,14 +489,19 @@ class TicketController extends Controller
$month_data['status'] = 'no_days_left'; $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."; $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; $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 } else if ($remainingDaysInMonthRemaining < $monthData['days_requested']) { // không đủ ngày phép
$hasInsufficientDays = true; $hasInsufficientDays = true;
$month_data['status'] = 'insufficient_days'; $month_data['status'] = 'insufficient_days';
$daysNotEnough = $monthData['days_requested'] - $remainingDaysInMonthRemaining; $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."; $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; $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage;
$remainingDaysInMonthIsUsed = $remainingDaysInMonth; // lấy số ngày phép còn lại của tháng đó $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 đó } 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 // 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; $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."; $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; $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']; $remainingDaysInMonthRemaining = $monthData['days_requested'];
} else {
$days_will_use = $monthData['days_requested'];
$days_will_use_without_pay = 0;
} }
$month_data = [ $month_data = [
'year' => $monthData['year'], 'year' => $monthData['year'],
'month' => $monthData['month'], 'month' => $monthData['month'],
'total_leave_days_in_month' => $totalLeaveDaysInMonth, //tổng số ngày phép '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 'total_leave_days_in_month_to_month' => $totalLeaveDaysInMonthToMonth, //tổng ngày nghỉ có phép đã nghỉ
'remaining_days_in_month' => $remainingDaysInMonth, //ngày phép còn lại '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 ở tháng hiện tạ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 ở 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 'days_requested' => $monthData['days_requested'], //số ngày yêu cầu nghỉ của tháng
'remaining_days_in_month_remaining' => $remainingDaysInMonthRemaining, '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 'status' => 'ok', // mặc định là ok
]; ];
@ -660,18 +678,12 @@ class TicketController extends Controller
if (!$ticket || $ticket->status !== "WAITING") { if (!$ticket || $ticket->status !== "WAITING") {
return response()->json(['message' => "Ticket not found", 'status' => false]); 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 // 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 // Update updated_by and admin_note in tickets table
// Send notification email to users // Send notification email to users
// Refuse
// Update updated_by and admin_note in tickets table
$startDate = $ticket->start_date; //Start day $startDate = $ticket->start_date; //Start day
$startPeriod = $ticket->start_period; //The session begins $startPeriod = $ticket->start_period; //The session begins
$endDate = $ticket->end_date; //End date $endDate = $ticket->end_date; //End date
@ -682,25 +694,171 @@ class TicketController extends Controller
$dataMasterEndPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $endPeriod); $dataMasterEndPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $endPeriod);
$dataMasterType = CategoryController::getListMasterByCodeAndType("REASON", $type); $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'); $formattedStartDate = Carbon::createFromFormat('Y-m-d', $startDate)->format('d/m/Y');
$formattedEndDate = Carbon::createFromFormat('Y-m-d', $endDate)->format('d/m/Y'); $formattedEndDate = Carbon::createFromFormat('Y-m-d', $endDate)->format('d/m/Y');
$user = Admin::find($ticket->user_id); $user = Admin::find($ticket->user_id);
if ($action == "confirm") { if ($onleave == null || $leaveWithoutPay == null) {
foreach ($results as $result) { return response()->json(['message' => "Data reason notes not found", 'status' => false]);
list($year, $month, $day) = explode('-', $result['date']); }
Notes::create([
'n_user_id' => $ticket->user_id,
'n_day' => $day,
'n_month' => $month,
'n_year' => $year,
'n_time_type' => $result['period'],
'n_reason' => $ticket->type,
'n_note' => $ticket->reason
]);
if ($ticket->type == "WFH") { 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']; $type = $result['period'];
$date = Carbon::create($year, $month, $day)->setTimezone(env('TIME_ZONE')); $date = Carbon::create($year, $month, $day)->setTimezone(env('TIME_ZONE'));
@ -730,6 +888,7 @@ class TicketController extends Controller
'created_at' => $end->setTimezone('UTC') '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 // Parse the date string from c_code to a Carbon instance for proper comparison
return Carbon::createFromFormat('d-m-Y', $item->c_code); 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) // Get the most recent schedule date (first item after sorting)
$latest_schedule = $saturday_work_schedules->first(); $latest_schedule = $saturday_work_schedules->first();
@ -825,26 +991,33 @@ class TicketController extends Controller
return []; // Return empty or throw exception 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) { foreach ($period as $date) {
// Check if the current day is a Saturday // Check phải ngày thứ 7 đặc biệt thì tính như ngày bình thường
if ($date->dayOfWeek === Carbon::SATURDAY) { 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) { // echo $date->toDateString() . ' - ' . ($isSaturdayWorkDay ? 'Làm việc' : 'Nghỉ') . "<br>";
$weeksDifference = $latestScheduleDate->startOfDay()->diffInWeeks($date->copy()->startOfDay()); }
$isSaturdayWorkDay = ($weeksDifference % 2 === 0);
// echo $date->toDateString() . ' - ' . ($isSaturdayWorkDay ? 'Làm việc' : 'Nghỉ') . "<br>"; if ($isSaturdayWorkDay) {
$results[] = ['date' => $date->toDateString(), 'period' => "S"];
}
continue;
} }
// Skip Sundays entirely
if ($isSaturdayWorkDay) { else if ($date->dayOfWeek === Carbon::SUNDAY) {
$results[] = ['date' => $date->toDateString(), 'period' => "S"]; continue;
} }
continue;
}
// Skip Sundays entirely
else if ($date->dayOfWeek === Carbon::SUNDAY) {
continue;
} }
if ($date->isSameDay($startDate)) { if ($date->isSameDay($startDate)) {

View File

@ -152,8 +152,6 @@ class TimekeepingController extends Controller
return response()->json(['status' => true, 'message' => 'Add successfully']); return response()->json(['status' => true, 'message' => 'Add successfully']);
} }
public function updateCacheMonth(Request $request) public function updateCacheMonth(Request $request)
{ {
$month = $request->month; $month = $request->month;
@ -180,6 +178,71 @@ class TimekeepingController extends Controller
$note = Notes::find($id); $note = Notes::find($id);
if ($note) { 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(); $note->delete();
$this->createOrUpdateRecordForCurrentMonth($month, $year); $this->createOrUpdateRecordForCurrentMonth($month, $year);
return response()->json(['message' => 'Delete success', 'status' => true]); 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 // 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; return isset($user['user']['permission']) && strpos($user['user']['permission'], 'staff') !== false;
}); });
$currentDate = date('d_His'); $currentDate = date('d_His');
return Excel::download( return Excel::download(
new TimekeepingExport( new TimekeepingExport(

View File

@ -11,7 +11,13 @@ class Notes extends Model
use HasFactory; use HasFactory;
protected $fillable = [ 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) { return self::leftJoin("categories as reason", function ($join) {
$join->on('n_reason', '=', 'reason.c_code'); $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) { ->leftJoin("categories as timeTypes", function ($join) {
$join->on('n_time_type', '=', 'timeTypes.c_code'); $join->on('n_time_type', '=', 'timeTypes.c_code');
@ -47,4 +53,18 @@ class Notes extends Model
) )
->get(); ->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();
}
} }

View File

@ -0,0 +1,52 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class UpdateLeaveCategories extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
// Xóa item với type REASON và code LEAVE_WITHOUT_PAY
DB::table('categories')
->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']);
}
}

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class AddLimitLeaveMonthCategory extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
DB::table('categories')->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();
}
}

View File

@ -0,0 +1,40 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class AddSaturdayWorkScheduleCategory extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
DB::table('categories')->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();
}
}

View File

@ -0,0 +1,60 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\DB;
class AddLeaveCategories extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
DB::table('categories')->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();
}
}

View File

@ -114,6 +114,10 @@ const LeaveManagement = () => {
const [dataTimeType, setDataTimeType] = useState<DataTimeType[]>([]) const [dataTimeType, setDataTimeType] = useState<DataTimeType[]>([])
const [dataReason, setDataReason] = useState<DataReason[]>([]) const [dataReason, setDataReason] = useState<DataReason[]>([])
const now = new Date()
const currentMonth = now.getMonth() + 1 // getMonth() trả về 0-11
const currentYear = now.getFullYear()
const getListMasterByType = async (type: string) => { const getListMasterByType = async (type: string) => {
try { try {
const params = { const params = {
@ -361,8 +365,8 @@ const LeaveManagement = () => {
}) })
} }
}} }}
label={'Total Leave'} label={'Phép năm'}
placeholder="Input total leave days" placeholder="Nhập số ngày phép năm"
/> />
<TextInput <TextInput
mb={'md'} mb={'md'}
@ -388,8 +392,8 @@ const LeaveManagement = () => {
}) })
} }
}} }}
label={'Day additional leave'} label={'Phép năm cũ'}
placeholder="Input additional leave days" placeholder="Nhập số ngày phép năm cũ"
/> />
<TextInput <TextInput
mb={'md'} mb={'md'}
@ -415,8 +419,8 @@ const LeaveManagement = () => {
}) })
} }
}} }}
label={'Day special leave'} label={'Phép đặc biệt'}
placeholder="Input special leave days" placeholder="Nhập số ngày phép đặc biệt"
/> />
<Textarea <Textarea
mb={'md'} mb={'md'}
@ -525,12 +529,20 @@ const LeaveManagement = () => {
<Table.Th ta={'center'} style={{ width: '40px' }}></Table.Th> <Table.Th ta={'center'} style={{ width: '40px' }}></Table.Th>
<Table.Th>User</Table.Th> <Table.Th>User</Table.Th>
{monthInYear.map((d) => { {monthInYear.map((d) => {
const isCurrentMonth =
Number(date.year) === currentYear && d.value === currentMonth
return ( return (
<Menu width={200} shadow="md" key={d.value}> <Menu width={200} shadow="md" key={d.value}>
<Menu.Target> <Menu.Target>
<Table.Th <Table.Th
ta={'center'} ta={'center'}
style={{ cursor: 'pointer', width: '40px' }} style={{
cursor: 'pointer',
width: '40px',
backgroundColor: isCurrentMonth ? '#ffe066' : undefined,
color: isCurrentMonth ? '#000' : undefined,
fontWeight: isCurrentMonth ? 'bold' : undefined,
}}
> >
<span>{d.name}</span> <span>{d.name}</span>
</Table.Th> </Table.Th>
@ -590,6 +602,8 @@ const LeaveManagement = () => {
</Table.Td> </Table.Td>
{monthInYear.map((d, i) => { {monthInYear.map((d, i) => {
const isCurrentMonth =
Number(date.year) === currentYear && d.value === currentMonth
let leaveDataByMonth = getDetailLeaveDay( let leaveDataByMonth = getDetailLeaveDay(
user.monthlyLeaveDays, user.monthlyLeaveDays,
) )