diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php index 6dd816e..2965054 100644 --- a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php +++ b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php @@ -220,21 +220,29 @@ class TicketController extends Controller $start_date = Carbon::create($startDate)->setTimezone(env('TIME_ZONE')); $end_date = Carbon::create($endDate)->setTimezone(env('TIME_ZONE')); - $currentYear = $start_date->year; // --- Chỉ kiểm tra ngày phép khi loại là ONLEAVE --- if ($type === 'ONLEAVE' && !$isAccept) { - // Get mảng ngày nghỉ và tính tổng số ngày yêu cầu + // Get mảng ngày nghỉ $dataListPeriod = $this->getAllPeriodNew($start_date, $startPeriod, $end_date, $endPeriod); if (empty($dataListPeriod)) { - return response()->json(['message' => 'Không thể tính toán khoảng thời gian nghỉ hợp lệ.', 'status' => false]); + 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 + $ticketsWaiting = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->get(); + if ($ticketsWaiting->count() > 0) { + foreach ($ticketsWaiting as $ticket) { + $dataListPeriod = array_merge($dataListPeriod, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period)); + } + } + // dd($dataListPeriod); // Kiểm tra số dư ngày phép - $balanceCheckResult = $this->checkLeaveBalance($user, $currentYear, $dataListPeriod); + $balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod); + // dd($balanceCheckResult); // Nếu không đủ ngày phép, trả về thông báo và không tạo ticket if (!$balanceCheckResult['success']) { - return response()->json($balanceCheckResult); + return AbstractController::ResultError("Không thỏa mãn điều kiện ngày phép", $balanceCheckResult); } } // --- Kết thúc kiểm tra --- @@ -286,54 +294,24 @@ class TicketController extends Controller * Kiểm tra số dư ngày phép của người dùng. * * @param UserModel $user Người dùng tạo ticket - * @param int $year Năm kiểm tra - * @param float $daysRequested Số ngày yêu cầu nghỉ * @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, ...] */ - private function checkLeaveBalance($user, int $year, ?array $dataListPeriod = null): array + private function checkLeaveBalance($user, ?array $dataListPeriod = null): array { - // Tính tổng số ngày yêu cầu - $daysRequested = $this->calculateTotalLeaveDays($dataListPeriod); - - // 1. Tính tổng ngày phép được cấp - $totalAllocated = $this->getTotalAllocatedDays($user, $year); - - // 2. Tính số ngày đã nghỉ có phép trong năm - $usedDays = $this->getUsedLeaveDays($user, $year); - - // 3. Tính số ngày còn lại - $remainingDays = $totalAllocated - $usedDays; - - // 4. 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 $monthsInfo = []; if (!empty($dataListPeriod)) { $monthlyCheckResult = $this->checkMonthlyLeaveLimit($user, $dataListPeriod); if (!$monthlyCheckResult['success']) { return $monthlyCheckResult; } - $monthsInfo = $monthlyCheckResult['months_info']; - if (!empty($monthsInfo)) { - //Danh sách ngày nghỉ trong tháng dựa trên list ngày xin nghỉ - - } - } - - // 5. Kiểm tra đủ ngày phép không - if ($remainingDays < $daysRequested) { - return $this->insufficientLeaveDaysResponse($user, $remainingDays, $daysRequested); - } - - // 6. Kiểm tra giới hạn ngày liên tục - if ($daysRequested > 3) { - return $this->exceedMaxConsecutiveDaysResponse($user, $daysRequested); } // Đủ điều kiện return [ 'success' => true, 'message' => null, - 'remaining_days' => $remainingDays, 'months_info' => $monthsInfo ]; } @@ -386,6 +364,7 @@ class TicketController extends Controller $hasInsufficientDays = false; $errorMessage = ''; $remainingDaysInMonthIsUsed = 0; + foreach ($requestMonths as $monthKey => $monthData) { // Tính tổng số ngày nghỉ có phép trong tháng $usedDaysInMonth = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'ONLEAVE'); @@ -425,18 +404,27 @@ class TicketController extends Controller if ($remainingDaysInMonthRemaining <= 0) { //hết phép $hasInsufficientDays = true; $month_data['status'] = 'no_days_left'; - $monthMessage = "Hiện tại bạn đã hết phép nghỉ trong tháng {$monthData['month']}/{$monthData['year']}\nBạ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; } 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']}: Số ngày phép còn lại: {$remainingDaysInMonthRemaining}, Số ngày yêu cầu: {$monthData['days_requested']}.\nBạ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; $remainingDaysInMonthIsUsed = $remainingDaysInMonth; - } + } else if ($remainingDaysInMonthRemaining >= $monthData['days_requested']) { // Đủ ngày phép ở tháng đó + // 1. Check thêm rule 1 tháng chỉ được nghỉ tối đa $maxDaysPerMonth ngày có phép, ngày vượt sẽ là ngày không phép + if ($monthData['days_requested'] > $maxDaysPerMonth) { + $hasInsufficientDays = true; + $month_data['status'] = 'exceed_max_days'; + $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; + } + } // Thêm thông tin tháng vào mảng kết quả $monthsInfo[] = $month_data; } @@ -445,7 +433,7 @@ class TicketController extends Controller if ($hasInsufficientDays) { return [ 'success' => false, - 'message' => $errorMessage . "\n\nBạn có chấp nhận không?", + 'message' => $errorMessage . "\n\nBạn có chấp nhận không?\n", 'warning_type' => 'exceed_monthly_limit', 'months_info' => $monthsInfo ]; @@ -480,11 +468,13 @@ class TicketController extends Controller $totalAllocated = 0; if ($leaveDaysInfo) { - if ($leaveDaysInfo->ld_day_total > $month) { - $totalAllocated = $month; - } else { - $totalAllocated = $leaveDaysInfo->ld_day_total; - } + // if ($leaveDaysInfo->ld_day_total > $month) { + // $totalAllocated = $month; + // } else { + // $totalAllocated = $leaveDaysInfo->ld_day_total; + // } + $totalAllocated = $month; //(+ tạm để check) + // 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 { Log::warning("No LeaveDays record found for user ID: {$user->id}, year: {$year}. Assuming 0 allocated days."); } @@ -529,36 +519,6 @@ class TicketController extends Controller ->sum('categories.c_value'); } - private function insufficientLeaveDaysResponse($user, float $remainingDays, float $daysRequested): array - { - $daysNotEnough = $daysRequested - $remainingDays; - Log::warning("Insufficient leave balance for user ID: {$user->id}. Remaining: {$remainingDays}, Requested: {$daysRequested}"); - - return [ - 'success' => false, - 'message' => "Bạn không đủ ngày phép. Số ngày phép còn lại: {$remainingDays}, Số ngày yêu cầu: {$daysRequested}. Bạn có chấp nhận nộp: {$daysNotEnough} ngày không phép không?", - 'warning_type' => 'insufficient_leave_days', - 'month_data' => [] - ]; - } - - private function exceedMaxConsecutiveDaysResponse($user, float $daysRequested): array - { - $noLeavePermissionDays = $daysRequested - 3; - $message = "Bạn đã yêu cầu {$daysRequested} ngày nghỉ. Theo quy định, chỉ 3 ngày đầu là nghỉ có phép, {$noLeavePermissionDays} ngày còn lại sẽ là nghỉ không phép. Bạn có chấp nhận tiếp tục không?"; - Log::info("User ID: {$user->id} requested {$daysRequested} days which exceeds the 3-day limit. {$noLeavePermissionDays} days will be marked as leave without permission."); - - return [ - 'success' => true, - 'message' => $message, - 'require_acceptance' => true, - 'warning_type' => 'exceed_max_days', - 'max_leave_days' => 3, - 'days_with_permission' => 3, - 'days_without_permission' => $noLeavePermissionDays - ]; - } - public function deleteTicket(Request $request) { $rules = [ diff --git a/FRONTEND/src/pages/Tickets/Tickets.tsx b/FRONTEND/src/pages/Tickets/Tickets.tsx index e2e0054..bd0b13e 100755 --- a/FRONTEND/src/pages/Tickets/Tickets.tsx +++ b/FRONTEND/src/pages/Tickets/Tickets.tsx @@ -6,7 +6,7 @@ import { } from '@/api/Admin' import { DataTablePagination } from '@/components/DataTable/DataTable' import { Xdelete, create } from '@/rtk/helpers/CRUD' -import { get } from '@/rtk/helpers/apiService' +import { get, post } from '@/rtk/helpers/apiService' import { Badge, Box, @@ -26,6 +26,7 @@ import { IconTrash } from '@tabler/icons-react' import moment from 'moment' import { useEffect, useState } from 'react' import classes from './Tickets.module.css' +import { _NOTIFICATION_MESS } from '@/rtk/helpers/notificationMess' type TTickets = { id: number @@ -82,6 +83,11 @@ const Tickets = () => { const [dataTimeType, setDataTimeType] = useState([]) const [dataReason, setDataReason] = useState([]) + const [confirmModal, setConfirmModal] = useState(false) + const [confirmMessage, setConfirmMessage] = useState('') + const [confirmValues, setConfirmValues] = useState(null) + const [confirmLoading, setConfirmLoading] = useState(false) + const getListMasterByType = async (type: string) => { try { const params = { @@ -271,27 +277,61 @@ const Tickets = () => { } } - const handleCreate = async (values: TTickets) => { + const handleCreate = async (values: TTickets, confirm: boolean = false) => { try { - const res = await create( - addTicket, - { - // time_string: moment(values.time_string).format('YYYY-MM-DD HH:mm:ss'), - start_date: moment(values.start_date).format('YYYY-MM-DD'), - start_period: values.start_period, - end_date: moment(values.end_date).format('YYYY-MM-DD'), - end_period: values.end_period, - type: values.type, - reason: values.reason, - }, - getAllTickets, - ) - if (res === true) { + let params = { + // time_string: moment(values.time_string).format('YYYY-MM-DD HH:mm:ss'), + start_date: moment(values.start_date).format('YYYY-MM-DD'), + start_period: values.start_period, + end_date: moment(values.end_date).format('YYYY-MM-DD'), + end_period: values.end_period, + type: values.type, + reason: values.reason, + is_accept: confirm, + } + + let res = await post(addTicket, params) + + if (res.status) { + notifications.show({ + title: 'Success', + message: _NOTIFICATION_MESS.create_success, + color: 'green', + }) setAction('') form.reset() + + getAllTickets() + } + if (!res.status && res.errors) { + if (!res.data?.success && res.data?.message) { + //popup notification confirm or cancel + setConfirmMessage(res.data?.message) + setConfirmValues(values) + setConfirmModal(true) + } else { + notifications.show({ + title: 'Error', + message: ( +
+ {res.message ?? _NOTIFICATION_MESS.create_error} +
+ ), + color: 'red', + }) + } + } + console.log(res, 'res') + // return res.status + } catch (error: any) { + if (error.response.status === 422) { + const errorMess = error.response.data.message + notifications.show({ + title: 'Error', + message: errorMess, + color: 'red', + }) } - } catch (error) { - console.log(error) } } @@ -322,9 +362,7 @@ const Tickets = () => { return (
-

- Tickets -

+

Tickets

+ + + +
) } diff --git a/FRONTEND/src/rtk/helpers/CRUD.tsx b/FRONTEND/src/rtk/helpers/CRUD.tsx index a75fdc7..86205a9 100755 --- a/FRONTEND/src/rtk/helpers/CRUD.tsx +++ b/FRONTEND/src/rtk/helpers/CRUD.tsx @@ -36,7 +36,7 @@ export const create = async ( if (res.status === false) { notifications.show({ title: 'Error', - message: res.message ?? _NOTIFICATION_MESS.create_error, + message:
{res.message ?? _NOTIFICATION_MESS.create_error}
, color: 'red', }) }