diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/CategoryController.php b/BACKEND/Modules/Admin/app/Http/Controllers/CategoryController.php
index 58c767c..0150d30 100644
--- a/BACKEND/Modules/Admin/app/Http/Controllers/CategoryController.php
+++ b/BACKEND/Modules/Admin/app/Http/Controllers/CategoryController.php
@@ -14,7 +14,7 @@ class CategoryController extends Controller
* @param Request $request The HTTP request object.
* @return \Illuminate\Http\JsonResponse The JSON response containing the list of master data.
*/
- public function getListMaster(Request $request)
+ public static function getListMaster(Request $request)
{
$data = Category::where('c_type', '=', $request->type)->where('c_active', '=', 1)->select('id', 'c_code', 'c_name', 'c_value', 'c_type')->get();
return AbstractController::ResultSuccess($data);
@@ -24,4 +24,9 @@ class CategoryController extends Controller
$data = Category::where('c_type', '=', $type)->where('c_code', '=', $code)->where('c_active', '=', 1)->select('id', 'c_code', 'c_name', 'c_value', 'c_type')->first();
return $data;
}
+ public static function getListMasterByType($type)
+ {
+ $data = Category::where('c_type', '=', $type)->where('c_active', '=', 1)->select('id', 'c_code', 'c_name', 'c_value', 'c_type')->get();
+ return $data;
+ }
}
diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/LeaveManagementController.php b/BACKEND/Modules/Admin/app/Http/Controllers/LeaveManagementController.php
index 477b056..a8b1863 100644
--- a/BACKEND/Modules/Admin/app/Http/Controllers/LeaveManagementController.php
+++ b/BACKEND/Modules/Admin/app/Http/Controllers/LeaveManagementController.php
@@ -35,7 +35,7 @@ class LeaveManagementController extends Controller
})
->leftJoin("categories as reason", function ($join) {
$join->on('n_reason', '=', 'reason.c_code');
- $join->on('reason.c_type', DB::raw("CONCAT('REASON')"));
+ $join->on('reason.c_type', DB::raw("CONCAT('REASON_NOTES')"));
})
->select(
DB::raw('notes.n_user_id as n_user_id'),
@@ -44,13 +44,14 @@ class LeaveManagementController extends Controller
DB::raw('notes.n_month as month'),
DB::raw('categories.c_value as leave_days'),
DB::raw('notes.n_day as day'),
+ DB::raw('notes.n_reason as reason_code'),
'reason.c_name as reason_name',
'categories.c_name as time_type_name',
// DB::raw('SUM(categories.c_value) as leave_days')
)
// ->where('notes.n_user_id', "1")
->where('notes.n_year', $year)
- ->where('notes.n_reason', 'ONLEAVE')
+ ->whereIn('notes.n_reason', ['ONLEAVE', 'LEAVE_WITHOUT_PAY', 'TEMPORARY_ONLEAVE'])
// ->groupBy("notes.n_user_id")
->orderBy('notes.n_month')
->orderBy('notes.n_day')
@@ -59,7 +60,7 @@ class LeaveManagementController extends Controller
return [
"day" => $item->day,
"n_user_id" => $item->n_user_id,
- // "time_type" => $item->time_type,
+ "reason_code" => $item->reason_code,
"reason_name" => $item->reason_name,
"time_type_name" => $item->time_type_name,
"month" => $item->month,
@@ -105,9 +106,10 @@ class LeaveManagementController extends Controller
'leaveDay' => [
'id' => $item->id,
'ld_user_id' => $item->ld_user_id,
- 'ld_day' => $item->ld_day,
+ 'ld_day_total' => $item->ld_day_total,
'ld_year' => $item->ld_year,
- 'ld_date_additional' => $item->ld_date_additional,
+ 'ld_additional_day' => $item->ld_additional_day,
+ 'ld_special_leave_day' => $item->ld_special_leave_day,
'ld_note' => $item->ld_note,
'created_at' => $item->created_at,
'updated_at' => $item->updated_at,
@@ -133,8 +135,9 @@ class LeaveManagementController extends Controller
$validatedData = $request->all();
$leaveDays = LeaveDays::find($validatedData['id']);
- $leaveDays->ld_day = $validatedData['totalLeave'];
- $leaveDays->ld_date_additional = $validatedData['dayAdditional']; // Assuming you have this field to store additional days
+ $leaveDays->ld_day_total = $validatedData['totalLeave'];
+ $leaveDays->ld_additional_day = $validatedData['dayAdditional'];
+ $leaveDays->ld_special_leave_day = $validatedData['specialLeave'];
$leaveDays->ld_note = $validatedData['note'];
$leaveDays->save();
@@ -152,7 +155,7 @@ class LeaveManagementController extends Controller
}
// Lọc chỉ lấy user có permission bao gồm staff
- $staffData = $leaveDays->filter(function($user) {
+ $staffData = $leaveDays->filter(function ($user) {
return isset($user['user']['permission']) && strpos($user['user']['permission'], 'staff') !== false;
});
diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php
index 1e3e27b..2df8e81 100644
--- a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php
+++ b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php
@@ -2,9 +2,7 @@
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;
@@ -20,6 +18,9 @@ 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 App\Models\Admin as UserModel;
class TicketController extends Controller
{
@@ -101,7 +102,6 @@ class TicketController extends Controller
->paginate($request->get('per_page'))->toArray(),
['status' => true]
);
-
return response()->json($responseData);
}
@@ -191,7 +191,7 @@ class TicketController extends Controller
public function createTicket(Request $request)
{
- // Define validation rules
+ // Validate input
$rules = [
'start_date' => 'required|date',
'start_period' => 'required|string',
@@ -199,34 +199,146 @@ class TicketController extends Controller
'end_period' => 'required|string',
'type' => 'required|string',
];
-
- // Validate the request
$request->validate($rules);
- // return $request;
- //Get data from request
- $startDate = $request->input('start_date'); //Start day
- $startPeriod = $request->input('start_period'); //The session begins
- $endDate = $request->input('end_date'); //End date
- $endPeriod = $request->input('end_period'); //Session ends
+ // Get input data
+ $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');
- $user = auth('admins')->user(); // user create ticket
-
- // return $user;
-
- $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');
-
+ $isAccept = $request->input('is_accept') ?? false;
$user = auth('admins')->user();
+
+ $start_date = Carbon::create($startDate)->setTimezone(env('TIME_ZONE'));
+ $end_date = Carbon::create($endDate)->setTimezone(env('TIME_ZONE'));
+
+ $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ệ.');
+ }
+
+ // Lấy các ticket của user trong thời gian request
+ $userTickets = Ticket::where('user_id', $user->id)
+ ->whereIn('status', ['WAITING', 'CONFIRMED'])
+ ->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();
+ $userTicketListPeriod = [];
+ if ($userTickets->count() > 0) {
+ foreach ($userTickets as $ticket) {
+ $userTicketListPeriod = array_merge($userTicketListPeriod, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period));
+ }
+ }
+
+ // Kiểm tra ticket tạo có trùng thời gian với các ticket cũ
+ $periodStrings = [];
+ $userTicketPeriodStrings = [];
+ foreach ($dataListPeriod as $period) {
+ if ($period['period'] == 'ALL') {
+ $periodStrings[] = $period['date'] . '_S';
+ $periodStrings[] = $period['date'] . '_C';
+ continue;
+ }
+
+ $periodStrings[] = $period['date'] . '_' . $period['period'];
+ }
+ foreach ($userTicketListPeriod as $period) {
+ if ($period['period'] == 'ALL') {
+ $userTicketPeriodStrings[] = $period['date'] . '_S';
+ $userTicketPeriodStrings[] = $period['date'] . '_C';
+ continue;
+ }
+
+ $userTicketPeriodStrings[] = $period['date'] . '_' . $period['period'];
+ }
+
+ if (count(array_intersect($periodStrings, $userTicketPeriodStrings)) > 0) {
+ return AbstractController::ResultError('Đã có ticket được tạo trong thời gian này, không thể tạo ticket mới!');
+ }
+
+ // Kiểm tra khi type = ONLEAVE (nghỉ phép)
+ if ($type === 'ONLEAVE' && !$isAccept) {
+ // Lấy tickets nghỉ phép đang ở trạng thái WAITING
+ $ticketsWaiting = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->where('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));
+ }
+ }
+
+ // 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à chưa 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);
+ }
+ }
+ // --- Kết thúc kiểm tra ---
+
+ // Tạo ticket mới khi:
+ // - Ticket được tạo không trùng ngày
+ // - User có đủ phép
+ // - User không đủ phép nhưng vẫn đồng ý tạo
$ticket = Ticket::create([
- 'start_date' => Carbon::create($startDate)->setTimezone(env('TIME_ZONE')),
+ 'start_date' => $start_date->toDateString(),
'start_period' => $startPeriod,
- 'end_date' => Carbon::create($endDate)->setTimezone(env('TIME_ZONE')),
+ 'end_date' => $end_date->toDateString(),
'end_period' => $endPeriod,
'type' => $type,
'status' => 'WAITING',
@@ -235,24 +347,479 @@ class TicketController extends Controller
]);
// 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" => $dataMasterStartPeriod->c_name . " (" . $formattedStartDate . ") - " . $dataMasterEndPeriod->c_name . " (" . $formattedEndDate . ")",
- "type" => $dataMasterType->c_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
);
- Mail::to($value->email)->send(new TicketMail($data));
+ // Thêm kiểm tra null trước khi gửi mail
+ if ($dataMasterStartPeriod && $dataMasterEndPeriod && $dataMasterType) {
+ Mail::to($value->email)->queue(new TicketMail($data));
+ } else {
+ Log::error("Missing category data for ticket ID: {$ticket->id}. Mail not sent.");
+ }
}
return response()->json(['data' => $ticket, 'status' => true]);
}
+ public function updateTicket(Request $request)
+ {
+ // Validate input
+ $rules = [
+ 'ticket_id' => 'required|exists:tickets,id',
+ 'status' => 'required|string|in:CONFIRMED,REFUSED'
+ ];
+ $request->validate($rules);
+
+ $ticket = Ticket::find($request->input('ticket_id'));
+ if (!$ticket) {
+ return AbstractController::ResultError("Ticket not found.");
+ }
+
+ $admin = auth('admins')->user();
+ // Delete related note, if status change to Refuse
+ if ($request->status == "REFUSED") {
+ $ticket->status = "REFUSED";
+
+ // Handle send mail
+ $dataMasterStartPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $ticket->start_period);
+ $dataMasterEndPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $ticket->end_period);
+ $dataMasterType = CategoryController::getListMasterByCodeAndType("REASON", $ticket->type);
+ $formattedStartDate = Carbon::createFromFormat('Y-m-d', $ticket->start_date)->format('d/m/Y');
+ $formattedEndDate = Carbon::createFromFormat('Y-m-d', $ticket->end_date)->format('d/m/Y');
+
+ $user = Admin::find($ticket->user_id);
+
+ $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" => $ticket->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));
+
+ Notes::where('ticket_id', $ticket->id)->delete();
+ }
+
+ $ticket->updated_by = $admin->name;
+ $ticket->admin_note = $request->admin_note;
+
+ // Clear Timekeeping cache
+ $ticket->save();
+
+ $this->createOrUpdateRecordForCurrentMonth(Carbon::parse($ticket->start_date)->month, Carbon::parse($ticket->start_date)->year);
+ $this->createOrUpdateRecordForCurrentMonth(Carbon::parse($ticket->end_date)->month, Carbon::parse($ticket->end_date)->year);
+
+ return AbstractController::ResultSuccess($ticket, "Ticket updated successfully!");
+ }
+
+ /**
+ * 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();
+ $monthIndex = 0;
+ $onleaveTmp = 0; // Ngày phép trừ tạm (tính phép cho nhiều tháng)
+
+ foreach ($requestMonths as $monthData) {
+ if ($monthsInfoWaiting) {
+ foreach ($monthsInfoWaiting as $monthInfo) {
+ if ($monthInfo['month'] == $monthData['month'] && $monthInfo['year'] == $monthData['year']) {
+ $remainingDaysInMonthIsUsed += $monthInfo['remaining_days_in_month_remaining'];
+ }
+ }
+ }
+ // Số ngày nghỉ trong tháng
+ $onleaveDaysInMonth = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'ONLEAVE'); // Có phép
+ $nopayDaysInMonth = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'LEAVE_WITHOUT_PAY'); // Không phép
+ $usedDaysInMonth = $onleaveDaysInMonth + $nopayDaysInMonth; // Tổng
+
+ // Tổng ngày nghỉ sẽ dùng trong tháng
+ $willUsedDaysInMonth = $usedDaysInMonth + $monthData['days_requested'];
+
+ // Ngày phép
+ $onleaveDaysTotal = $this->getTotalLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], $isAccept); // Tổng phép của user
+ $usedOnleaveDaysTotal = $this->getTotalLeaveDaysInMonthToMonth($user, $monthData['year'], $monthData['month']); // Phép đã dùng
+ $remainingOnleaveDays = $onleaveDaysTotal - $usedOnleaveDaysTotal - $onleaveTmp; // Phép còn lại
+
+ // Tổng ngày phép còn lại trong tháng
+ $remainingOnleaveDaysInMonth = $remainingOnleaveDays - $remainingDaysInMonthIsUsed;
+
+ // Log::debug(
+ // "📊 Thống kê ngày phép:\n" .
+ // " - Tháng: {$monthData['month']}\n" .
+ // " - Tổng ngày nghỉ có phép trong tháng: $onleaveDaysInMonth\n" .
+ // " - Tổng ngày nghỉ không phép trong tháng: $nopayDaysInMonth\n" .
+ // " - Tổng ngày nghỉ đã dùng trong tháng: $usedDaysInMonth\n" .
+ // " - Tổng ngày nghỉ sẽ dùng trong tháng: $willUsedDaysInMonth\n" .
+ // " - Tổng ngày phép: $onleaveDaysTotal\n" .
+ // " - Tổng ngày phép đã nghỉ: $usedOnleaveDaysTotal\n" .
+ // " - Tổng ngày phép còn lại: $remainingOnleaveDays\n" .
+ // " - Tổng ngày phép còn lại trong tháng: $remainingOnleaveDaysInMonth\n"
+ // );
+
+ $month_data_status = 'ok';
+ $onleave_days_will_use = 0; // Ngày phép sẽ dùng trong tháng
+ $nopay_days_will_use = 0; // Ngày ko phép sẽ dùng trong tháng
+
+ // Ngày phép còn lại <= 0 (Hết phép) hoặc là nhân viên chưa chính thức
+ if ($remainingOnleaveDaysInMonth <= 0 || !$user->is_permanent) {
+ $hasInsufficientDays = true;
+ $month_data_status = 'no_days_left';
+ $onleave_days_will_use = 0;
+ $nopay_days_will_use = $monthData['days_requested'];
+
+ // Message cảnh báo nghỉ ko phép
+ $monthMessage = $this->buildMonthlyLeaveMessage(
+ $monthIndex,
+ $maxDaysPerMonth,
+ $monthData,
+ $remainingOnleaveDaysInMonth,
+ $onleaveDaysInMonth,
+ $nopayDaysInMonth,
+ $onleave_days_will_use,
+ $nopay_days_will_use
+ );
+ $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage;
+
+ Log::debug("--- Hết phép trong tháng ---", [
+ "Phep" => $onleave_days_will_use,
+ "Khong Phep" => $nopay_days_will_use
+ ]);
+ }
+
+ // Ngày phép còn lại < ngày yêu cầu (Không đủ phép)
+ else if ($remainingOnleaveDaysInMonth < $monthData['days_requested']) {
+ // Vượt limit
+ if ($willUsedDaysInMonth > $maxDaysPerMonth) {
+ $hasInsufficientDays = true;
+ $month_data_status = 'exceed_max_days';
+
+ // Phép còn lại > limit
+ if ($remainingOnleaveDaysInMonth > $maxDaysPerMonth) {
+ $onleave_days_will_use = $maxDaysPerMonth - $onleaveDaysInMonth;
+ $nopay_days_will_use = $monthData['days_requested'] - $maxDaysPerMonth + $onleaveDaysInMonth;
+ }
+ // Phép còn lại < limit
+ else {
+ $onleave_days_will_use = $remainingOnleaveDaysInMonth;
+ $nopay_days_will_use = $monthData['days_requested'] - $remainingOnleaveDaysInMonth;
+ }
+
+ Log::debug("--- Không đủ phép trong tháng, vượt quá limit ---", [
+ "Phep" => $onleave_days_will_use,
+ "Khong Phep" => $nopay_days_will_use
+ ]);
+ }
+ // Không vượt limit
+ else {
+ $hasInsufficientDays = true;
+ $month_data_status = 'insufficient_days';
+ $onleave_days_will_use = $remainingOnleaveDaysInMonth;
+ $nopay_days_will_use = $monthData['days_requested'] - $remainingOnleaveDaysInMonth;
+
+ Log::debug("--- Không đủ phép trong tháng, ko vượt limit ---", [
+ "Phep" => $onleave_days_will_use,
+ "Khong Phep" => $nopay_days_will_use
+ ]);
+ }
+
+ // Message cảnh báo nghỉ ko phép
+ $monthMessage = $this->buildMonthlyLeaveMessage(
+ $monthIndex,
+ $maxDaysPerMonth,
+ $monthData,
+ $remainingOnleaveDaysInMonth,
+ $onleaveDaysInMonth,
+ $nopayDaysInMonth,
+ $onleave_days_will_use,
+ $nopay_days_will_use
+ );
+ $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage;
+ }
+
+ // Ngày phép còn lại >= ngày yêu cầu (Đủ phép)
+ else {
+ // Vượt limit
+ if ($willUsedDaysInMonth > $maxDaysPerMonth) {
+ $hasInsufficientDays = true;
+ $month_data_status = 'exceed_max_days';
+ $onleave_days_will_use = $maxDaysPerMonth - $onleaveDaysInMonth;
+ $nopay_days_will_use = $willUsedDaysInMonth - $maxDaysPerMonth - $nopayDaysInMonth;
+
+ Log::debug("--- Đủ phép, vượt limit ---", [
+ "Phep" => $onleave_days_will_use,
+ "Khong Phep" => $nopay_days_will_use
+ ]);
+ }
+ // Waiting ticket
+ else if ($monthData['days_requested'] + $remainingDaysInMonthIsUsed > $maxDaysPerMonth) {
+ if ($remainingDaysInMonthIsUsed > 0) {
+ $onleave_days_will_use = $maxDaysPerMonth - $remainingDaysInMonthIsUsed;
+ $nopay_days_will_use = $monthData['days_requested'] - $onleave_days_will_use;
+ } else {
+ $onleave_days_will_use = $maxDaysPerMonth;
+ $nopay_days_will_use = $monthData['days_requested'] - $maxDaysPerMonth;
+ }
+ $hasInsufficientDays = true;
+ $month_data_status = 'exceed_max_days';
+
+ Log::debug("--- Đủ phép, Waiting ticket ---", [
+ "Phep" => $onleave_days_will_use,
+ "Khong Phep" => $nopay_days_will_use
+ ]);
+ }
+ // Đủ phép
+ else {
+ $onleave_days_will_use = $monthData['days_requested'];
+ $nopay_days_will_use = 0;
+
+ Log::debug("--- Đủ phép ---", [
+ "Phep" => $onleave_days_will_use,
+ "Khong Phep" => $nopay_days_will_use
+ ]);
+ }
+
+ // Message cảnh báo nghỉ ko phép
+ $monthMessage = $this->buildMonthlyLeaveMessage(
+ $monthIndex,
+ $maxDaysPerMonth,
+ $monthData,
+ $remainingOnleaveDaysInMonth,
+ $onleaveDaysInMonth,
+ $nopayDaysInMonth,
+ $onleave_days_will_use,
+ $nopay_days_will_use
+ );
+ $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage;
+
+ $remainingOnleaveDaysInMonth = $monthData['days_requested'];
+ }
+
+ $month_data = [
+ 'year' => $monthData['year'],
+ 'month' => $monthData['month'],
+ 'total_leave_days_in_month' => $onleaveDaysTotal, // tổng số ngày phép
+ 'total_leave_days_in_month_to_month' => $usedOnleaveDaysTotal, // tổng ngày nghỉ có phép đã nghỉ
+ 'remaining_days_in_month' => $remainingOnleaveDays, // số ngày phép còn lại
+ 'days_used' => $onleaveDaysInMonth, // tổng số ngày nghỉ có phép đã nghỉ ở tháng hiện tại
+ 'days_used_without_pay' => $nopayDaysInMonth, // 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' => $remainingOnleaveDaysInMonth,
+ 'days_will_use' => $onleave_days_will_use, //Số ngày phép sẽ sử dụng
+ 'days_will_use_without_pay' => $nopay_days_will_use, //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;
+ $monthIndex++;
+ $onleaveTmp += $onleave_days_will_use; // Cộng ngày phép dùng tạm trong tháng
+ }
+
+ // 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;
+ // Xử lý gửi ticket sau tháng hiện tại
+ if ($leaveDaysInfo && $user->is_permanent) {
+ $currentMonth = Carbon::now()->month;
+ $totalAllocated = $leaveDaysInfo->ld_day_total;
+
+ // Check có phải là nhân viên chính thức trong năm nay
+ $isProbationInYear = false;
+ if ($user->permanent_date && $user->permanent_date !== '0000-00-00') {
+ $permenantYear = Carbon::parse($user->permanent_date)->year;
+
+ if ($permenantYear === $year) {
+ $isProbationInYear = true;
+ }
+ }
+
+ // Nhân viên mới
+ if ($isProbationInYear) {
+ $permanentMonth = Carbon::parse($user->permanent_date)->month;
+ if ($month > $currentMonth) {
+ $permanentCategory = Category::where('c_type', 'PERMANENT_ONLEAVE')->where('c_code', "PERMANENT")->first();
+ $permanentDefault = (int) $permanentCategory->c_value; // Ngày phép khi thành nv chính thức
+
+ $totalAllocated = $month - ($permanentMonth - $permanentDefault);
+ }
+ }
+ // Nhân viên cũ
+ else {
+ if ($month > $currentMonth) {
+ $totalAllocated = $month;
+ }
+ }
+ }
+ $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 = [
@@ -280,11 +847,9 @@ class TicketController extends Controller
public function handleTicket(Request $request)
{
-
$rules = [
'ticket_id' => 'required',
'action' => 'required',
- // 'admin_note' => 'required'
];
// Validate the request
@@ -299,18 +864,92 @@ class TicketController extends Controller
if (!$ticket || $ticket->status !== "WAITING") {
return response()->json(['message' => "Ticket not found", 'status' => false]);
}
- $results = $this->getAllPeriod($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period);
- // $admin->id != user_id of ticket ---> continue
- // Confirm
- // Add records to the notes table like function Timekeeping.addNoteForUser() based on the $results array
+ $dataMasterTypeNotes = CategoryController::getListMasterByType("REASON_NOTES");
+ $onleave = null;
+ $leaveWithoutPay = null;
+ $temporaryOnleave = null;
- // Update updated_by and admin_note in tickets table
+ 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;
+ $temporaryOnleave = optional($dataMasterTypeNotes->where('c_code', 'TEMPORARY_ONLEAVE')->first())->c_code;
+ }
- // Send notification email to users
+ if ($onleave == null || $leaveWithoutPay == null || $temporaryOnleave == null) {
+ return response()->json(['message' => "Data reason notes not found", 'status' => false]);
+ }
- // Refuse
- // Update updated_by and admin_note in tickets table
+ if ($action == "confirm") {
+ $this->handleConfirmTicket($ticket, $admin, $admin_note, $onleave, $leaveWithoutPay, $temporaryOnleave);
+ return response()->json(['message' => "confirmed", 'status' => true]);
+ }
+
+ if ($action == "refuse") {
+ $this->handleRefuseTicket($ticket, $admin, $admin_note);
+ return response()->json(['message' => "refused", 'status' => true]);
+ }
+
+ return response()->json(['message' => "failed", 'status' => false]);
+ }
+
+ 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') . '/404');
+ }
+
+ $dataMasterTypeNotes = CategoryController::getListMasterByType("REASON_NOTES");
+ $onleave = null;
+ $leaveWithoutPay = null;
+ $temporaryOnleave = 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;
+ $temporaryOnleave = optional($dataMasterTypeNotes->where('c_code', 'TEMPORARY_ONLEAVE')->first())->c_code;
+ }
+
+ // Không tìm được ngày phép, ko phép
+ if ($onleave == null || $leaveWithoutPay == null || $temporaryOnleave == null) {
+ return redirect()->to(config('app.client_url') . '/404');
+ }
+
+ if ($action == "confirm") {
+ $this->handleConfirmTicket($ticket, $admin, $admin_note, $onleave, $leaveWithoutPay, $temporaryOnleave);
+ return redirect()->to(config('app.client_url') . '/tickets-management');
+ }
+
+ if ($action == "refuse") {
+ $this->handleRefuseTicket($ticket, $admin, $admin_note);
+ return redirect()->to(config('app.client_url') . '/tickets-management');
+ }
+
+ // Failed
+ return redirect()->to(config('app.client_url') . '/tickets-management');
+ }
+
+ private function handleConfirmTicket($ticket, $admin, $admin_note, $onleave, $leaveWithoutPay, $temporaryOnleave)
+ {
$startDate = $ticket->start_date; //Start day
$startPeriod = $ticket->start_period; //The session begins
$endDate = $ticket->end_date; //End date
@@ -325,9 +964,143 @@ class TicketController extends Controller
$formattedEndDate = Carbon::createFromFormat('Y-m-d', $endDate)->format('d/m/Y');
$user = Admin::find($ticket->user_id);
-
- if ($action == "confirm") {
- foreach ($results as $result) {
+
+ 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);
+ $currentMonth = Carbon::now()->month;
+ 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' => $month > $currentMonth ? $temporaryOnleave : $onleave,
+ 'n_note' => $ticket->reason,
+ 'ticket_id' => $ticket->id
+ ]);
+ Notes::create([
+ 'n_user_id' => $ticket->user_id,
+ 'n_day' => $day,
+ 'n_month' => $month,
+ 'n_year' => $year,
+ 'n_time_type' => 'C',
+ 'n_reason' => $month > $currentMonth ? $temporaryOnleave : $leaveWithoutPay,
+ 'n_note' => $ticket->reason,
+ 'ticket_id' => $ticket->id
+ ]);
+ $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' => $month > $currentMonth ? $temporaryOnleave : $onleave,
+ 'n_note' => $ticket->reason,
+ 'ticket_id' => $ticket->id
+ ]);
+ $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' => $month > $currentMonth ? $temporaryOnleave : $leaveWithoutPay,
+ 'n_note' => $ticket->reason,
+ 'ticket_id' => $ticket->id
+ ]);
+ $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' => $month > $currentMonth ? $temporaryOnleave : $onleave,
+ 'n_note' => $ticket->reason,
+ 'ticket_id' => $ticket->id
+ ]);
+ }
+ }
+
+ // $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,
@@ -336,92 +1109,212 @@ class TicketController extends Controller
'n_year' => $year,
'n_time_type' => $result['period'],
'n_reason' => $ticket->type,
- 'n_note' => $ticket->reason
+ 'n_note' => $ticket->reason,
+ 'ticket_id' => $ticket->id
]);
- if ($ticket->type == "WFH") {
- $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);
+ //WFH - start tracking
+ $type = $result['period'];
+ $date = Carbon::create($year, $month, $day)->setTimezone(env('TIME_ZONE'));
- if ($type == 'S') {
- $end = $date->copy()->setTime(11, 30, 0);
- } else if ($type == 'C') {
- $start = $date->copy()->setTime(11, 30, 0);
+ //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(Carbon::parse($ticket->start_date)->month, Carbon::parse($ticket->start_date)->year);
+ $this->createOrUpdateRecordForCurrentMonth(Carbon::parse($ticket->end_date)->month, Carbon::parse($ticket->end_date)->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));
+ }
+
+ private function handleRefuseTicket($ticket, $admin, $admin_note)
+ {
+ $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);
+
+ $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);
+
+ $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));
+ }
+
+ 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ỉ') . "
";
}
- 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')
- ]
- ]);
+ if ($isSaturdayWorkDay) {
+ $results[] = ['date' => $date->toDateString(), 'period' => "S"];
+ }
+
+ continue;
+ }
+ // Skip Sundays entirely
+ else if ($date->dayOfWeek === Carbon::SUNDAY) {
+ continue;
}
}
- $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 ($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];
+ }
}
- 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]);
+ // Returns results
+ return $results;
}
private function getAllPeriod($startDate, $startPeriod, $endDate, $endPeriod)
@@ -488,4 +1381,221 @@ class TicketController extends Controller
//Returns results
return $results;
}
+
+ private function buildMonthlyLeaveMessage(
+ $index,
+ $max,
+ $monthData,
+ $totalLeave,
+ $usedLeave,
+ $usedNoPay,
+ $willUseLeave,
+ $willUseNoPay
+ ): string {
+ $message = "";
+
+ if ($index === 0) {
+ $showMonth = $monthData['month'] > Carbon::now()->month ? $monthData['month'] : Carbon::now()->month;
+ $message .= "* Quy định: mỗi tháng được nghỉ tối đa {$max} ngày phép\n";
+ $message .= "- Bạn có: {$totalLeave} ngày phép (Tính tới {$showMonth}/{$monthData['year']})\n\n";
+ }
+
+ // Hiển thị cộng phép nếu gửi ticket trong tương lai
+ $monthValue = $monthData['year'] . '-' . sprintf('%02d', $monthData['month']);
+ $currentMonth = date('Y-m');
+ if ($monthValue > $currentMonth && $index !== 0) {
+ $message .= "* Bạn được cộng 1 phép\n";
+ }
+
+ // In mỗi tháng
+ $message .= "Tháng {$monthData['month']}/{$monthData['year']}:\n";
+ if ($usedLeave > 0 || $usedNoPay > 0) {
+ $message .= " - Bạn đã sử dụng: ";
+ $usedParts = [];
+ if ($usedLeave > 0) $usedParts[] = "{$usedLeave} phép";
+ if ($usedNoPay > 0) $usedParts[] = "{$usedNoPay} không phép";
+ $message .= implode(', ', $usedParts) . "\n";
+ }
+
+ if ($willUseLeave > 0 || $willUseNoPay > 0) {
+ $message .= " - Dự kiến bạn sẽ sử dụng: ";
+ $usedParts = [];
+ if ($willUseLeave > 0) $usedParts[] = "{$willUseLeave} phép";
+ if ($willUseNoPay > 0) $usedParts[] = "{$willUseNoPay} không phép";
+ $message .= implode(', ', $usedParts);
+ }
+
+ return $message;
+ }
+
+ /**
+ * 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;
+ }
+
+ public function updateOldData(int $month, int $year)
+ {
+ LeaveDays::where('ld_year', $year)
+ ->update(['ld_day_total' => $month]);
+
+ $users = Admin::all();
+
+ foreach ($users as $user) {
+ $leaveDay = LeaveDays::where('ld_user_id', $user->id)
+ ->where('ld_year', $year)
+ ->first();
+ $notes = Notes::where('n_user_id', $user->id)
+ ->where('n_year', $year)
+ ->where('n_reason', 'ONLEAVE')
+ ->orderBy('n_month')
+ ->orderBy('n_day')
+ ->get()
+ ->groupBy('n_month');
+
+ $onleaveDaysTotal = $leaveDay->ld_additional_day;
+
+ $previousYearData = LeaveDays::where('ld_user_id', $user->id)
+ ->where('ld_year', $year - 1)
+ ->first();
+
+ $ld_additional_day = 0;
+ $ld_note = '';
+
+ if ($previousYearData) {
+ $ld_additional_day = $previousYearData->ld_day_total + $previousYearData->ld_additional_day;
+ $totalLeaveDaysByMonth = Notes::join('categories', function ($join) {
+ $join->on('notes.n_time_type', '=', 'categories.c_code')
+ ->where('categories.c_type', 'TIME_TYPE');
+ })
+ ->select(
+ DB::raw('notes.n_user_id as n_user_id'),
+ DB::raw('notes.n_year as year'),
+ DB::raw('SUM(categories.c_value) as leave_days')
+ )
+ ->where('notes.n_year', $year - 1)
+ ->where('notes.n_user_id', $user->id)
+ ->where('notes.n_reason', 'ONLEAVE')
+ ->groupBy(DB::raw('notes.n_year'))
+ ->first();
+ if ($totalLeaveDaysByMonth) {
+ $ld_additional_day = $ld_additional_day - $totalLeaveDaysByMonth->leave_days;
+ if ($ld_additional_day < 0) {
+ $ld_additional_day = 0;
+ }
+ }
+
+ if ($ld_additional_day > 0) {
+ $ld_note = "Cộng " . $ld_additional_day . " ngày phép tồn năm trước. \n";
+ }
+ }
+
+ for ($i = 1; $i <= $month; $i++) {
+ // Giả lập cộng phép
+ $onleaveDaysTotal++;
+ // $tmpOnleaveDaysTotal = $onleaveDaysTotal;
+
+ $onleaveDaysInMonth = 0;
+ $nopayDaysInMonth = 0;
+
+ if ($notes->has($i)) {
+ foreach ($notes[$i] as $note) {
+ $onleaveDaysInMonth += $note->n_time_type == 'ALL' ? 1.0 : 0.5;
+ }
+
+ if ($onleaveDaysInMonth > $onleaveDaysTotal) {
+ $nopayDaysInMonth = $onleaveDaysInMonth - $onleaveDaysTotal;
+ $onleaveDaysTotal = 0;
+ } else {
+ $onleaveDaysTotal -= $onleaveDaysInMonth;
+ }
+
+ // Xử lý cập nhật lại các note có phép thành không phép
+ if ($nopayDaysInMonth > 0) {
+ $revertNotes = $notes->get($i, collect())->reverse();
+ $nopayDaysUpdated = 0;
+
+ foreach ($revertNotes as $note) {
+ if ($note->n_time_type == 'ALL') {
+ if ($nopayDaysInMonth - $nopayDaysUpdated == 0.5) {
+ Notes::create([
+ 'n_user_id' => $user->id,
+ 'n_day' => $note->n_day,
+ 'n_month' => $note->n_month,
+ 'n_year' => $note->n_year,
+ 'n_time_type' => 'S',
+ 'n_reason' => 'ONLEAVE',
+ 'n_note' => $note->n_note,
+ 'ticket_id' => $note->ticket_id
+ ]);
+ Notes::create([
+ 'n_user_id' => $user->id,
+ 'n_day' => $note->n_day,
+ 'n_month' => $note->n_month,
+ 'n_year' => $note->n_year,
+ 'n_time_type' => 'C',
+ 'n_reason' => 'LEAVE_WITHOUT_PAY',
+ 'n_note' => $note->n_note,
+ 'ticket_id' => $note->ticket_id
+ ]);
+
+ $note->delete();
+ break;
+ }
+
+ $nopayDaysUpdated += 1.0;
+ $note->update([
+ 'n_reason' => "LEAVE_WITHOUT_PAY"
+ ]);
+ } else {
+ $nopayDaysUpdated += 0.5;
+ $note->update([
+ 'n_reason' => "LEAVE_WITHOUT_PAY"
+ ]);
+ }
+
+ if ($nopayDaysUpdated >= $nopayDaysInMonth) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Log thông kê sau mỗi tháng
+ // Log::debug(
+ // "📊 Thống kê ngày phép Tháng {$i}:\n" .
+ // " - Tổng phép đầu tháng: $tmpOnleaveDaysTotal\n" .
+ // " - Có phép: $onleaveDaysInMonth\n" .
+ // " - Không phép: $nopayDaysInMonth\n" .
+ // " - Tổng phép cuối tháng: $onleaveDaysTotal\n"
+ // );
+ }
+
+ $leaveDay->ld_note = $ld_note;
+ $leaveDay->save();
+ }
+ }
}
diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php
index cddc744..f0a046c 100644
--- a/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php
+++ b/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php
@@ -11,12 +11,14 @@ use App\Traits\HasOrderByRequest;
use App\Traits\HasSearchRequest;
use Carbon\Carbon;
use Illuminate\Http\Request;
-use Illuminate\Support\Facades\DB;
+use Illuminate\Support\Facades\Mail;
+use App\Mail\TicketMail;
use Modules\Admin\app\Models\Admin;
use Modules\Admin\app\Models\MonthlyTimekeeping;
use Modules\Admin\app\Models\Tracking;
use Maatwebsite\Excel\Facades\Excel;
use App\Exports\TimekeepingExport;
+use Modules\Admin\app\Models\Ticket;
class TimekeepingController extends Controller
{
@@ -152,8 +154,6 @@ class TimekeepingController extends Controller
return response()->json(['status' => true, 'message' => 'Add successfully']);
}
-
-
public function updateCacheMonth(Request $request)
{
$month = $request->month;
@@ -172,20 +172,54 @@ class TimekeepingController extends Controller
// Validate the request
$request->validate($rules);
-
$id = $request->input('id');
- $month = $request->month;
- $year = $request->year;
-
$note = Notes::find($id);
- if ($note) {
- $note->delete();
- $this->createOrUpdateRecordForCurrentMonth($month, $year);
- return response()->json(['message' => 'Delete success', 'status' => true]);
+ if (!$note) {
+ return response()->json(['message' => 'Note not found', 'status' => false]);
}
- return response()->json(['message' => 'Delete fail', 'status' => false]);
+ $ticket = Ticket::find($note->ticket_id);
+ if (!$ticket) {
+ return response()->json(['message' => 'Ticket not found, can not delete note', 'status' => false]);
+ }
+
+ $admin = auth('admins')->user();
+
+ // Handle send mail
+ $dataMasterStartPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $ticket->start_period);
+ $dataMasterEndPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $ticket->end_period);
+ $dataMasterType = CategoryController::getListMasterByCodeAndType("REASON", $ticket->type);
+ $formattedStartDate = Carbon::createFromFormat('Y-m-d', $ticket->start_date)->format('d/m/Y');
+ $formattedEndDate = Carbon::createFromFormat('Y-m-d', $ticket->end_date)->format('d/m/Y');
+
+ $user = Admin::find($ticket->user_id);
+
+ $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" => $ticket->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));
+
+ // Update
+ $ticket->updated_by = $admin->name;
+ $ticket->status = "REFUSED";
+ $ticket->save();
+ Notes::where('ticket_id', $ticket->id)->delete();
+
+ // Clear Timekeeping cache
+ $this->createOrUpdateRecordForCurrentMonth(Carbon::parse($ticket->start_date)->month, Carbon::parse($ticket->start_date)->year);
+ $this->createOrUpdateRecordForCurrentMonth(Carbon::parse($ticket->end_date)->month, Carbon::parse($ticket->end_date)->year);
+ return response()->json(['message' => 'Delete success', 'status' => true]);
}
public function export(Request $request)
@@ -206,10 +240,10 @@ class TimekeepingController extends Controller
}
// Lọc chỉ lấy user có permission bao gồm staff
- $staffData = array_filter($responseData['data'], function($user) {
+ $staffData = array_filter($responseData['data'], function ($user) {
return isset($user['user']['permission']) && strpos($user['user']['permission'], 'staff') !== false;
});
-
+
$currentDate = date('d_His');
return Excel::download(
new TimekeepingExport(
diff --git a/BACKEND/Modules/Admin/routes/api.php b/BACKEND/Modules/Admin/routes/api.php
index 8f9ee2c..fe3832e 100755
--- a/BACKEND/Modules/Admin/routes/api.php
+++ b/BACKEND/Modules/Admin/routes/api.php
@@ -44,6 +44,7 @@ Route::middleware('api')
Route::post('login', [AdminController::class, 'login']);
Route::post('reset-password', [AdminController::class, 'resetPassword']);
Route::get('forgot-password', [AdminController::class, 'forgotPassword']);
+ Route::get('/email-handle-ticket', [TicketController::class, 'handleTicketEmail'])->name('email.ticket.handle');
});
// NOTE after login
@@ -163,6 +164,7 @@ Route::middleware('api')
], function () {
Route::get('/all', [TicketController::class, 'getAll'])->middleware('check.permission:admin.hr');
Route::get('/getByUserId', [TicketController::class, 'getByUserId'])->middleware('check.permission:admin.hr.staff');
+ Route::post('/update', [TicketController::class, 'updateTicket'])->middleware('check.permission:admin.hr');
Route::post('/create', [TicketController::class, 'createTicket'])->middleware('check.permission:admin.hr.staff');
Route::get('/delete', [TicketController::class, 'deleteTicket'])->middleware('check.permission:admin.hr.staff');
Route::post('/handle-ticket', [TicketController::class, 'handleTicket'])->middleware('check.permission:admin');
diff --git a/BACKEND/Modules/Auth/app/Http/Controllers/UserController.php b/BACKEND/Modules/Auth/app/Http/Controllers/UserController.php
index bb1e898..ff345da 100755
--- a/BACKEND/Modules/Auth/app/Http/Controllers/UserController.php
+++ b/BACKEND/Modules/Auth/app/Http/Controllers/UserController.php
@@ -3,13 +3,15 @@
namespace Modules\Auth\app\Http\Controllers;
use App\Http\Controllers\Controller;
+use App\Models\LeaveDays;
use App\Traits\IsAPI;
+use Carbon\Carbon;
use Illuminate\Http\Request;
-use Illuminate\Http\Response;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Modules\Auth\app\Models\User;
use Illuminate\Support\Str;
+use Modules\Admin\app\Models\Category;
use SimpleSoftwareIO\QrCode\Facades\QrCode;
class UserController extends Controller
@@ -34,9 +36,37 @@ class UserController extends Controller
]);
if ($request->has('id')) {
- $payload = $request->only(['name', 'email', 'permission']);
+ $payload = $request->only(['name', 'email', 'permission', 'is_permanent']);
$user = User::find($request->id);
+ // Không cho chuyển từ chính thức thành lại thử việc
+ if (!$request->is_permanent && $user->is_permanent) {
+ return response()->json(['status' => false, 'message' => 'You cannot change an employee from permanent to probationary.']);
+ }
+
+ // Thêm ngày phép khi thành nhân viên chính thức
+ if ($request->is_permanent && !$user->is_permanent) {
+ $userLeaveDay = LeaveDays::where('ld_user_id', $user->id)
+ ->where('ld_year', Carbon::now()->year)
+ ->first();
+
+ if ($userLeaveDay) {
+ $permanentCategory = Category::where('c_type', 'PERMANENT_ONLEAVE')->where('c_code', "PERMANENT")->first();
+ $permanentDefault = (int) $permanentCategory->c_value; // Ngày phép khi thành nv chính thức
+ $userLeaveDay->ld_day_total = $permanentDefault;
+
+ $newNote = "Cộng ngày phép cho nhân viên chính thức"; // Thêm ghi chú
+ if (!empty($userLeaveDay->ld_note)) {
+ $userLeaveDay->ld_note = $userLeaveDay->ld_note . "\n" . $newNote;
+ } else {
+ $userLeaveDay->ld_note = $newNote;
+ }
+ $userLeaveDay->save();
+ }
+
+ $payload['permanent_date'] = Carbon::now()->toDateString();
+ }
+
$user->update($payload);
return response()->json(['data' => $user, 'status' => true, 'message' => 'Update successful']);
} else {
@@ -44,9 +74,22 @@ class UserController extends Controller
'name' => $request->name,
'email' => $request->email,
'password' => bcrypt('Work@1234'),
- 'permission' => $request->permission
+ 'permission' => $request->permission,
+ 'is_permanent' => false
]);
+ // Khởi tạo LeaveDays cho nhân viên mới
+ LeaveDays::insert([
+ 'ld_user_id' => $user->id,
+ 'ld_day_total' => 0,
+ 'ld_year' => Carbon::now()->year,
+ 'ld_additional_day' => 0,
+ 'ld_note' => '',
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]);
+ $this->createOrUpdateRecordForCurrentMonth(Carbon::now()->month, Carbon::now()->year);
+
$user_res = [
'name' => $user->name,
'email' => $user->email,
@@ -98,8 +141,6 @@ class UserController extends Controller
return response()->json(['data' => ['user' => $user_res, 'gitea' => "dev", 'zulip' => "dev"], 'status' => true, 'message' => 'Create successful']);
}
}
-
- return response()->json(['status' => false, 'message' => 'Process fail']);
}
public function delete(Request $request)
diff --git a/BACKEND/Modules/Auth/app/Models/User.php b/BACKEND/Modules/Auth/app/Models/User.php
index 0fc45ba..4347eca 100755
--- a/BACKEND/Modules/Auth/app/Models/User.php
+++ b/BACKEND/Modules/Auth/app/Models/User.php
@@ -25,7 +25,9 @@ class User extends Authenticatable implements JWTSubject
'name',
'email',
'password',
- 'permission'
+ 'permission',
+ 'is_permanent',
+ 'permanent_date'
];
/**
diff --git a/BACKEND/app/Console/Commands/AddMonthlyLeaveDaysCommand.php b/BACKEND/app/Console/Commands/AddMonthlyLeaveDaysCommand.php
new file mode 100644
index 0000000..267c767
--- /dev/null
+++ b/BACKEND/app/Console/Commands/AddMonthlyLeaveDaysCommand.php
@@ -0,0 +1,24 @@
+argument('month');
+ $year = $this->argument('year');
+ AddMonthlyLeaveDays::dispatch($month, $year);
+ }
+}
diff --git a/BACKEND/app/Console/Commands/InitializeLeaveDaysCommand.php b/BACKEND/app/Console/Commands/InitializeLeaveDaysCommand.php
index ad0a9e9..bf74a72 100644
--- a/BACKEND/app/Console/Commands/InitializeLeaveDaysCommand.php
+++ b/BACKEND/app/Console/Commands/InitializeLeaveDaysCommand.php
@@ -8,7 +8,7 @@ use App\Jobs\InitializeLeaveDays;
class InitializeLeaveDaysCommand extends Command
{
protected $signature = 'initialize:leavedays {year?}';
- protected $description = 'Initialize leave days for users';
+ protected $description = 'Cấp phép năm cho tất cả người dùng';
public function __construct()
{
@@ -18,6 +18,7 @@ class InitializeLeaveDaysCommand extends Command
public function handle()
{
$year = $this->argument('year');
- InitializeLeaveDays::dispatch($year);
+ // Không sử dụng nữa, theo rule mới
+ // InitializeLeaveDays::dispatch($year);
}
}
diff --git a/BACKEND/app/Console/Commands/UpdateTemporaryLeaveDaysCommand.php b/BACKEND/app/Console/Commands/UpdateTemporaryLeaveDaysCommand.php
new file mode 100644
index 0000000..9cb2db8
--- /dev/null
+++ b/BACKEND/app/Console/Commands/UpdateTemporaryLeaveDaysCommand.php
@@ -0,0 +1,24 @@
+argument('month');
+ $year = $this->argument('year');
+ UpdateTemporaryLeaveDays::dispatch($month, $year);
+ }
+}
diff --git a/BACKEND/app/Console/Kernel.php b/BACKEND/app/Console/Kernel.php
index 6d56e12..3e9cf06 100755
--- a/BACKEND/app/Console/Kernel.php
+++ b/BACKEND/app/Console/Kernel.php
@@ -3,6 +3,7 @@
namespace App\Console;
use App\Jobs\DeductLeaveDays;
+use App\Jobs\AddMonthlyLeaveDays;
use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
@@ -32,6 +33,10 @@ class Kernel extends ConsoleKernel
// Chạy buổi chiều lúc 17:30
$schedule->command('attendance:check C')->dailyAt('17:30');
+
+ // Chạy vào ngày đầu tiên của mỗi tháng
+ $schedule->command('add:monthly-leavedays')->monthlyOn(1, '00:01');
+ $schedule->command('update:temporary-leavedays')->monthlyOn(1, '00:05');
}
/**
diff --git a/BACKEND/app/Exports/LeaveManagementExport.php b/BACKEND/app/Exports/LeaveManagementExport.php
index 038ade9..402353b 100644
--- a/BACKEND/app/Exports/LeaveManagementExport.php
+++ b/BACKEND/app/Exports/LeaveManagementExport.php
@@ -41,7 +41,7 @@ class LeaveManagementExport implements FromArray, WithHeadings, WithStyles, With
$stt = 0;
foreach ($this->data as $index => $user) {
$totalDayOff = 0;
- $totalDayLeave = $user['leaveDay']['ld_day'] + $user['leaveDay']['ld_date_additional'];
+ $totalDayLeave = $user['leaveDay']['ld_day_total'] + $user['leaveDay']['ld_additional_day'];
// Tính tổng ngày nghỉ theo tháng
$monthlyLeaves = array_fill(1, 12, 0);
diff --git a/BACKEND/app/Jobs/AddMonthlyLeaveDays.php b/BACKEND/app/Jobs/AddMonthlyLeaveDays.php
new file mode 100644
index 0000000..15372f4
--- /dev/null
+++ b/BACKEND/app/Jobs/AddMonthlyLeaveDays.php
@@ -0,0 +1,82 @@
+month = $month ?? Carbon::now()->month;
+ $this->year = $year ?? Carbon::now()->year;
+ }
+
+ public function handle(): void
+ {
+ $users = User::get();
+
+ foreach ($users as $user) {
+ // Nếu là nhân viên chưa chính thức, ko cộng phép
+ if (!$user->is_permanent) {
+ continue;
+ }
+
+ $leaveDay = LeaveDays::where('ld_user_id', $user->id)
+ ->where('ld_year', $this->year)
+ ->first();
+
+ if (!$leaveDay) {
+ // Nếu chưa có dữ liệu năm hiện tại, tạo mới
+ // Số ngày phép bằng với tháng hiện tại
+ $leaveDay = new LeaveDays([
+ 'ld_user_id' => $user->id,
+ 'ld_day_total' => $this->month, // Số ngày phép bằng tháng hiện tại
+ 'ld_year' => $this->year,
+ 'ld_additional_day' => 0,
+ 'ld_note' => 'Khởi tạo ngày phép đến tháng ' . $this->month,
+ 'ld_special_leave_day' => 0,
+ ]);
+ $leaveDay->save();
+ } else {
+ // Check có phải là nhân viên chính thức trong năm nay (Nhân viên mới)
+ if ($user->permanent_date && $user->permanent_date !== '0000-00-00') {
+ $permenantYear = Carbon::parse($user->permanent_date)->year;
+
+ if ($permenantYear === $this->year) {
+ $permanentCategory = Category::where('c_type', 'PERMANENT_ONLEAVE')->where('c_code', "PERMANENT")->first();
+ $permanentDefault = (int) $permanentCategory->c_value; // Ngày phép khi thành nv chính thức
+
+ $permanentMonth = Carbon::parse($user->permanent_date)->month;
+ if ($this->month > $leaveDay->ld_day_total - ($permanentDefault - $permanentMonth)) {
+ $leaveDay->ld_day_total += self::ONLEAVE_PER_MONTH;
+ $leaveDay->save();
+ }
+ }
+ }
+
+ // Kiểm tra nếu số ngày phép hiện tại nhỏ hơn tháng hiện tại (Nhân viên cũ)
+ if ($leaveDay->ld_day_total < $this->month) {
+ // Cộng mỗi tháng 1 ngày phép cho nhân viên
+ $leaveDay->ld_day_total += self::ONLEAVE_PER_MONTH;
+ $leaveDay->save();
+ }
+ }
+ }
+ }
+}
diff --git a/BACKEND/app/Jobs/DeductLeaveDays.php b/BACKEND/app/Jobs/DeductLeaveDays.php
index 86dc3b0..3dd165a 100644
--- a/BACKEND/app/Jobs/DeductLeaveDays.php
+++ b/BACKEND/app/Jobs/DeductLeaveDays.php
@@ -10,7 +10,6 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
-use Illuminate\Support\Facades\DB;
use Carbon\Carbon;
class DeductLeaveDays implements ShouldQueue
@@ -36,44 +35,34 @@ class DeductLeaveDays implements ShouldQueue
foreach ($users as $user) {
$existingData = LeaveDays::where('ld_user_id', $user->id)
->where('ld_year', $this->year)
- ->where('ld_date_additional', ">", 0)
+ ->where('ld_additional_day', ">", 0)
->first();
if (!$existingData) {
continue;
}
- $totalLeaveDaysByMonth = Notes::join('categories', function ($join) {
+ // Lấy tổng ngày nghỉ phép 3 tháng đầu trong năm
+ $usedOnleaveDaysTotal = Notes::join('categories', function ($join) {
$join->on('notes.n_time_type', '=', 'categories.c_code')
->where('categories.c_type', 'TIME_TYPE');
})
- ->select(
- DB::raw('notes.n_user_id as n_user_id'),
- DB::raw('notes.n_year as year'),
- DB::raw('SUM(categories.c_value) as leave_days')
- )
- ->where('notes.n_year', $this->year)
- ->where('notes.n_user_id', $user->id)
- ->where('notes.n_reason', 'ONLEAVE')
- ->groupBy(DB::raw('notes.n_year'))
- ->first();
-
- if ($totalLeaveDaysByMonth) {
- //Nếu ngày phép thừa năm trước chưa sử dụng hết => cập nhật lại ngày đó (Ngày tồn đọng - ngày sử dụng)
- if ($existingData->ld_date_additional > $totalLeaveDaysByMonth->leave_days) {
- LeaveDays::where('ld_year', $this->year)
- ->where('ld_user_id', $user->id)
- ->update([
- 'ld_date_additional' => $totalLeaveDaysByMonth->leave_days,
- ]);
+ ->where('n_user_id', $user->id)
+ ->where('n_year', $this->year)
+ ->where('n_month', "<=", 3)
+ ->where('n_reason', 'ONLEAVE')
+ ->sum('categories.c_value');
+
+ if ($usedOnleaveDaysTotal) {
+ if ($existingData->ld_additional_day > $usedOnleaveDaysTotal) {
+ $ld_note = "Trừ " . $existingData->ld_additional_day - $usedOnleaveDaysTotal . " ngày phép tồn năm trước. \n";
+ $existingData->ld_note = $existingData->ld_note . "\n" . $ld_note;
+ $existingData->ld_additional_day = $usedOnleaveDaysTotal;
}
} else {
- //Nếu không sử dụng ngày nghỉ còn lại ở năm rồi thì xóa => theo luật ld
- LeaveDays::where('ld_year', $this->year)
- ->where('ld_user_id', $user->id)
- ->update([
- 'ld_date_additional' => "0",
- ]);
+ $existingData->ld_additional_day = 0;
}
+
+ $existingData->save();
}
}
}
diff --git a/BACKEND/app/Jobs/InitializeLeaveDays.php b/BACKEND/app/Jobs/InitializeLeaveDays.php
index 3c545b5..424745c 100644
--- a/BACKEND/app/Jobs/InitializeLeaveDays.php
+++ b/BACKEND/app/Jobs/InitializeLeaveDays.php
@@ -34,7 +34,8 @@ class InitializeLeaveDays implements ShouldQueue
public function handle(): void
{
$users = User::get();
- $ld_day = 12;
+ $ld_day_total = Carbon::now()->month; // Khởi tạo phép hiện có bằng tháng hiện tại
+
foreach ($users as $user) {
// Kiểm tra xem dữ liệu của user này đã tồn tại cho năm hiện tại chưa
$existingData = LeaveDays::where('ld_user_id', $user->id)
@@ -51,11 +52,11 @@ class InitializeLeaveDays implements ShouldQueue
->where('ld_year', $this->year - 1)
->first();
- $ld_date_additional = 0;
+ $ld_additional_day = 0;
$ld_note = '';
if ($previousYearData) {
- $ld_date_additional = $previousYearData->ld_day + $previousYearData->ld_date_additional;
+ $ld_additional_day = $previousYearData->ld_day_total + $previousYearData->ld_additional_day;
$totalLeaveDaysByMonth = Notes::join('categories', function ($join) {
$join->on('notes.n_time_type', '=', 'categories.c_code')
->where('categories.c_type', 'TIME_TYPE');
@@ -71,20 +72,23 @@ class InitializeLeaveDays implements ShouldQueue
->groupBy(DB::raw('notes.n_year'))
->first();
if ($totalLeaveDaysByMonth) {
- $ld_date_additional = $ld_date_additional - $totalLeaveDaysByMonth->leave_days;
- if ($ld_date_additional < 0) {
- $ld_date_additional = 0;
+ $ld_additional_day = $ld_additional_day - $totalLeaveDaysByMonth->leave_days;
+ if ($ld_additional_day < 0) {
+ $ld_additional_day = 0;
}
}
- $ld_note = 'Cộng dồn ngày phép năm cũ';
+
+ if ($ld_additional_day > 0) {
+ $ld_note = "Cộng " . $ld_additional_day . " ngày phép tồn năm trước. \n";
+ }
}
// Tạo dữ liệu cho năm hiện tại
LeaveDays::insert([
'ld_user_id' => $user->id,
- 'ld_day' => $ld_day,
+ 'ld_day_total' => $user->is_permanent ? $ld_day_total : 0, // Nếu là nhân viên mới, ko cấp phép
'ld_year' => $this->year,
- 'ld_date_additional' => $ld_date_additional,
+ 'ld_additional_day' => $ld_additional_day,
'ld_note' => $ld_note,
'created_at' => now(),
'updated_at' => now(),
diff --git a/BACKEND/app/Jobs/UpdateTemporaryLeaveDays.php b/BACKEND/app/Jobs/UpdateTemporaryLeaveDays.php
new file mode 100644
index 0000000..f578d28
--- /dev/null
+++ b/BACKEND/app/Jobs/UpdateTemporaryLeaveDays.php
@@ -0,0 +1,220 @@
+month = $month ?? Carbon::now()->month;
+ $this->year = $year ?? Carbon::now()->year;
+ }
+
+ /**
+ * Execute the job.
+ */
+ public function handle()
+ {
+ $users = User::get();
+
+ foreach ($users as $user) {
+ $leaveDay = LeaveDays::where('ld_user_id', $user->id)
+ ->where('ld_year', $this->year)
+ ->first();
+
+ $notes = Notes::where('n_reason', 'TEMPORARY_ONLEAVE')
+ ->where('n_user_id', $user->id)
+ ->where('n_year', $this->year)
+ ->where('n_month', $this->month)
+ ->whereExists(function ($query) use ($user) {
+ $query->select(DB::raw(1))
+ ->from('tickets')
+ ->where('tickets.user_id', $user->id)
+ ->where('tickets.status', 'CONFIRMED')
+ ->where('tickets.type', 'ONLEAVE');
+ })
+ ->get();
+
+ $maxDaysPerMonth = $this->getMaxLeaveDaysPerMonth();
+
+ // Tổng ngày nghỉ sẽ dùng trong tháng
+ $willUsedDaysInMonth = 0;
+ foreach ($notes as $note) {
+ $willUsedDaysInMonth += $note->n_time_type == 'ALL' ? 1.0 : 0.5;
+ }
+
+ // Tổng phép đang có
+ $onleaveDaysTotal = $leaveDay->ld_day_total + $leaveDay->ld_additional_day + $leaveDay->ld_special_leave_day;
+ // Phép đã sử dụng tới tháng hiện tại
+ $usedOnleaveDaysTotal = 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', $this->year)
+ ->where('n_month', "<=", $this->month)
+ ->where('n_reason', 'ONLEAVE')
+ ->sum('categories.c_value');
+ // Phép còn lại
+ $remainingOnleaveDays = $onleaveDaysTotal - $usedOnleaveDaysTotal;
+
+ // Log::debug("User {$user->name}\n");
+ // Log::debug(
+ // "📊 Thống kê ngày phép:\n" .
+ // " - Tháng: {$this->month}\n" .
+ // " - Tổng ngày nghỉ sẽ dùng trong tháng: $willUsedDaysInMonth\n" .
+ // " - Tổng ngày phép: $onleaveDaysTotal\n" .
+ // " - Tổng ngày phép đã nghỉ: $usedOnleaveDaysTotal\n" .
+ // " - Tổng ngày phép còn lại: $remainingOnleaveDays\n"
+ // );
+
+ $onleave_days_will_use = 0; // Ngày phép sẽ dùng
+ $nopay_days_will_use = 0; // Ngày ko phép sẽ dùng
+
+ // Ngày phép còn lại <= 0 (Hết phép)
+ if ($remainingOnleaveDays <= 0) {
+ $onleave_days_will_use = 0;
+ $nopay_days_will_use = $willUsedDaysInMonth;
+
+ Log::debug("--- Hết phép trong tháng ---");
+ }
+
+ // Ngày phép còn lại < ngày yêu cầu (Không đủ phép)
+ else if ($remainingOnleaveDays < $willUsedDaysInMonth) {
+ // Vượt limit
+ if ($willUsedDaysInMonth > $maxDaysPerMonth) {
+ // Phép còn lại > limit
+ if ($remainingOnleaveDays > $maxDaysPerMonth) {
+ $onleave_days_will_use = $maxDaysPerMonth;
+ $nopay_days_will_use = $willUsedDaysInMonth - $maxDaysPerMonth;
+ }
+ // Phép còn lại < limit
+ else {
+ $onleave_days_will_use = $remainingOnleaveDays;
+ $nopay_days_will_use = $willUsedDaysInMonth - $remainingOnleaveDays;
+ }
+
+ Log::debug("--- Không đủ phép trong tháng, vượt quá limit ---",);
+ }
+ // Không vượt limit
+ else {
+ $onleave_days_will_use = $remainingOnleaveDays;
+ $nopay_days_will_use = $willUsedDaysInMonth - $remainingOnleaveDays;
+
+ Log::debug("--- Không đủ phép trong tháng, ko vượt limit ---");
+ }
+ }
+
+ // Ngày phép còn lại >= ngày yêu cầu (Đủ phép)
+ else {
+ // Vượt limit
+ if ($willUsedDaysInMonth > $maxDaysPerMonth) {
+ $onleave_days_will_use = $maxDaysPerMonth;
+ $nopay_days_will_use = $willUsedDaysInMonth - $maxDaysPerMonth;
+
+ Log::debug("--- Đủ phép, vượt limit ---");
+ }
+ // Không vượt limit
+ else {
+ $onleave_days_will_use = $willUsedDaysInMonth;
+ $nopay_days_will_use = 0;
+
+ Log::debug("--- Đủ phép ---");
+ }
+ }
+
+ Log::debug("", [
+ "Phep" => $onleave_days_will_use,
+ "Khong Phep" => $nopay_days_will_use
+ ]);
+
+ // Có nghỉ không phép
+ if ($nopay_days_will_use > 0) {
+ foreach ($notes as $note) {
+ $value = ($note->n_time_type === 'ALL') ? 1.0 : 0.5;
+
+ if ($note->n_time_type === 'ALL' && $onleave_days_will_use == 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' => $user->id,
+ 'n_day' => $note->n_day,
+ 'n_month' => $note->n_month,
+ 'n_year' => $note->n_year,
+ 'n_time_type' => 'S',
+ 'n_reason' => 'ONLEAVE',
+ 'n_note' => $note->n_note,
+ 'ticket_id' => $note->ticket_id
+ ]);
+ Notes::create([
+ 'n_user_id' => $user->id,
+ 'n_day' => $note->n_day,
+ 'n_month' => $note->n_month,
+ 'n_year' => $note->n_year,
+ 'n_time_type' => 'C',
+ 'n_reason' => 'LEAVE_WITHOUT_PAY',
+ 'n_note' => $note->n_note,
+ 'ticket_id' => $note->ticket_id
+ ]);
+
+ $note->delete();
+
+ $onleave_days_will_use = 0;
+ $nopay_days_will_use -= 0.5;
+ } elseif ($onleave_days_will_use > 0) {
+ // Dùng ngày phép trước
+ $use = min($onleave_days_will_use, $value);
+ $note->update([
+ 'n_reason' => "ONLEAVE"
+ ]);
+ $onleave_days_will_use -= $use;
+ } elseif ($nopay_days_will_use > 0) {
+ // Hết phép, chuyển sang không phép
+ $use = min($nopay_days_will_use, $value);
+ $note->update([
+ 'n_reason' => "LEAVE_WITHOUT_PAY"
+ ]);
+ $nopay_days_will_use -= $use;
+ }
+ }
+ }
+ // Đủ phép
+ else {
+ foreach ($notes as $note) {
+ $note->update([
+ 'n_reason' => "ONLEAVE"
+ ]);
+ }
+ }
+ }
+ }
+
+ 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;
+ }
+}
diff --git a/BACKEND/app/Models/LeaveDays.php b/BACKEND/app/Models/LeaveDays.php
index 67b2024..a974950 100644
--- a/BACKEND/app/Models/LeaveDays.php
+++ b/BACKEND/app/Models/LeaveDays.php
@@ -10,7 +10,7 @@ class LeaveDays extends Model
use HasFactory;
protected $fillable = [
- 'id', 'ld_user_id', 'ld_day', 'ld_year', 'ld_date_additional', 'ld_note'
+ 'id', 'ld_user_id', 'ld_day_total', 'ld_year', 'ld_additional_day', 'ld_note'
];
protected $table = 'leave_days';
diff --git a/BACKEND/app/Models/Notes.php b/BACKEND/app/Models/Notes.php
index 57e4e49..f1a90a7 100644
--- a/BACKEND/app/Models/Notes.php
+++ b/BACKEND/app/Models/Notes.php
@@ -11,7 +11,14 @@ class Notes extends Model
use HasFactory;
protected $fillable = [
- 'n_user_id', 'n_day', 'n_month', 'n_year', 'n_time_type', 'n_reason', 'n_note',
+ 'n_user_id',
+ 'n_day',
+ 'n_month',
+ 'n_year',
+ 'n_time_type',
+ 'n_reason',
+ 'n_note',
+ 'ticket_id'
];
/**
@@ -25,7 +32,7 @@ class Notes extends Model
{
return self::leftJoin("categories as reason", function ($join) {
$join->on('n_reason', '=', 'reason.c_code');
- $join->on('reason.c_type', DB::raw("CONCAT('REASON')"));
+ $join->on('reason.c_type', DB::raw("CONCAT('REASON_NOTES')"));
})
->leftJoin("categories as timeTypes", function ($join) {
$join->on('n_time_type', '=', 'timeTypes.c_code');
@@ -47,4 +54,18 @@ class Notes extends Model
)
->get();
}
+
+ public static function getNotesByMonthAndYearAndUserId($month, $year, $userId, $idNote)
+ {
+ return self::where('n_reason', 'ONLEAVE')->where('n_month', $month)->where('n_year', $year)
+ ->where('n_user_id', $userId)
+ ->where('id', '!=', $idNote)->get();
+ }
+
+ public static function getNotesByMonthAndYearAndUserIdAndReason($month, $year, $userId, $reason)
+ {
+ return self::where('n_reason', $reason)->where('n_month', $month)->where('n_year', $year)
+ ->where('n_user_id', $userId)
+ ->orderBy('n_day', 'asc')->orderBy('n_time_type', 'desc')->get();
+ }
}
diff --git a/BACKEND/config/app.php b/BACKEND/config/app.php
index 6e6a756..8b3161b 100755
--- a/BACKEND/config/app.php
+++ b/BACKEND/config/app.php
@@ -59,6 +59,8 @@ return [
'asset_url' => env('ASSET_URL'),
+ 'client_url' => env('ADMIN_URL', 'http://localhost'),
+
/*
|--------------------------------------------------------------------------
| Application Timezone
diff --git a/BACKEND/database/migrations/2025_03_13_070714_rename_ld_day_to_ld_day_total_in_leave_days_table.php b/BACKEND/database/migrations/2025_03_13_070714_rename_ld_day_to_ld_day_total_in_leave_days_table.php
new file mode 100644
index 0000000..e280937
--- /dev/null
+++ b/BACKEND/database/migrations/2025_03_13_070714_rename_ld_day_to_ld_day_total_in_leave_days_table.php
@@ -0,0 +1,22 @@
+renameColumn('ld_day', 'ld_day_total');
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('leave_days', function (Blueprint $table) {
+ $table->renameColumn('ld_day_total', 'ld_day');
+ });
+ }
+}
diff --git a/BACKEND/database/migrations/2025_03_13_075133_add_ld_special_leave_day_to_leave_days_table.php b/BACKEND/database/migrations/2025_03_13_075133_add_ld_special_leave_day_to_leave_days_table.php
new file mode 100644
index 0000000..863d79f
--- /dev/null
+++ b/BACKEND/database/migrations/2025_03_13_075133_add_ld_special_leave_day_to_leave_days_table.php
@@ -0,0 +1,22 @@
+float('ld_special_leave_day')->default(0); // Adding the new field
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('leave_days', function (Blueprint $table) {
+ $table->dropColumn('ld_special_leave_day'); // Dropping the field if needed
+ });
+ }
+}
diff --git a/BACKEND/database/migrations/2025_03_13_075235_rename_ld_date_additional_to_ld_additional_day_in_leave_days_table.php b/BACKEND/database/migrations/2025_03_13_075235_rename_ld_date_additional_to_ld_additional_day_in_leave_days_table.php
new file mode 100644
index 0000000..b6be1ae
--- /dev/null
+++ b/BACKEND/database/migrations/2025_03_13_075235_rename_ld_date_additional_to_ld_additional_day_in_leave_days_table.php
@@ -0,0 +1,22 @@
+renameColumn('ld_date_additional', 'ld_additional_day');
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('leave_days', function (Blueprint $table) {
+ $table->renameColumn('ld_date_additional', 'ld_additional_day');
+ });
+ }
+}
\ No newline at end of file
diff --git a/BACKEND/database/migrations/2025_03_13_083500_update_name_in_categories_table.php b/BACKEND/database/migrations/2025_03_13_083500_update_name_in_categories_table.php
new file mode 100644
index 0000000..9cd370f
--- /dev/null
+++ b/BACKEND/database/migrations/2025_03_13_083500_update_name_in_categories_table.php
@@ -0,0 +1,41 @@
+insert([
+ [
+ 'c_code' => 'LEAVE_WITHOUT_PAY',
+ 'c_name' => 'Nghỉ không hưởng lương',
+ 'c_type' => 'REASON',
+ 'c_value' => "",
+ 'c_active' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ],
+ ]);
+
+ DB::table('categories')
+ ->where('c_name', 'Nghỉ phép')
+ ->update(['c_name' => 'Nghỉ phép năm']);
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ DB::table('categories')
+ ->where('c_name', 'Nghỉ phép năm')
+ ->update(['c_name' => 'Nghỉ phép']);
+ }
+};
diff --git a/BACKEND/database/migrations/2025_05_07_023335_update_leave_categories.php b/BACKEND/database/migrations/2025_05_07_023335_update_leave_categories.php
new file mode 100644
index 0000000..ddb1b92
--- /dev/null
+++ b/BACKEND/database/migrations/2025_05_07_023335_update_leave_categories.php
@@ -0,0 +1,52 @@
+where('c_type', 'REASON')
+ ->where('c_code', 'LEAVE_WITHOUT_PAY')
+ ->delete();
+
+ // Cập nhật tên "Nghỉ phép năm" thành "Nghỉ phép"
+ DB::table('categories')
+ ->where('c_name', 'Nghỉ phép năm')
+ ->update(['c_name' => 'Nghỉ phép']);
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ // Khôi phục item đã xóa
+ DB::table('categories')->insert([
+ 'c_code' => 'LEAVE_WITHOUT_PAY',
+ 'c_name' => 'Không phép',
+ 'c_type' => 'REASON',
+ 'c_value' => "",
+ 'c_active' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]);
+
+ // Khôi phục tên cũ
+ DB::table('categories')
+ ->where('c_name', 'Nghỉ phép')
+ ->update(['c_name' => 'Nghỉ phép năm']);
+ }
+}
diff --git a/BACKEND/database/migrations/2025_05_07_024806_add_limit_leave_month_category.php b/BACKEND/database/migrations/2025_05_07_024806_add_limit_leave_month_category.php
new file mode 100644
index 0000000..91cb49f
--- /dev/null
+++ b/BACKEND/database/migrations/2025_05_07_024806_add_limit_leave_month_category.php
@@ -0,0 +1,40 @@
+insert([
+ 'c_code' => 'LIMIT',
+ 'c_name' => 'Giới hạn số ngày nghỉ có phép/tháng',
+ 'c_type' => 'LIMIT_LEAVE_MONTH',
+ 'c_value' => '3',
+ 'c_active' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]);
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ DB::table('categories')
+ ->where('c_code', 'LIMIT')
+ ->where('c_type', 'LIMIT_LEAVE_MONTH')
+ ->delete();
+ }
+}
diff --git a/BACKEND/database/migrations/2025_05_07_024949_add_saturday_work_schedule_category.php b/BACKEND/database/migrations/2025_05_07_024949_add_saturday_work_schedule_category.php
new file mode 100644
index 0000000..c0e51c8
--- /dev/null
+++ b/BACKEND/database/migrations/2025_05_07_024949_add_saturday_work_schedule_category.php
@@ -0,0 +1,40 @@
+insert([
+ 'c_code' => '10-05-2025',
+ 'c_name' => 'Ngày bắt đầu làm việc thứ 7 trong năm',
+ 'c_type' => 'SATURDAY_WORK_SCHEDULE',
+ 'c_value' => '2025',
+ 'c_active' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]);
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ DB::table('categories')
+ ->where('c_code', '10-05-2025')
+ ->where('c_type', 'SATURDAY_WORK_SCHEDULE')
+ ->delete();
+ }
+}
diff --git a/BACKEND/database/migrations/2025_05_07_062606_add_day_work_special_category.php b/BACKEND/database/migrations/2025_05_07_062606_add_day_work_special_category.php
new file mode 100644
index 0000000..c73131d
--- /dev/null
+++ b/BACKEND/database/migrations/2025_05_07_062606_add_day_work_special_category.php
@@ -0,0 +1,40 @@
+insert([
+ 'c_code' => '17-05-2025',
+ 'c_name' => 'Ngày làm việc đặc biệt',
+ 'c_type' => 'DAY_WORK_SPECIAL',
+ 'c_value' => '2025',
+ 'c_active' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ]);
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ DB::table('categories')
+ ->where('c_code', '17-05-2025')
+ ->where('c_type', 'DAY_WORK_SPECIAL')
+ ->delete();
+ }
+}
diff --git a/BACKEND/database/migrations/2025_05_07_092903_add_leave_categories.php b/BACKEND/database/migrations/2025_05_07_092903_add_leave_categories.php
new file mode 100644
index 0000000..30ffb7f
--- /dev/null
+++ b/BACKEND/database/migrations/2025_05_07_092903_add_leave_categories.php
@@ -0,0 +1,60 @@
+insert([
+ [
+ 'c_code' => 'LEAVE_WITHOUT_PAY',
+ 'c_name' => 'Không phép',
+ 'c_type' => 'REASON_NOTES',
+ 'c_value' => "",
+ 'c_active' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ],
+ [
+ 'c_code' => 'WFH',
+ 'c_name' => 'Work From Home',
+ 'c_type' => 'REASON_NOTES',
+ 'c_value' => "",
+ 'c_active' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ],
+ [
+ 'c_code' => 'ONLEAVE',
+ 'c_name' => 'Nghỉ phép',
+ 'c_type' => 'REASON_NOTES',
+ 'c_value' => "",
+ 'c_active' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ],
+ ]);
+ }
+
+ /**
+ * Reverse the migrations.
+ *
+ * @return void
+ */
+ public function down()
+ {
+ DB::table('categories')
+ ->whereIn('c_code', ['LEAVE_WITHOUT_PAY', 'WFH', 'ONLEAVE'])
+ ->where('c_type', 'REASON_NOTES')
+ ->delete();
+ }
+}
diff --git a/BACKEND/database/migrations/2025_06_20_050728_add_ticket_relation_notes_table.php b/BACKEND/database/migrations/2025_06_20_050728_add_ticket_relation_notes_table.php
new file mode 100644
index 0000000..1a9b528
--- /dev/null
+++ b/BACKEND/database/migrations/2025_06_20_050728_add_ticket_relation_notes_table.php
@@ -0,0 +1,26 @@
+foreignId('ticket_id')
+ ->nullable()
+ ->constrained('tickets')
+ ->onDelete('cascade');
+ });
+ }
+
+ public function down(): void
+ {
+ Schema::table('notes', function (Blueprint $table) {
+ $table->dropForeign(['ticket_id']);
+ $table->dropColumn('ticket_id');
+ });
+ }
+};
diff --git a/BACKEND/database/migrations/2025_06_23_098764_add_permanent_users_table.php b/BACKEND/database/migrations/2025_06_23_098764_add_permanent_users_table.php
new file mode 100644
index 0000000..d761819
--- /dev/null
+++ b/BACKEND/database/migrations/2025_06_23_098764_add_permanent_users_table.php
@@ -0,0 +1,31 @@
+boolean('is_permanent')->default(true);
+ $table->date('permanent_date');
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::table('users', function (Blueprint $table) {
+ $table->dropColumn('is_permanent');
+ $table->dropColumn('permanent_date');
+ });
+ }
+};
diff --git a/BACKEND/database/migrations/2025_06_30_025714_add_onleave_permanent_temporary_categories_table.php b/BACKEND/database/migrations/2025_06_30_025714_add_onleave_permanent_temporary_categories_table.php
new file mode 100644
index 0000000..968b07d
--- /dev/null
+++ b/BACKEND/database/migrations/2025_06_30_025714_add_onleave_permanent_temporary_categories_table.php
@@ -0,0 +1,45 @@
+insert([
+ [
+ 'c_code' => 'PERMANENT',
+ 'c_name' => 'Phép cộng nhân viên chính thức',
+ 'c_type' => 'PERMANENT_ONLEAVE',
+ 'c_value' => 1,
+ 'c_active' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ],
+ [
+ 'c_code' => 'TEMPORARY_ONLEAVE',
+ 'c_name' => 'Nghỉ dự kiến',
+ 'c_type' => 'REASON_NOTES',
+ 'c_value' => "",
+ 'c_active' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ],
+ ]);
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ DB::table('categories')->where('c_code', 'PERMANENT')->where('c_type', 'PERMANENT_ONLEAVE')->delete();
+ DB::table('categories')->where('c_code', 'TEMPORARY_ONLEAVE')->where('c_type', 'REASON_NOTES')->delete();
+ }
+};
diff --git a/BACKEND/resources/views/email/notification_tickets.blade.php b/BACKEND/resources/views/email/notification_tickets.blade.php
index a013eed..f9c4d13 100644
--- a/BACKEND/resources/views/email/notification_tickets.blade.php
+++ b/BACKEND/resources/views/email/notification_tickets.blade.php
@@ -1,4 +1,3 @@
-
@@ -37,119 +36,167 @@
-
© 2024 APAC Tech. + + |
+