Merge pull request 'truong-leave-day' (#124) from truong-leave-day into dev

Reviewed-on: #124
This commit is contained in:
vincent.vo 2025-06-16 16:34:55 +10:00
commit 172af363de
1 changed files with 192 additions and 58 deletions

View File

@ -220,15 +220,16 @@ class TicketController extends Controller
$start_date = Carbon::create($startDate)->setTimezone(env('TIME_ZONE')); $start_date = Carbon::create($startDate)->setTimezone(env('TIME_ZONE'));
$end_date = Carbon::create($endDate)->setTimezone(env('TIME_ZONE')); $end_date = Carbon::create($endDate)->setTimezone(env('TIME_ZONE'));
// --- Chỉ kiểm tra ngày phép khi loại là ONLEAVE ---
if ($type === 'ONLEAVE' && !$isAccept) {
// Get mảng ngày nghỉ // Get mảng ngày nghỉ
$dataListPeriod = $this->getAllPeriodNew($start_date, $startPeriod, $end_date, $endPeriod); $dataListPeriod = $this->getAllPeriodNew($start_date, $startPeriod, $end_date, $endPeriod);
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ệ.');
} }
// --- Chỉ kiểm tra ngày phép khi loại là ONLEAVE ---
if ($type === 'ONLEAVE' && !$isAccept) {
// Lấy thông tin tickets nghỉ phép đ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')->whereIn('type', ['ONLEAVE'])
->get(); ->get();
$dataListPeriodWaiting = []; $dataListPeriodWaiting = [];
if ($ticketsWaiting->count() > 0) { if ($ticketsWaiting->count() > 0) {
@ -237,11 +238,34 @@ 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 // 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)
->whereIn('type', ['ONLEAVE']) ->where('status', 'CONFIRMED')
->where(DB::raw('DATE(start_date)'), '>=', $start_date->toDateString()) ->whereIn('type', ['ONLEAVE', 'WFH'])
->where(DB::raw('DATE(end_date)'), '<=', $end_date->toDateString()) ->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(); ->get();
$dataListPeriodConfirmed = []; $dataListPeriodConfirmed = [];
@ -250,10 +274,11 @@ class TicketController extends Controller
$dataListPeriodConfirmed = array_merge($dataListPeriodConfirmed, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period)); $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 // Chuyển đổi mảng đa chiều thành mảng chuỗi để có thể so sánh
$periodStrings = []; $periodStrings = [];
$waitingPeriodStrings = []; $waitingPeriodStrings = [];
$waitingPeriodStringsWFH = [];
$confirmedPeriodStrings = []; $confirmedPeriodStrings = [];
foreach ($dataListPeriod as $period) { foreach ($dataListPeriod as $period) {
@ -274,6 +299,15 @@ class TicketController extends Controller
} }
} }
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) { foreach ($dataListPeriodConfirmed as $period) {
if ($period['period'] == 'ALL') { if ($period['period'] == 'ALL') {
$confirmedPeriodStrings[] = $period['date'] . '_S'; $confirmedPeriodStrings[] = $period['date'] . '_S';
@ -288,6 +322,11 @@ class TicketController extends Controller
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 đ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 // 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!');
@ -338,6 +377,85 @@ class TicketController extends Controller
return AbstractController::ResultError("Không thỏa mãn điều kiện ngày phép", $balanceCheckResult); 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 --- // --- Kết thúc kiểm tra ---
@ -393,11 +511,11 @@ class TicketController extends Controller
* @param array|null $dataListPeriod Danh sách các ngày xin nghỉ [['date' => 'Y-m-d', 'period' => 'ALL|S|C'], ...] * @param array|null $dataListPeriod Danh sách các ngày xin nghỉ [['date' => 'Y-m-d', 'period' => 'ALL|S|C'], ...]
* @return array Kết quả kiểm tra ['success' => bool, 'message' => string|null, ...] * @return array Kết quả kiểm tra ['success' => bool, 'message' => string|null, ...]
*/ */
private function checkLeaveBalance($user, ?array $dataListPeriod = null, ?array $monthsInfoWaiting = null): array private function checkLeaveBalance($user, ?array $dataListPeriod = null, ?array $monthsInfoWaiting = null, ?bool $isAccept = false): array
{ {
// Kiểm tra giới hạn nghỉ phép theo tháng // Kiểm tra giới hạn nghỉ phép theo tháng
if (!empty($dataListPeriod)) { if (!empty($dataListPeriod)) {
return $this->checkMonthlyLeaveLimit($user, $dataListPeriod, $monthsInfoWaiting); return $this->checkMonthlyLeaveLimit($user, $dataListPeriod, $monthsInfoWaiting, $isAccept);
} }
// Đủ điều kiện // Đủ điều kiện
@ -448,7 +566,7 @@ class TicketController extends Controller
return $maxDaysPerMonth; return $maxDaysPerMonth;
} }
private function checkMonthlyLeaveLimit($user, array $dataListPeriod, ?array $monthsInfoWaiting = null): array private function checkMonthlyLeaveLimit($user, array $dataListPeriod, ?array $monthsInfoWaiting = null, ?bool $isAccept = false): array
{ {
// Danh sách ngày nghỉ theo tháng // Danh sách ngày nghỉ theo tháng
$requestMonths = $this->groupLeaveRequestsByMonth($dataListPeriod); $requestMonths = $this->groupLeaveRequestsByMonth($dataListPeriod);
@ -479,7 +597,7 @@ class TicketController extends Controller
$totalDaysInMonth = $usedDaysInMonth + $usedDaysInMonthWithoutPay + $monthData['days_requested']; $totalDaysInMonth = $usedDaysInMonth + $usedDaysInMonthWithoutPay + $monthData['days_requested'];
// Tổng phép có trong tháng // Tổng phép có trong tháng
$totalLeaveDaysInMonth = $this->getTotalLeaveDaysInMonth($user, $monthData['year'], $monthData['month']); $totalLeaveDaysInMonth = $this->getTotalLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], $isAccept);
// Tính tổng số ngày nghỉ có phép đến tháng hiện tạ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']); $totalLeaveDaysInMonthToMonth = $this->getTotalLeaveDaysInMonthToMonth($user, $monthData['year'], $monthData['month']);
@ -512,6 +630,17 @@ class TicketController extends Controller
$days_will_use = 0; $days_will_use = 0;
$days_will_use_without_pay = $monthData['days_requested']; $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
if (
$remainingDaysInMonthRemaining >= $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 {
$hasInsufficientDays = true; $hasInsufficientDays = true;
$month_data_status = 'insufficient_days'; $month_data_status = 'insufficient_days';
$daysNotEnough = $monthData['days_requested'] - $remainingDaysInMonthRemaining; $daysNotEnough = $monthData['days_requested'] - $remainingDaysInMonthRemaining;
@ -521,9 +650,9 @@ class TicketController extends Controller
$days_will_use = $remainingDaysInMonthRemaining; $days_will_use = $remainingDaysInMonthRemaining;
$days_will_use_without_pay = $daysNotEnough; $days_will_use_without_pay = $daysNotEnough;
}
} else if ( } else if (
$remainingDaysInMonthRemaining >= $monthData['days_requested'] $remainingDaysInMonthRemaining >= $monthData['days_requested']
// || $remainingDaysInMonthIsUsed + $monthData['days_requested'] > $maxDaysPerMonth
) { // Đủ ngày phép ở tháng đó ) { // Đủ 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
if ($totalDaysInMonth > $maxDaysPerMonth) { if ($totalDaysInMonth > $maxDaysPerMonth) {
@ -615,7 +744,7 @@ class TicketController extends Controller
->sum('categories.c_value'); ->sum('categories.c_value');
} }
private function getTotalLeaveDaysInMonth($user, int $year, int $month): float private function getTotalLeaveDaysInMonth($user, int $year, int $month, ?bool $isAccept = false): float
{ {
$leaveDaysInfo = LeaveDays::where('ld_user_id', $user->id) $leaveDaysInfo = LeaveDays::where('ld_user_id', $user->id)
->where('ld_year', $year) ->where('ld_year', $year)
@ -628,8 +757,15 @@ class TicketController extends Controller
// } else { // } else {
$totalAllocated = $leaveDaysInfo->ld_day_total; $totalAllocated = $leaveDaysInfo->ld_day_total;
// } // }
// $totalAllocated = $month; //(+ tạm để check)
// 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;
}
}
// bên hàm duyệt ticket sẽ check lại để + 1 ngày trước job để đảm bảo đủ ngày phép // bên hàm duyệt ticket sẽ check lại để + 1 ngày trước job để đảm bảo đủ ngày phép
} else { } else {
Log::warning("No LeaveDays record found for user ID: {$user->id}, year: {$year}. Assuming 0 allocated days."); Log::warning("No LeaveDays record found for user ID: {$user->id}, year: {$year}. Assuming 0 allocated days.");
} }
@ -758,7 +894,7 @@ class TicketController extends Controller
if ($action == "confirm") { if ($action == "confirm") {
if ($ticket->type == "ONLEAVE") { if ($ticket->type == "ONLEAVE") {
$dataListPeriod = $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period); $dataListPeriod = $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period);
$balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod); $balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod, null, true);
// dd($balanceCheckResult, $dataListPeriod); // dd($balanceCheckResult, $dataListPeriod);
if ($balanceCheckResult['success'] == false) { if ($balanceCheckResult['success'] == false) {
if ($balanceCheckResult['months_info']) { if ($balanceCheckResult['months_info']) {
@ -877,14 +1013,12 @@ class TicketController extends Controller
Log::warning("User ID: {$ticket->user_id} has used more leave days ({$totalUsedLeaveDays}) than allocated ({$totalAllocatedDays})"); 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 // 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->ld_day_total += $roundedExcessDays;
$leaveDaysInfo->save(); $leaveDaysInfo->save();
Log::info("Updated leave days for User ID: {$ticket->user_id}. Added {$roundedExcessDays} days (rounded from {$excessDays})"); Log::info("Updated leave days for User ID: {$ticket->user_id}. Added {$roundedExcessDays} days (rounded from {$excessDays})");
} }
} }
}
} else if ($ticket->type == "WFH") { } else if ($ticket->type == "WFH") {
$dataListPeriod = $this->getAllPeriod($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period); $dataListPeriod = $this->getAllPeriod($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period);
foreach ($dataListPeriod as $result) { foreach ($dataListPeriod as $result) {