Hiệu chỉnh add ticket

This commit is contained in:
Truong Vo 2025-04-25 10:29:35 +07:00
parent ddcb78ef98
commit 44fa6b55f7
3 changed files with 146 additions and 98 deletions

View File

@ -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ố 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 = [

View File

@ -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<DataTimeType[]>([])
const [dataReason, setDataReason] = useState<DataReason[]>([])
const [confirmModal, setConfirmModal] = useState(false)
const [confirmMessage, setConfirmMessage] = useState('')
const [confirmValues, setConfirmValues] = useState<TTickets | null>(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: (
<div style={{ whiteSpace: 'pre-line' }}>
{res.message ?? _NOTIFICATION_MESS.create_error}
</div>
),
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 (
<div>
<div className={classes.title}>
<h3>
Tickets
</h3>
<h3>Tickets</h3>
<Button
m={5}
onClick={() => {
@ -490,6 +528,56 @@ const Tickets = () => {
</Group>
</Text>
</Dialog>
{/* Confirm Modal */}
<Modal
opened={confirmModal}
onClose={() => !confirmLoading && setConfirmModal(false)}
title={
<Text fw={700} fz="lg">
Warning
</Text>
}
centered
closeOnClickOutside={!confirmLoading}
closeOnEscape={!confirmLoading}
>
<Box p="md">
<Text style={{ whiteSpace: 'pre-line' }} mb={20}>
{confirmMessage}
</Text>
<Group justify="center">
<Button
color="green"
loading={confirmLoading}
onClick={async () => {
if (confirmValues) {
try {
setConfirmLoading(true)
action === 'add' && (await handleCreate(confirmValues, true))
setConfirmLoading(false)
setConfirmModal(false)
} catch (error) {
setConfirmLoading(false)
console.error(error)
}
}
}}
>
Confirm
</Button>
<Button
color="red"
disabled={confirmLoading}
onClick={() => {
setConfirmModal(false)
}}
>
Cancel
</Button>
</Group>
</Box>
</Modal>
</div>
)
}

View File

@ -36,7 +36,7 @@ export const create = async (
if (res.status === false) {
notifications.show({
title: 'Error',
message: res.message ?? _NOTIFICATION_MESS.create_error,
message: <div style={{ whiteSpace: 'pre-line' }}>{res.message ?? _NOTIFICATION_MESS.create_error}</div>,
color: 'red',
})
}