ManagementSystem/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php

1614 lines
78 KiB
PHP

<?php
namespace Modules\Admin\app\Http\Controllers;
use App\Helper\Cache\CurrentMonthTimekeeping;
use App\Http\Controllers\Controller;
use App\Mail\ContactMail;
use App\Mail\TicketMail;
use App\Models\Notes;
use App\Traits\AnalyzeData;
use App\Traits\HasFilterRequest;
use App\Traits\HasOrderByRequest;
use App\Traits\HasSearchRequest;
use Carbon\Carbon;
use Carbon\CarbonPeriod;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Mail;
use Modules\Admin\app\Models\Admin;
use Modules\Admin\app\Models\Category;
use Modules\Admin\app\Models\Ticket;
use Modules\Admin\app\Models\Tracking;
use Illuminate\Support\Facades\Log;
use App\Models\LeaveDays;
use Illuminate\Http\JsonResponse;
use App\Models\Admin as UserModel;
class TicketController extends Controller
{
use HasOrderByRequest;
use HasFilterRequest;
use HasSearchRequest;
use AnalyzeData;
public function getByUserId(Request $request)
{
$tickets = new Ticket;
// Order by
$this->orderByRequest($tickets, $request);
// Filter
$this->filterRequest(
builder: $tickets,
request: $request,
filterKeys: [
'type' => self::F_TEXT,
'reason' => self::F_TEXT,
'updated_by' => self::F_TEXT,
'start_date' => [
'type' => self::F_THAN_EQ_DATETIME,
'column' => 'tickets.start_date'
],
'end_date' => [
'type' => self::F_THAN_EQ_DATETIME,
'column' => 'tickets.end_date'
],
]
);
$this->searchRequest(
builder: $tickets,
value: $request->get('search'),
fields: [
'users.name',
"start_date",
"startPeriod.c_name",
"end_date",
"endPeriod.c_name",
"typeReason.c_name",
'status',
"reason",
]
);
$responseData = array_merge(
$tickets
->join('users', 'tickets.user_id', '=', 'users.id')
->leftJoin("categories as startPeriod", function ($join) {
$join->on('start_period', '=', 'startPeriod.c_code');
$join->on('startPeriod.c_type', DB::raw("CONCAT('TIME_TYPE')"));
})
->leftJoin("categories as endPeriod", function ($join) {
$join->on('end_period', '=', 'endPeriod.c_code');
$join->on('endPeriod.c_type', DB::raw("CONCAT('TIME_TYPE')"));
})
->leftJoin("categories as typeReason", function ($join) {
$join->on('type', '=', 'typeReason.c_code');
$join->on('typeReason.c_type', DB::raw("CONCAT('REASON')"));
})
->leftJoin("categories as statusTickets", function ($join) {
$join->on('status', '=', 'statusTickets.c_code');
$join->on('statusTickets.c_type', DB::raw("CONCAT('STATUS_TICKETS')"));
})
->where('user_id', auth('admins')->user()->id)->orderBy('tickets.created_at', 'desc')
->select(
'users.name as user_name',
'users.email',
'tickets.*',
'startPeriod.c_name as startPeriodName',
'endPeriod.c_name as endPeriodName',
'typeReason.c_name as typeReasonName',
'statusTickets.c_name as statusTicketsName',
)
->paginate($request->get('per_page'))->toArray(),
['status' => true]
);
return response()->json($responseData);
}
public function getAll(Request $request)
{
$tickets = new Ticket;
// Order by
$this->orderByRequest($tickets, $request);
// Filter
$this->filterRequest(
builder: $tickets,
request: $request,
filterKeys: [
'type' => self::F_TEXT,
'reason' => self::F_TEXT,
'updated_by' => self::F_TEXT,
'start_date' => [
'type' => self::F_THAN_EQ_DATETIME,
'column' => 'tickets.start_date'
],
'end_date' => [
'type' => self::F_THAN_EQ_DATETIME,
'column' => 'tickets.end_date'
],
]
);
$this->searchRequest(
builder: $tickets,
value: $request->get('search'),
fields: [
'users.name',
"start_date",
"startPeriod.c_name",
"end_date",
"endPeriod.c_name",
"typeReason.c_name",
'status',
"reason",
"admin_note"
]
);
if ($request->typeReason != '') {
$tickets = $tickets->where('tickets.type', '=', $request->typeReason);
}
if ($request->statusFilter != '') {
$tickets = $tickets->where('tickets.status', '=', $request->statusFilter);
}
$responseData = array_merge(
$tickets
->join('users', 'tickets.user_id', '=', 'users.id')
->leftJoin("categories as startPeriod", function ($join) {
$join->on('start_period', '=', 'startPeriod.c_code');
$join->on('startPeriod.c_type', DB::raw("CONCAT('TIME_TYPE')"));
})
->leftJoin("categories as endPeriod", function ($join) {
$join->on('end_period', '=', 'endPeriod.c_code');
$join->on('endPeriod.c_type', DB::raw("CONCAT('TIME_TYPE')"));
})
->leftJoin("categories as typeReason", function ($join) {
$join->on('type', '=', 'typeReason.c_code');
$join->on('typeReason.c_type', DB::raw("CONCAT('REASON')"));
})
->leftJoin("categories as statusTickets", function ($join) {
$join->on('status', '=', 'statusTickets.c_code');
$join->on('statusTickets.c_type', DB::raw("CONCAT('STATUS_TICKETS')"));
})
->select(
'users.name as user_name',
'users.email',
'tickets.*',
'startPeriod.c_name as startPeriodName',
'endPeriod.c_name as endPeriodName',
'typeReason.c_name as typeReasonName',
'statusTickets.c_name as statusTicketsName',
)
->orderBy('tickets.created_at', 'desc')->paginate($request->get('per_page'))->toArray(),
['status' => true]
);
return response()->json($responseData);
}
public function createTicket(Request $request)
{
// Define validation rules
$rules = [
'start_date' => 'required|date',
'start_period' => 'required|string',
'end_date' => 'required|date|after_or_equal:start_date',
'end_period' => 'required|string',
'type' => 'required|string',
];
// Validate the request
$request->validate($rules);
// return $request;
// Get data from request
$startDate = $request->input('start_date');
$startPeriod = $request->input('start_period');
$endDate = $request->input('end_date');
$endPeriod = $request->input('end_period');
$type = $request->input('type');
$reason = $request->input('reason');
$isAccept = $request->input('is_accept') ?? false;
$user = auth('admins')->user(); // user create ticket
$start_date = Carbon::create($startDate)->setTimezone(env('TIME_ZONE'));
$end_date = Carbon::create($endDate)->setTimezone(env('TIME_ZONE'));
// Get mảng ngày nghỉ
$dataListPeriod = $this->getAllPeriodNew($start_date, $startPeriod, $end_date, $endPeriod);
if (empty($dataListPeriod)) {
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
$ticketsWaiting = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->whereIn('type', ['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));
}
}
$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
$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 = [];
$waitingPeriodStringsWFH = [];
$confirmedPeriodStrings = [];
foreach ($dataListPeriod as $period) {
if ($period['period'] == 'ALL') {
$periodStrings[] = $period['date'] . '_S';
$periodStrings[] = $period['date'] . '_C';
} else {
$periodStrings[] = $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'];
}
}
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) {
if ($period['period'] == 'ALL') {
$confirmedPeriodStrings[] = $period['date'] . '_S';
$confirmedPeriodStrings[] = $period['date'] . '_C';
} else {
$confirmedPeriodStrings[] = $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 đ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
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!');
}
// Tạo thông báo về tickets waiting nếu có
$waitingTicketsMessage = '';
if (!empty($dataListPeriodWaiting)) {
// Kiểm tra số dư ngày phép cho tickets waiting
$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
if ($ticketsWaiting->count() > 0) {
$waitingTicketsMessage .= ":\n";
foreach ($ticketsWaiting as $ticket) {
$startDateFormat = Carbon::parse($ticket->start_date)->format('d/m/Y');
$endDateFormat = Carbon::parse($ticket->end_date)->format('d/m/Y');
$waitingTicketsMessage .= "- " . $ticket->startPeriodName . " (" . $startDateFormat . ") - " .
$ticket->endPeriodName . " (" . $endDateFormat . ")\n";
}
}
}
$balanceCheckResultWaiting = $this->checkLeaveBalance($user, $dataListPeriodWaiting);
// dd($balanceCheckResultWaiting,$dataListPeriodWaiting,$user);
if ($balanceCheckResultWaiting['months_info']) {
$monthsInfoWaiting = $balanceCheckResultWaiting['months_info'];
if ($balanceCheckResultWaiting['success']) {
$waitingTicketsMessage .= "------------------------------------------------";
} else {
$waitingTicketsMessage .= $balanceCheckResultWaiting['message'] . "\n------------------------------------------------";
}
$balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod, $monthsInfoWaiting);
// dd($balanceCheckResult, $waitingTicketsMessage);
} else {
$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']) {
$finalMessage = $waitingTicketsMessage;
if (!empty($finalMessage)) {
$finalMessage .= "\n\n";
}
$finalMessage .= $balanceCheckResult['message'];
$balanceCheckResult['message'] = $finalMessage . "\n\nBạn có chấp nhận không?\n";
$balanceCheckResult['waitingTicketMessage'] = $waitingTicketsMessage;
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 ---
// Nếu đủ ngày phép (hoặc loại ticket không phải ONLEAVE), tiếp tục tạo ticket
$ticket = Ticket::create([
'start_date' => $start_date->toDateString(),
'start_period' => $startPeriod,
'end_date' => $end_date->toDateString(),
'end_period' => $endPeriod,
'type' => $type,
'status' => 'WAITING',
'reason' => $reason,
'user_id' => $user->id
]);
// Send notification email to admin (list)
$dataMasterStartPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $startPeriod);
$dataMasterEndPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $endPeriod);
$dataMasterType = CategoryController::getListMasterByCodeAndType("REASON", $type);
$formattedStartDate = Carbon::createFromFormat('Y-m-d', $startDate)->format('d/m/Y');
$formattedEndDate = Carbon::createFromFormat('Y-m-d', $endDate)->format('d/m/Y');
$admins = Admin::where('permission', 'like', '%admin%')->get();
foreach ($admins as $key => $value) {
$data = array(
"ticket_id" => $ticket->id,
"email_template" => "email.notification_tickets",
"email" => $user->email,
"admin_email" => $value->email,
"name" => $user->name,
"date" => optional($dataMasterStartPeriod)->c_name . " (" . $formattedStartDate . ") - " . optional($dataMasterEndPeriod)->c_name . " (" . $formattedEndDate . ")",
"type" => optional($dataMasterType)->c_name,
"note" => $reason,
"link" => "/tickets-management", //link đến page admin
"subject" => "[Ticket request] Ticket From " . $user->name
);
// Thêm kiểm tra null trước khi gửi mail
if ($dataMasterStartPeriod && $dataMasterEndPeriod && $dataMasterType) {
Mail::to($value->email)->send(new TicketMail($data));
} else {
Log::error("Missing category data for ticket ID: {$ticket->id}. Mail not sent.");
}
}
return response()->json(['data' => $ticket, 'status' => true]);
}
/**
* 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 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, ?array $dataListPeriod = null, ?array $monthsInfoWaiting = null, ?bool $isAccept = false): array
{
// Kiểm tra giới hạn nghỉ phép theo tháng
if (!empty($dataListPeriod)) {
return $this->checkMonthlyLeaveLimit($user, $dataListPeriod, $monthsInfoWaiting, $isAccept);
}
// Đủ điều kiện
return [
'success' => true,
'message' => null,
'months_info' => []
];
}
private function getTotalAllocatedDays($user, int $year): float
{
$leaveDaysInfo = LeaveDays::where('ld_user_id', $user->id)
->where('ld_year', $year)
->first();
$totalAllocated = 0;
if ($leaveDaysInfo) {
$totalAllocated = $leaveDaysInfo->ld_day_total + $leaveDaysInfo->ld_additional_day + $leaveDaysInfo->ld_special_leave_day;
} else {
Log::warning("No LeaveDays record found for user ID: {$user->id}, year: {$year}. Assuming 0 allocated days.");
}
return $totalAllocated;
}
private function getUsedLeaveDays($user, int $year): float
{
return Notes::join('categories', function ($join) {
$join->on('notes.n_time_type', '=', 'categories.c_code')
->where('categories.c_type', 'TIME_TYPE');
})
->where('n_user_id', $user->id)
->where('n_year', $year)
->where('n_reason', 'ONLEAVE')
->sum('categories.c_value');
}
//Tính tổng giới hạn ngày nghỉ có phép tối đa trong tháng
private function getMaxLeaveDaysPerMonth(): int
{
$limitLeaveMonth = Category::where('c_type', 'LIMIT_LEAVE_MONTH')->where('c_code', "LIMIT")->first();
if ($limitLeaveMonth) {
$maxDaysPerMonth = (int)$limitLeaveMonth->c_value;
} else {
$maxDaysPerMonth = 3; // default nếu k có setting
}
return $maxDaysPerMonth;
}
private function checkMonthlyLeaveLimit($user, array $dataListPeriod, ?array $monthsInfoWaiting = null, ?bool $isAccept = false): array
{
// Danh sách ngày nghỉ theo tháng
$requestMonths = $this->groupLeaveRequestsByMonth($dataListPeriod);
$monthsInfo = [];
$hasInsufficientDays = false;
$errorMessage = '';
$remainingDaysInMonthIsUsed = 0;
// Tổng giới hạn ngày nghỉ có phép tối đa trong tháng
$maxDaysPerMonth = $this->getMaxLeaveDaysPerMonth();
foreach ($requestMonths as $monthKey => $monthData) {
if ($monthsInfoWaiting) {
// dd($requestMonths, $monthsInfoWaiting);
foreach ($monthsInfoWaiting as $monthInfo) {
if ($monthInfo['month'] == $monthData['month'] && $monthInfo['year'] == $monthData['year']) {
$remainingDaysInMonthIsUsed += $monthInfo['remaining_days_in_month_remaining'];
// dd($remainingDaysInMonthIsUsed);
}
}
}
// Tổng số ngày nghỉ có phép trong tháng
$usedDaysInMonth = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'ONLEAVE');
// Tổng số ngày nghỉ không phép trong tháng
$usedDaysInMonthWithoutPay = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'LEAVE_WITHOUT_PAY');
// Tổng số ngày nghỉ trong tháng = tổng ngày nghỉ có phép + tổng ngày nghỉ không phép + tổng ngày yêu cầu
$totalDaysInMonth = $usedDaysInMonth + $usedDaysInMonthWithoutPay + $monthData['days_requested'];
// Tổng phép có trong tháng
$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
$totalLeaveDaysInMonthToMonth = $this->getTotalLeaveDaysInMonthToMonth($user, $monthData['year'], $monthData['month']);
//Ngày phép còn lại trong tháng
$remainingDaysInMonth = $totalLeaveDaysInMonth - $totalLeaveDaysInMonthToMonth;
$remainingDaysInMonthRemaining = $remainingDaysInMonth - $remainingDaysInMonthIsUsed;
// if ($monthsInfoWaiting) {
// dd(
// "Ngày phép còn lại trong tháng: " . $remainingDaysInMonthRemaining,
// "Ngày phép còn lại: " . $remainingDaysInMonth,
// "Ngày phép đã sử dụng: " . $remainingDaysInMonthIsUsed,
// "Ngày phép yêu cầu: " . $monthData['days_requested'],
// "Tổng ngày nghỉ trong tháng: " . $totalDaysInMonth,
// "Ngày phép đã sử dụng: " . $usedDaysInMonth,
// );
// }
$month_data_status = 'ok';
$days_will_use = 0;
$days_will_use_without_pay = 0;
// Xử lý các trường hợp thiếu ngày phép
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']}\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
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;
$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
if ($totalDaysInMonth > $maxDaysPerMonth) {
$daysWithoutPermission = $totalDaysInMonth - $maxDaysPerMonth;
$daysWillUse = $maxDaysPerMonth - $usedDaysInMonth; // số ngày phép sẽ sử dụng
$hasInsufficientDays = true;
$month_data_status = 'exceed_max_days';
$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 " . $daysWillUse . " ngày phép và {$daysWithoutPermission} ngày không phép.";
$errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage;
$days_will_use = $daysWillUse;
$days_will_use_without_pay = $daysWithoutPermission;
} else if ($monthData['days_requested'] + $remainingDaysInMonthIsUsed > $maxDaysPerMonth) {
if ($remainingDaysInMonthIsUsed > 0) {
$daysWillUse = $maxDaysPerMonth - $remainingDaysInMonthIsUsed; // số ngày phép sẽ sử dụng
$daysWithoutPermission = $monthData['days_requested'] - $daysWillUse;
$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 {$remainingDaysInMonthIsUsed} ngày phép, còn lại {$remainingDaysInMonthRemaining} ngày phép.\n - Bạn sẽ sử dụng " . $daysWillUse . " ngày phép và {$daysWithoutPermission} ngày không phép.";
} else {
$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.";
}
$hasInsufficientDays = true;
$month_data_status = 'exceed_max_days';
$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 đã 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' => $month_data_status, // mặc định là ok
];
// Thêm thông tin tháng vào mảng kết quả
$monthsInfo[] = $month_data;
}
// Trả về kết quả tổng hợp
if ($hasInsufficientDays) {
return [
'success' => false,
'message' => $errorMessage,
'warning_type' => 'exceed_monthly_limit',
'months_info' => $monthsInfo
];
}
return [
'success' => true,
'message' => "Đủ ngày phép cho yêu cầu.",
'months_info' => $monthsInfo
];
}
//Tính tổng số ngày nghỉ có phép đến tháng hiện tại
private function getTotalLeaveDaysInMonthToMonth($user, int $year, int $month): float
{
return Notes::join('categories', function ($join) {
$join->on('notes.n_time_type', '=', 'categories.c_code')
->where('categories.c_type', 'TIME_TYPE');
})
->where('n_user_id', $user->id)
->where('n_year', $year)
->where('n_month', "<=", $month)
->where('n_reason', 'ONLEAVE')
->sum('categories.c_value');
}
private function getTotalLeaveDaysInMonth($user, int $year, int $month, ?bool $isAccept = false): float
{
$leaveDaysInfo = LeaveDays::where('ld_user_id', $user->id)
->where('ld_year', $year)
->first();
$totalAllocated = 0;
if ($leaveDaysInfo) {
// if ($leaveDaysInfo->ld_day_total > $month) {
// $totalAllocated = $month;
// } else {
$totalAllocated = $leaveDaysInfo->ld_day_total;
// }
// 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
} else {
Log::warning("No LeaveDays record found for user ID: {$user->id}, year: {$year}. Assuming 0 allocated days.");
}
$totalAllocated = $totalAllocated + $leaveDaysInfo->ld_additional_day + $leaveDaysInfo->ld_special_leave_day;
return $totalAllocated;
}
private function groupLeaveRequestsByMonth(array $dataListPeriod): array
{
$requestMonths = [];
foreach ($dataListPeriod as $periodData) {
$date = Carbon::parse($periodData['date']);
$monthKey = $date->format('Y-m'); // YYYY-MM
if (!isset($requestMonths[$monthKey])) {
$requestMonths[$monthKey] = [
'year' => $date->year,
'month' => $date->month,
'days_requested' => 0,
'days_used' => 0
];
}
// Tính số ngày yêu cầu trong tháng
$dayValue = ($periodData['period'] === 'ALL') ? 1.0 : 0.5;
$requestMonths[$monthKey]['days_requested'] += $dayValue;
}
return $requestMonths;
}
private function getUsedLeaveDaysInMonth($user, int $year, int $month, string $reason): float
{
return Notes::join('categories', function ($join) {
$join->on('notes.n_time_type', '=', 'categories.c_code')
->where('categories.c_type', 'TIME_TYPE');
})
->where('n_user_id', $user->id)
->where('n_year', $year)
->where('n_month', $month)
->where('n_reason', $reason)
->sum('categories.c_value');
}
public function deleteTicket(Request $request)
{
$rules = [
'ticket_id' => 'required'
];
// Validate the request
$request->validate($rules);
$user = auth('admins')->user();
$ticket_id = $request->input('ticket_id');
$ticket = Ticket::find($ticket_id);
if ($ticket) {
// $user->id == user_id of ticket ---> delete
if ($ticket->user_id == $user->id) {
$ticket->delete();
return response()->json(['message' => 'delete success', 'status' => true]);
} else {
return response()->json(['message' => 'You are committing an act of vandalism', 'status' => false]);
}
}
return response()->json(['message' => 'Delete fail', 'status' => false]);
}
public function handleTicket(Request $request)
{
$rules = [
'ticket_id' => 'required',
'action' => 'required',
// 'admin_note' => 'required'
];
// Validate the request
$request->validate($rules);
$ticket_id = $request->input('ticket_id');
$admin_note = $request->input('admin_note');
$action = $request->input('action'); // 'confirm' or 'refuse'
$admin = auth('admins')->user();
$ticket = Ticket::find($ticket_id);
if (!$ticket || $ticket->status !== "WAITING") {
return response()->json(['message' => "Ticket not found", 'status' => false]);
}
// Confirm
// Update updated_by and admin_note in tickets table
// Refuse
// Update updated_by and admin_note in tickets table
// Send notification email to users
$startDate = $ticket->start_date; //Start day
$startPeriod = $ticket->start_period; //The session begins
$endDate = $ticket->end_date; //End date
$endPeriod = $ticket->end_period; //Session ends
$type = $ticket->type;
$dataMasterStartPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $startPeriod);
$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 ($onleave == null || $leaveWithoutPay == null) {
return response()->json(['message' => "Data reason notes not found", 'status' => false]);
}
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, null, true);
// 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
$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'));
//Default: ALL
$start = $date->copy()->setTime(7, 30, 0);
$end = $date->copy()->setTime(17, 0, 0);
if ($type == 'S') {
$end = $date->copy()->setTime(11, 30, 0);
} else if ($type == 'C') {
$start = $date->copy()->setTime(11, 30, 0);
}
Tracking::insert([
[
'name' => $user->name,
'user_id' => $user->id,
'status' => 'check in',
'time_string' => $start->format('Y-m-d H:i:s'),
'created_at' => $start->setTimezone('UTC')
],
[
'name' => $user->name,
'user_id' => $user->id,
'status' => 'check out',
'time_string' => $end->format('Y-m-d H:i:s'),
'created_at' => $end->setTimezone('UTC')
]
]);
//WFH - end tracking
}
}
$ticket['updated_by'] = $admin->name;
$ticket['admin_note'] = $admin_note;
$ticket['status'] = 'CONFIRMED';
$ticket->save();
$this->createOrUpdateRecordForCurrentMonth($month, $year);
// Send notification email to users
$data = array(
"email_template" => "email.notification_tickets_user",
"user_name" => $user->name,
"email" => $user->email,
"name" => $admin->name, //name admin duyệt
"date" => $dataMasterStartPeriod->c_name . " (" . $formattedStartDate . ") - " . $dataMasterEndPeriod->c_name . " (" . $formattedEndDate . ")",
"type" => $dataMasterType->c_name,
"note" => $ticket->reason,
"admin_note" => $admin_note,
"link" => "/tickets", //link đến page admin
"status" => "confirmed",
"subject" => "[Ticket response] Ticket From " . $admin->name
);
Mail::to($user->email)->send(new TicketMail($data));
return response()->json(['message' => "confirmed", 'status' => true]);
}
if ($action == "refuse") {
$ticket['updated_by'] = $admin->name;
$ticket['admin_note'] = $admin_note;
$ticket['status'] = 'REFUSED';
$ticket->save();
$data = array(
"email_template" => "email.notification_tickets_user",
"user_name" => $user->name,
"email" => $user->email,
"name" => $admin->name, //name admin duyệt
"date" => $dataMasterStartPeriod->c_name . " (" . $formattedStartDate . ") - " . $dataMasterEndPeriod->c_name . " (" . $formattedEndDate . ")",
"type" => $dataMasterType->c_name,
"note" => $ticket->reason,
"admin_note" => $admin_note,
"link" => "/tickets", //link đến page admin
"status" => "refused",
"subject" => "[Ticket response] Ticket From " . $admin->name
);
Mail::to($user->email)->send(new TicketMail($data));
return response()->json(['message' => "refused", 'status' => true]);
}
return response()->json(['message' => "failed", 'status' => false]);
}
// Handle Logic same as HandleTicket, but need email to identify admin and redirect to Management page
public function handleTicketEmail(Request $request)
{
$rules = [
'ticket_id' => 'required',
'action' => 'required',
'admin_email' => 'required' // Need Admin Email
];
// Validate the request
$request->validate($rules);
$ticket_id = $request->input('ticket_id');
$admin_note = $request->input('admin_note');
$admin_email = $request->input('admin_email');
$action = $request->input('action'); // 'confirm' or 'refuse'
$admin = Admin::where('email', $admin_email)->first(); // Get admin by email not token
$ticket = Ticket::find($ticket_id);
if (!$ticket || $ticket->status !== "WAITING") {
// No ticket found or already confirmed or refused
return redirect()->to(config('app.client_url') . '/tickets-management');
}
// Send notification email to users
$startDate = $ticket->start_date; //Start day
$startPeriod = $ticket->start_period; //The session begins
$endDate = $ticket->end_date; //End date
$endPeriod = $ticket->end_period; //Session ends
$type = $ticket->type;
$dataMasterStartPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $startPeriod);
$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 ($onleave == null || $leaveWithoutPay == null) {
// Data reason notes not found
return redirect()->to(config('app.client_url') . '/tickets-management');
}
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'));
//Default: ALL
$start = $date->copy()->setTime(7, 30, 0);
$end = $date->copy()->setTime(17, 0, 0);
if ($type == 'S') {
$end = $date->copy()->setTime(11, 30, 0);
} else if ($type == 'C') {
$start = $date->copy()->setTime(11, 30, 0);
}
Tracking::insert([
[
'name' => $user->name,
'user_id' => $user->id,
'status' => 'check in',
'time_string' => $start->format('Y-m-d H:i:s'),
'created_at' => $start->setTimezone('UTC')
],
[
'name' => $user->name,
'user_id' => $user->id,
'status' => 'check out',
'time_string' => $end->format('Y-m-d H:i:s'),
'created_at' => $end->setTimezone('UTC')
]
]);
//WFH - end tracking
}
}
$ticket['updated_by'] = $admin->name;
$ticket['admin_note'] = $admin_note;
$ticket['status'] = 'CONFIRMED';
$ticket->save();
$this->createOrUpdateRecordForCurrentMonth($month, $year);
// Send notification email to users
$data = array(
"email_template" => "email.notification_tickets_user",
"user_name" => $user->name,
"email" => $user->email,
"name" => $admin->name, //name admin duyệt
"date" => $dataMasterStartPeriod->c_name . " (" . $formattedStartDate . ") - " . $dataMasterEndPeriod->c_name . " (" . $formattedEndDate . ")",
"type" => $dataMasterType->c_name,
"note" => $ticket->reason,
"admin_note" => $admin_note,
"link" => "/tickets", //link đến page admin
"status" => "confirmed",
"subject" => "[Ticket response] Ticket From " . $admin->name
);
Mail::to($user->email)->send(new TicketMail($data));
// Confirm Success
return redirect()->to(config('app.client_url') . '/tickets-management');
}
if ($action == "refuse") {
$ticket['updated_by'] = $admin->name;
$ticket['admin_note'] = $admin_note;
$ticket['status'] = 'REFUSED';
$ticket->save();
$data = array(
"email_template" => "email.notification_tickets_user",
"user_name" => $user->name,
"email" => $user->email,
"name" => $admin->name, //name admin duyệt
"date" => $dataMasterStartPeriod->c_name . " (" . $formattedStartDate . ") - " . $dataMasterEndPeriod->c_name . " (" . $formattedEndDate . ")",
"type" => $dataMasterType->c_name,
"note" => $ticket->reason,
"admin_note" => $admin_note,
"link" => "/tickets", //link đến page admin
"status" => "refused",
"subject" => "[Ticket response] Ticket From " . $admin->name
);
Mail::to($user->email)->send(new TicketMail($data));
// Refuse Success
return redirect()->to(config('app.client_url') . '/tickets-management');
}
// Failed
return redirect()->to(config('app.client_url') . '/tickets-management');
}
private function getAllPeriodNew($startDate, $startPeriod, $endDate, $endPeriod)
{
// Đảm bảo $startDate và $endDate là đối tượng Carbon
if (!($startDate instanceof Carbon)) {
$startDate = Carbon::parse($startDate);
}
if (!($endDate instanceof Carbon)) {
$endDate = Carbon::parse($endDate);
}
// Create an array to contain the results
$results = [];
// Use CarbonPeriod to create a period from the start date to the end date
$period = CarbonPeriod::create($startDate, $endDate);
$time_type = Category::where('c_type', 'TIME_TYPE')->get()->keyBy('c_code');
$morning = $time_type->get('S');
$afternoon = $time_type->get('C');
$all_day = $time_type->get('ALL');
// Get all Saturday work schedules and sort them by date in descending order
$saturday_work_schedules = Category::where('c_type', 'SATURDAY_WORK_SCHEDULE')
->get()
->sortByDesc(function ($item) {
// 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();
$latestScheduleDate = Carbon::createFromFormat('d-m-Y', $latest_schedule->c_code);
if (!$morning || !$afternoon || !$all_day) {
// Handle error: TIME_TYPE categories not found
Log::error("TIME_TYPE categories (S, C, ALL) not found in database.");
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 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);
// echo $date->toDateString() . ' - ' . ($isSaturdayWorkDay ? 'Làm việc' : 'Nghỉ') . "<br>";
}
if ($isSaturdayWorkDay) {
$results[] = ['date' => $date->toDateString(), 'period' => "S"];
}
continue;
}
// Skip Sundays entirely
else if ($date->dayOfWeek === Carbon::SUNDAY) {
continue;
}
}
if ($date->isSameDay($startDate)) {
//If the start date is morning, add afternoon
if ($startDate->isSameDay($endDate)) { // Nghỉ trong cùng 1 ngày
if ($startPeriod == $endPeriod) { // Cùng 1 buổi (S hoặc C)
$results[] = ['date' => $date->toDateString(), 'period' => $startPeriod];
} else { // Khác buổi (S đến C) -> cả ngày
$results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
}
} else { // Ngày bắt đầu khác ngày kết thúc
if ($startPeriod == $morning->c_code) { // Bắt đầu từ sáng -> tính cả ngày
$results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
} else { // Bắt đầu từ chiều -> tính buổi chiều
$results[] = ['date' => $date->toDateString(), 'period' => $startPeriod]; // Là $afternoon->c_code
}
}
} elseif ($date->isSameDay($endDate)) { // Ngày kết thúc (khác ngày bắt đầu)
if ($endPeriod == $afternoon->c_code) { // Kết thúc vào buổi chiều -> tính cả ngày
$results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
} else { // Kết thúc vào buổi sáng -> tính buổi sáng
$results[] = ['date' => $date->toDateString(), 'period' => $endPeriod]; // Là $morning->c_code
}
} else { // Những ngày ở giữa
$results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
}
}
// Returns results
return $results;
}
private function getAllPeriod($startDate, $startPeriod, $endDate, $endPeriod)
{
//Create an array to contain the results
$results = [];
//Use CarbonPeriod to create a period from the start date to the end date
$period = CarbonPeriod::create($startDate, $endDate);
$time_type = Category::where('c_type', 'TIME_TYPE')->get();
$morning = null;
$afternoon = null;
$all_day = null;
foreach ($time_type as $item) {
switch ($item->c_code) {
case 'S':
$morning = $item;
break;
case 'C':
$afternoon = $item;
break;
case 'ALL':
$all_day = $item;
break;
}
}
foreach ($period as $date) {
//If it is the start date
if ($date->isSameDay($startDate)) {
//Add start session
//If the start date is morning, add afternoon
if ($startDate == $endDate) {
if ($startPeriod == $endPeriod) {
$results[] = ['date' => $date->toDateString(), 'period' => $startPeriod];
} else {
$results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
}
} else {
if ($startPeriod == $morning->c_code) {
$results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
} else {
$results[] = ['date' => $date->toDateString(), 'period' => $startPeriod];
}
}
} elseif ($date->isSameDay($endDate)) {
//If it is the end date
//If the end session is afternoon, add morning first
if ($endPeriod == $afternoon->c_code) {
$results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
} else {
$results[] = ['date' => $date->toDateString(), 'period' => $endPeriod];
}
} else {
//Add both sessions for days between intervals
$results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
// $results[] = ['date' => $date->toDateString(), 'period' => 'afternoon'];
}
}
//Returns results
return $results;
}
/**
* Tính tổng số ngày nghỉ từ mảng các khoảng thời gian.
* 'ALL' = 1 ngày, 'S'/'C' = 0.5 ngày.
*
* @param array $dataListPeriod Mảng các khoảng thời gian nghỉ [['date' => 'Y-m-d', 'period' => 'ALL|S|C'], ...]
* @return float Tổng số ngày nghỉ
*/
private function calculateTotalLeaveDays(array $dataListPeriod): float
{
$totalDays = 0.0;
foreach ($dataListPeriod as $periodData) {
if (isset($periodData['period'])) {
switch ($periodData['period']) {
case 'ALL':
$totalDays += 1.0;
break;
case 'S': // Buổi sáng
case 'C': // Buổi chiều
$totalDays += 0.5;
break;
// Có thể thêm default case để xử lý lỗi nếu cần
}
}
}
return $totalDays;
}
}