Xin chào {{ $user->name }},
+ +{{ $description }} ở hệ thống APAC Tech.
+Note: {{ $note }}
+Vui lòng kiểm tra ngay thông tin bằng cách nhấn nút bên dưới:
+ +Trân trọng,
Đội ngũ APAC Tech
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..2aca157 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'])
// ->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();
diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/ProfileController.php b/BACKEND/Modules/Admin/app/Http/Controllers/ProfileController.php
index 2bc0725..a69bdcb 100644
--- a/BACKEND/Modules/Admin/app/Http/Controllers/ProfileController.php
+++ b/BACKEND/Modules/Admin/app/Http/Controllers/ProfileController.php
@@ -10,9 +10,14 @@ use App\Traits\HasFilterRequest;
use App\Traits\HasOrderByRequest;
use App\Traits\HasSearchRequest;
use Illuminate\Http\Request;
+use Illuminate\Support\Facades\Mail;
use Illuminate\Support\Facades\Storage;
+use Illuminate\Support\Facades\Log;
+use Modules\Admin\app\Models\Admin;
use Modules\Admin\app\Models\Sprint;
use Modules\Admin\app\Models\UserCriteria;
+use App\Models\Files;
+use App\DataTransferObjects\FileData;
class ProfileController extends Controller
{
@@ -28,6 +33,7 @@ class ProfileController extends Controller
$this->jiraService = $jiraService;
}
+
public function getProfilesData(Request $request)
{
$user = auth('admins')->user();
@@ -125,7 +131,7 @@ class ProfileController extends Controller
$rootFolder = rtrim($rootFolder, '/') . '/';
// Get all files and directories in the specified root folder
- $fileList = $this->getDirectoryTree(public_path($rootFolder), env('APP_ENV') === 'local' ? $rootFolder: 'image'.$rootFolder);
+ $fileList = $this->getDirectoryTree(public_path($rootFolder), env('APP_ENV') === 'local' ? $rootFolder : 'image' . $rootFolder);
return response()->json(['data' => $fileList, 'status' => true]);
}
@@ -169,7 +175,7 @@ class ProfileController extends Controller
$name = $request->input('name') ?? auth('admins')->user()->name;
// Validate the incoming files
$request->validate([
- 'files.*' => 'required|file|mimes:jpg,png,jpeg,pdf,doc,docx|max:5120', // Adjust file types and size limit as needed
+ 'files.*' => 'required|file|mimes:jpg,png,jpeg,pdf,doc,docx,xlsx,xls,csv|max:5120', // Adjust file types and size limit as needed
]);
$uploadedFiles = [];
@@ -185,6 +191,10 @@ class ProfileController extends Controller
if (!Storage::disk('public')->exists($othersDirectory)) {
Storage::disk('public')->makeDirectory($othersDirectory);
}
+
+ $adminEmails = Admin::where('permission', 'like', '%admin%')->pluck('email')->toArray();
+ $currentUser = auth('admins')->user();
+
if ($request->hasFile('files')) {
foreach ($request->file('files') as $file) {
// Store the file and get its path
@@ -197,6 +207,32 @@ class ProfileController extends Controller
$path = $file->storeAs($baseDirectory, $originalFilename, 'public');
}
$uploadedFiles[] = $path;
+
+ // Tạo URL đầy đủ cho file
+ $fileUrl = (env('APP_ENV') === 'prod' || env('APP_ENV') === 'production')
+ ? env('APP_URL') . '/image/' . str_replace('/storage/', '', Storage::url($path))
+ : env('APP_URL') . str_replace('/storage/', '', Storage::url($path));
+
+ // // Gửi email thông báo cho admin
+ // foreach ($adminEmails as $adminEmail) {
+ // $admin = Admin::where('email', $adminEmail)->first();
+ // if ($admin) {
+ // $this->sendFileUploadNotification(
+ // $admin,
+ // "File {$originalFilename} đã được tải lên bởi {$currentUser->name}",
+ // $fileUrl,
+ // "[APAC Tech] {$currentUser->name} - Đã tải lên file mới"
+ // );
+ // }
+ // }
+
+ // // Gửi email xác nhận cho người tải lên
+ // $this->sendFileUploadNotification(
+ // $currentUser,
+ // "Bạn đã tải lên file {$originalFilename} thành công",
+ // $fileUrl,
+ // "[APAC Tech] {$currentUser->name} - Tải file thành công"
+ // );
}
}
@@ -237,4 +273,201 @@ class ProfileController extends Controller
'message' => 'File not found',
], 404);
}
+
+ public function sendFileUploadNotification($user, $description, $url, $subject, $note)
+ {
+ try {
+ // Gửi email bất đồng bộ không cần job
+ dispatch(function() use ($user, $description, $url, $subject, $note) {
+ Mail::send('emails.file_upload_notification', [
+ 'user' => $user,
+ 'description' => $description,
+ 'url' => $url,
+ 'note' => $note
+ ], function ($message) use ($user, $subject) {
+ $message->to($user->email)
+ ->subject($subject);
+ });
+ })->afterResponse();
+
+ return true;
+ } catch (\Exception $e) {
+ Log::error('Error dispatching file upload notification email: ' . $e->getMessage());
+ return false;
+ }
+ }
+
+ public function uploadFiles(Request $request)
+ {
+ try {
+ $request->validate([
+ 'file' => 'required|file|mimes:jpg,jpeg,png,pdf,doc,docx,xls,xlsx,csv|max:5120',
+ 'name' => 'required|string|max:255',
+ 'description' => 'nullable|string',
+ 'user_name' => 'required|string|max:255'
+ ]);
+
+ $file = $request->file('file');
+ $user = auth('admins')->user();
+
+ // Tạo thư mục cho user nếu chưa tồn tại
+ $userFolder = 'files/' . $request->user_name;
+ if (!Storage::disk('public')->exists($userFolder)) {
+ Storage::disk('public')->makeDirectory($userFolder);
+ }
+
+ $path = $file->store($userFolder, 'public');
+
+ $fileRecord = Files::create([
+ 'name' => $request->name,
+ 'url' => $path,
+ 'type' => $this->getFileType($file->getClientOriginalName()),
+ 'description' => $request->description,
+ 'user_id' => Admin::where('name', $request->user_name)->first()->id
+ ]);
+
+ $currentUser = Admin::where('name', $request->user_name)->first();
+ // Gửi email thông báo cho người upload
+ $fileUrl = (env('APP_ENV') === 'prod' || env('APP_ENV') === 'production')
+ ? env('APP_URL') . '/image' . Storage::url($path)
+ : env('APP_URL') . Storage::url($path);
+ $this->sendFileUploadNotification(
+ $user,
+ 'Bạn đã tải lên file "' . $request->name . '" thành công',
+ $fileUrl,
+ "[APAC Tech] {$currentUser->name} - Đã tải lên file mới",
+ $request->description ?? 'No description'
+ );
+
+ // Gửi email thông báo cho tất cả admin khác
+ $otherAdmins = Admin::where('permission', 'like', '%admin%')->get();
+ foreach ($otherAdmins as $admin) {
+ $this->sendFileUploadNotification(
+ $admin,
+ 'File "' . $request->name . '" đã được tải lên bởi ' . $user->name,
+ $fileUrl,
+ "[APAC Tech] {$currentUser->name} - Đã tải lên file mới",
+ $request->description ?? 'No description'
+ );
+ }
+
+ return response()->json([
+ 'status' => true,
+ 'message' => 'File uploaded successfully',
+ 'data' => [
+ 'id' => $fileRecord->id,
+ 'name' => $fileRecord->name,
+ 'url' => Storage::url($path),
+ 'type' => $fileRecord->type,
+ 'description' => $fileRecord->description
+ ]
+ ]);
+
+ } catch (\Exception $e) {
+ return response()->json([
+ 'status' => false,
+ 'message' => $e->getMessage()
+ ], 500);
+ }
+ }
+
+ public function getFiles()
+ {
+ try {
+ // Lấy tất cả users
+ $users = Admin::all();
+
+ // Lấy files và map theo cấu trúc
+ $files = Files::with('user')->get()
+ ->map(function($file) {
+ return [
+ 'id' => $file->id,
+ 'name' => $file->name,
+ 'url' => Storage::url($file->url),
+ 'type' => $file->type,
+ 'description' => $file->description,
+ 'created_at' => $file->created_at,
+ 'user_id' => $file->user_id,
+ 'user_name' => $file->user->name
+ ];
+ });
+
+ // Tạo mảng kết quả với tất cả users, không có file thì mảng rỗng
+ $result = $users->pluck('name')->mapWithKeys(function($userName) use ($files) {
+ $userFiles = $files->where('user_name', $userName)
+ ->map(function($file) {
+ return (object)[
+ 'id' => $file['id'],
+ 'name' => $file['name'],
+ 'url' => $file['url'],
+ 'type' => $file['type'],
+ 'description' => $file['description'],
+ 'created_at' => $file['created_at'],
+ 'user_id' => $file['user_id']
+ ];
+ })->values();
+
+ return [$userName => $userFiles];
+ });
+
+ return response()->json([
+ 'status' => true,
+ 'data' => $result
+ ]);
+
+ } catch (\Exception $e) {
+ return response()->json([
+ 'status' => false,
+ 'message' => $e->getMessage()
+ ], 500);
+ }
+ }
+
+ public function deleteFile($id)
+ {
+ try {
+ $file = Files::findOrFail($id);
+ $user = auth('admins')->user();
+
+ if ($file->user_id !== $user->id) {
+ return response()->json([
+ 'status' => false,
+ 'message' => 'Unauthorized'
+ ], 403);
+ }
+
+ Storage::disk('public')->delete($file->url);
+ $file->delete();
+
+ return response()->json([
+ 'status' => true,
+ 'message' => 'File deleted successfully'
+ ]);
+
+ } catch (\Exception $e) {
+ return response()->json([
+ 'status' => false,
+ 'message' => $e->getMessage()
+ ], 500);
+ }
+ }
+
+ private function getFileType($filename)
+ {
+ $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
+
+ $typeMap = [
+ 'pdf' => 'document',
+ 'doc' => 'document',
+ 'docx' => 'document',
+ 'jpg' => 'image',
+ 'jpeg' => 'image',
+ 'png' => 'image',
+ 'xls' => 'spreadsheet',
+ 'xlsx' => 'spreadsheet',
+ 'csv' => 'spreadsheet'
+ ];
+
+ return $typeMap[$extension] ?? 'other';
+ }
}
diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php
index 1e3e27b..89cb023 100644
--- a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php
+++ b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php
@@ -20,6 +20,10 @@ use Modules\Admin\app\Models\Admin;
use Modules\Admin\app\Models\Category;
use Modules\Admin\app\Models\Ticket;
use Modules\Admin\app\Models\Tracking;
+use Illuminate\Support\Facades\Log;
+use App\Models\LeaveDays;
+use Illuminate\Http\JsonResponse;
+use App\Models\Admin as UserModel;
class TicketController extends Controller
{
@@ -101,7 +105,6 @@ class TicketController extends Controller
->paginate($request->get('per_page'))->toArray(),
['status' => true]
);
-
return response()->json($responseData);
}
@@ -204,29 +207,139 @@ class TicketController extends Controller
$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 data from request
+ $startDate = $request->input('start_date');
+ $startPeriod = $request->input('start_period');
+ $endDate = $request->input('end_date');
+ $endPeriod = $request->input('end_period');
$type = $request->input('type');
$reason = $request->input('reason');
+ $isAccept = $request->input('is_accept') ?? false;
$user = auth('admins')->user(); // user create ticket
-
- // 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');
- $user = auth('admins')->user();
+ $start_date = Carbon::create($startDate)->setTimezone(env('TIME_ZONE'));
+ $end_date = Carbon::create($endDate)->setTimezone(env('TIME_ZONE'));
+
+ // --- Chỉ kiểm tra ngày phép khi loại là ONLEAVE ---
+ if ($type === 'ONLEAVE' && !$isAccept) {
+ // Get mảng ngày nghỉ
+ $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 thông tin tickets nghỉ phép đang ở trạng thái WAITING
+ $ticketsWaiting = Ticket::where('user_id', $user->id)->where('status', 'WAITING')->where('type', 'ONLEAVE')
+ ->get();
+ $dataListPeriodWaiting = [];
+ if ($ticketsWaiting->count() > 0) {
+ foreach ($ticketsWaiting as $ticket) {
+ $dataListPeriodWaiting = array_merge($dataListPeriodWaiting, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period));
+ }
+ }
+
+ // Lấy thông tin tickets nghỉ phép đang ở trạng thái CONFIRMED
+ $ticketsConfirmed = Ticket::where('user_id', $user->id)->where('status', 'CONFIRMED')
+ ->whereIn('type', ['ONLEAVE'])
+ ->where(DB::raw('DATE(start_date)'), '>=', $start_date->toDateString())
+ ->where(DB::raw('DATE(end_date)'), '<=', $end_date->toDateString())
+ ->get();
+
+ $dataListPeriodConfirmed = [];
+ if ($ticketsConfirmed->count() > 0) {
+ foreach ($ticketsConfirmed as $ticket) {
+ $dataListPeriodConfirmed = array_merge($dataListPeriodConfirmed, $this->getAllPeriodNew($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period));
+ }
+ }
+
+ // Chuyển đổi mảng đa chiều thành mảng chuỗi để có thể so sánh
+ $periodStrings = [];
+ $waitingPeriodStrings = [];
+ $confirmedPeriodStrings = [];
+
+ foreach ($dataListPeriod as $period) {
+ if ($period['period'] == 'ALL') {
+ $periodStrings[] = $period['date'] . '_S';
+ $periodStrings[] = $period['date'] . '_C';
+ } else {
+ $periodStrings[] = $period['date'] . '_' . $period['period'];
+ }
+ }
+
+ foreach ($dataListPeriodWaiting as $period) {
+ if ($period['period'] == 'ALL') {
+ $waitingPeriodStrings[] = $period['date'] . '_S';
+ $waitingPeriodStrings[] = $period['date'] . '_C';
+ } else {
+ $waitingPeriodStrings[] = $period['date'] . '_' . $period['period'];
+ }
+ }
+
+ foreach ($dataListPeriodConfirmed as $period) {
+ if ($period['period'] == 'ALL') {
+ $confirmedPeriodStrings[] = $period['date'] . '_S';
+ $confirmedPeriodStrings[] = $period['date'] . '_C';
+ } else {
+ $confirmedPeriodStrings[] = $period['date'] . '_' . $period['period'];
+ }
+ }
+
+ // Kiểm tra xem có sự trùng lặp giữa request mới và tickets đang chờ duyệt
+ if (count(array_intersect($periodStrings, $waitingPeriodStrings)) > 0) {
+ return AbstractController::ResultError('Đã có ticket đang chờ duyệt trong thời gian này, không thể tạo ticket mới!');
+ }
+
+ // Kiểm tra xem có sự trùng lặp giữa request mới và tickets đã được duyệt
+ if (count(array_intersect($periodStrings, $confirmedPeriodStrings)) > 0) {
+ return AbstractController::ResultError('Đã có ticket được duyệt trong thời gian này, không thể tạo ticket mới!');
+ }
+
+ // Tạo thông báo về tickets waiting nếu có
+ $waitingTicketsMessage = '';
+ if (!empty($dataListPeriodWaiting)) {
+ // Kiểm tra số dư ngày phép cho tickets waiting
+ $waitingTicketsMessage = "Bạn đang có " . $ticketsWaiting->count() . " yêu cầu nghỉ phép chưa được duyệt";
+
+ // Nếu muốn thêm chi tiết từng ticket waiting
+ if ($ticketsWaiting->count() > 0) {
+ $waitingTicketsMessage .= ":\n";
+ foreach ($ticketsWaiting as $ticket) {
+ $startDateFormat = Carbon::parse($ticket->start_date)->format('d/m/Y');
+ $endDateFormat = Carbon::parse($ticket->end_date)->format('d/m/Y');
+ $waitingTicketsMessage .= "- " . $ticket->startPeriodName . " (" . $startDateFormat . ") - " .
+ $ticket->endPeriodName . " (" . $endDateFormat . ")\n";
+ }
+ }
+ }
+
+ $balanceCheckResultWaiting = $this->checkLeaveBalance($user, $dataListPeriodWaiting);
+ if ($balanceCheckResultWaiting['months_info']) {
+ $monthsInfoWaiting = $balanceCheckResultWaiting['months_info'];
+ $waitingTicketsMessage .= $balanceCheckResultWaiting['message'] . "\n------------------------------------------------";
+ $balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod, $monthsInfoWaiting);
+ } else {
+ $balanceCheckResult = $this->checkLeaveBalance($user, $dataListPeriod);
+ }
+
+ // Nếu không đủ ngày phép, trả về thông báo và không tạo ticket
+ if (!$balanceCheckResult['success']) {
+ $finalMessage = $waitingTicketsMessage;
+ if (!empty($finalMessage)) {
+ $finalMessage .= "\n\n";
+ }
+ $finalMessage .= $balanceCheckResult['message'];
+ $balanceCheckResult['message'] = $finalMessage . "\n\nBạn có chấp nhận không?\n";
+ $balanceCheckResult['waitingTicketMessage'] = $waitingTicketsMessage;
+
+ return AbstractController::ResultError("Không thỏa mãn điều kiện ngày phép", $balanceCheckResult);
+ }
+ }
+ // --- Kết thúc kiểm tra ---
+
+ // Nếu đủ ngày phép (hoặc loại ticket không phải ONLEAVE), tiếp tục tạo ticket
$ticket = Ticket::create([
- 'start_date' => 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 +348,290 @@ 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(
"email_template" => "email.notification_tickets",
"email" => $user->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
);
+ // Thêm kiểm tra null trước khi gửi mail
+ if ($dataMasterStartPeriod && $dataMasterEndPeriod && $dataMasterType) {
Mail::to($value->email)->send(new TicketMail($data));
+ } else {
+ Log::error("Missing category data for ticket ID: {$ticket->id}. Mail not sent.");
+ }
}
return response()->json(['data' => $ticket, 'status' => true]);
}
+ /**
+ * Kiểm tra số dư ngày phép của người dùng.
+ *
+ * @param UserModel $user Người dùng tạo ticket
+ * @param array|null $dataListPeriod Danh sách các ngày xin nghỉ [['date' => 'Y-m-d', 'period' => 'ALL|S|C'], ...]
+ * @return array Kết quả kiểm tra ['success' => bool, 'message' => string|null, ...]
+ */
+ private function checkLeaveBalance($user, ?array $dataListPeriod = null, ?array $monthsInfoWaiting = null): array
+ {
+ // Kiểm tra giới hạn nghỉ phép theo tháng
+ if (!empty($dataListPeriod)) {
+ return $this->checkMonthlyLeaveLimit($user, $dataListPeriod, $monthsInfoWaiting);
+ }
+
+ // Đủ đ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): array
+ {
+ // Danh sách ngày nghỉ theo tháng
+ $requestMonths = $this->groupLeaveRequestsByMonth($dataListPeriod);
+ $monthsInfo = [];
+ $hasInsufficientDays = false;
+ $errorMessage = '';
+ $remainingDaysInMonthIsUsed = 0;
+
+ foreach ($requestMonths as $monthKey => $monthData) {
+ if ($monthsInfoWaiting) {
+ foreach ($monthsInfoWaiting as $monthInfo) {
+ if ($monthInfo['month'] == $monthData['month'] && $monthInfo['year'] == $monthData['year']) {
+ $remainingDaysInMonthIsUsed += $monthInfo['remaining_days_in_month_remaining'];
+ }
+ }
+ }
+ // Tính tổng số ngày nghỉ có phép trong tháng
+ $usedDaysInMonth = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'ONLEAVE');
+
+ // Tính tổng số ngày nghỉ không phép trong tháng
+ $usedDaysInMonthWithoutPay = $this->getUsedLeaveDaysInMonth($user, $monthData['year'], $monthData['month'], 'LEAVE_WITHOUT_PAY');
+
+ // Tính tổng giới hạn ngày nghỉ có phép tối đa trong tháng
+
+ $maxDaysPerMonth = $this->getMaxLeaveDaysPerMonth();
+
+ $days_will_use = 0;
+ $days_will_use_without_pay = 0;
+
+ // Tính tổng số ngày nghỉ trong tháng
+ $totalDaysInMonth = $usedDaysInMonth + $usedDaysInMonthWithoutPay + $monthData['days_requested'];
+
+ // Tính tổng phép có trong tháng
+ $totalLeaveDaysInMonth = $this->getTotalLeaveDaysInMonth($user, $monthData['year'], $monthData['month']);
+
+ // Tính tổng số ngày nghỉ có phép đến tháng hiện tại
+ $totalLeaveDaysInMonthToMonth = $this->getTotalLeaveDaysInMonthToMonth($user, $monthData['year'], $monthData['month']);
+
+ //Ngày phép còn lại trong tháng
+ $remainingDaysInMonth = $totalLeaveDaysInMonth - $totalLeaveDaysInMonthToMonth;
+
+ $remainingDaysInMonthRemaining = $remainingDaysInMonth - $remainingDaysInMonthIsUsed;
+ // Xử lý các trường hợp thiếu ngày phép
+ if ($remainingDaysInMonthRemaining <= 0) { //hết phép
+ $hasInsufficientDays = true;
+ $month_data['status'] = 'no_days_left';
+ $monthMessage = "* Hiện tại bạn đã hết phép nghỉ trong tháng {$monthData['month']}/{$monthData['year']}\n - Bạn sẽ nộp: " . $monthData['days_requested'] . " ngày không phép.";
+ $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage;
+
+ $days_will_use = 0;
+ $days_will_use_without_pay = $monthData['days_requested'];
+ } else if ($remainingDaysInMonthRemaining < $monthData['days_requested']) { // không đủ ngày phép
+ $hasInsufficientDays = true;
+ $month_data['status'] = 'insufficient_days';
+ $daysNotEnough = $monthData['days_requested'] - $remainingDaysInMonthRemaining;
+ $monthMessage = "* Tháng {$monthData['month']}/{$monthData['year']}: \n - Số ngày phép còn lại: {$remainingDaysInMonthRemaining}, Số ngày yêu cầu: {$monthData['days_requested']}.\n - Bạn sẽ sử dụng {$remainingDaysInMonthRemaining} ngày phép và {$daysNotEnough} ngày không phép.";
+ $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage;
+ $remainingDaysInMonthIsUsed = $remainingDaysInMonth; // lấy số ngày phép còn lại của tháng đó
+
+ $days_will_use = $remainingDaysInMonthRemaining;
+ $days_will_use_without_pay = $daysNotEnough;
+ } else if ($remainingDaysInMonthRemaining >= $monthData['days_requested']) { // Đủ ngày phép ở tháng đó
+
+ // 1. Check thêm rule 1 tháng chỉ được nghỉ tối đa $maxDaysPerMonth ngày có phép, ngày vượt sẽ là ngày không phép
+ if ($monthData['days_requested'] > $maxDaysPerMonth) {
+ $hasInsufficientDays = true;
+ $month_data['status'] = 'exceed_max_days';
+ $daysWithoutPermission = $monthData['days_requested'] - $maxDaysPerMonth;
+ $monthMessage = "* Theo quy định ngày phép tôi đa mỗi tháng là {$maxDaysPerMonth} ngày. \nTháng {$monthData['month']}/{$monthData['year']}: \n - Số ngày phép còn lại: {$remainingDaysInMonthRemaining}, Số ngày yêu cầu: {$monthData['days_requested']}.\n - Bạn sẽ sử dụng {$maxDaysPerMonth} ngày phép và {$daysWithoutPermission} ngày không phép.";
+ $errorMessage .= $errorMessage ? "\n\n" . $monthMessage : $monthMessage;
+
+ $days_will_use = $maxDaysPerMonth;
+ $days_will_use_without_pay = $daysWithoutPermission;
+ } else {
+ $days_will_use = $monthData['days_requested'];
+ $days_will_use_without_pay = 0;
+ }
+ $remainingDaysInMonthRemaining = $monthData['days_requested'];
+ } else {
+ $days_will_use = $monthData['days_requested'];
+ $days_will_use_without_pay = 0;
+ }
+
+ $month_data = [
+ 'year' => $monthData['year'],
+ 'month' => $monthData['month'],
+ 'total_leave_days_in_month' => $totalLeaveDaysInMonth, //tổng số ngày phép
+ 'total_leave_days_in_month_to_month' => $totalLeaveDaysInMonthToMonth, //tổng ngày nghỉ có phép đã nghỉ
+ 'remaining_days_in_month' => $remainingDaysInMonth, //số ngày phép còn lại
+ 'days_used' => $usedDaysInMonth, //tổng số ngày nghỉ có phép đã nghỉ ở tháng hiện tại
+ 'days_used_without_pay' => $usedDaysInMonthWithoutPay, //tổng số ngày nghỉ không phép đã nghỉ ở tháng hiện tại
+ 'days_requested' => $monthData['days_requested'], //số ngày yêu cầu nghỉ của tháng
+ 'remaining_days_in_month_remaining' => $remainingDaysInMonthRemaining,
+ 'days_will_use' => $days_will_use, //Số ngày phép sẽ sử dụng
+ 'days_will_use_without_pay' => $days_will_use_without_pay, //Số ngày không phép sẽ sử dụng
+ 'status' => 'ok', // mặc định là ok
+ ];
+
+ // Thêm thông tin tháng vào mảng kết quả
+ $monthsInfo[] = $month_data;
+ }
+ // Trả về kết quả tổng hợp
+ if ($hasInsufficientDays) {
+ return [
+ 'success' => false,
+ 'message' => $errorMessage,
+ 'warning_type' => 'exceed_monthly_limit',
+ 'months_info' => $monthsInfo
+ ];
+ }
+
+ return [
+ 'success' => true,
+ 'message' => "Đủ ngày phép cho yêu cầu.",
+ 'months_info' => $monthsInfo
+ ];
+ }
+
+ //Tính tổng số ngày nghỉ có phép đến tháng hiện tại
+ private function getTotalLeaveDaysInMonthToMonth($user, int $year, int $month): float
+ {
+ return Notes::join('categories', function ($join) {
+ $join->on('notes.n_time_type', '=', 'categories.c_code')
+ ->where('categories.c_type', 'TIME_TYPE');
+ })
+ ->where('n_user_id', $user->id)
+ ->where('n_year', $year)
+ ->where('n_month', "<=", $month)
+ ->where('n_reason', 'ONLEAVE')
+ ->sum('categories.c_value');
+ }
+
+ private function getTotalLeaveDaysInMonth($user, int $year, int $month): float
+ {
+ $leaveDaysInfo = LeaveDays::where('ld_user_id', $user->id)
+ ->where('ld_year', $year)
+ ->first();
+
+ $totalAllocated = 0;
+ if ($leaveDaysInfo) {
+ // if ($leaveDaysInfo->ld_day_total > $month) {
+ // $totalAllocated = $month;
+ // } else {
+ // $totalAllocated = $leaveDaysInfo->ld_day_total;
+ // }
+ $totalAllocated = $month; //(+ tạm để check)
+ // bên hàm duyệt ticket sẽ check lại để + 1 ngày trước job để đảm bảo đủ ngày phép
+ } else {
+ Log::warning("No LeaveDays record found for user ID: {$user->id}, year: {$year}. Assuming 0 allocated days.");
+ }
+ $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 = [
@@ -299,18 +678,12 @@ 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
-
+ // Update updated_by and admin_note in tickets table
+ // Refuse
// Update updated_by and admin_note in tickets table
// Send notification email to users
-
- // Refuse
- // Update updated_by and admin_note in tickets table
$startDate = $ticket->start_date; //Start day
$startPeriod = $ticket->start_period; //The session begins
$endDate = $ticket->end_date; //End date
@@ -321,13 +694,158 @@ class TicketController extends Controller
$dataMasterEndPeriod = CategoryController::getListMasterByCodeAndType("TIME_TYPE", $endPeriod);
$dataMasterType = CategoryController::getListMasterByCodeAndType("REASON", $type);
+ $dataMasterTypeNotes = CategoryController::getListMasterByType("REASON_NOTES");
+ $onleave = null;
+ $leaveWithoutPay = null;
+
+ if ($dataMasterTypeNotes) {
+ // get nghỉ phép, nghỉ không phép
+ $onleave = optional($dataMasterTypeNotes->where('c_code', 'ONLEAVE')->first())->c_code;
+ $leaveWithoutPay = optional($dataMasterTypeNotes->where('c_code', 'LEAVE_WITHOUT_PAY')->first())->c_code;
+ }
+
$formattedStartDate = Carbon::createFromFormat('Y-m-d', $startDate)->format('d/m/Y');
$formattedEndDate = Carbon::createFromFormat('Y-m-d', $endDate)->format('d/m/Y');
$user = Admin::find($ticket->user_id);
-
+
+ if ($onleave == null || $leaveWithoutPay == null) {
+ return response()->json(['message' => "Data reason notes not found", 'status' => false]);
+ }
+
if ($action == "confirm") {
- 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);
+ // dd($balanceCheckResult,$dataListPeriod);
+ if ($balanceCheckResult['success'] == false) {
+ if ($balanceCheckResult['months_info']) {
+ foreach ($balanceCheckResult['months_info'] as $monthInfo) {
+ // Lọc các ngày thuộc đúng tháng/năm này
+ $daysInMonth = array_filter($dataListPeriod, function ($item) use ($monthInfo) {
+ $date = \Carbon\Carbon::parse($item['date']);
+ return $date->year == $monthInfo['year'] && $date->month == $monthInfo['month'];
+ });
+
+ $daysWillUse = $monthInfo['days_will_use'] ?? 0;
+ $daysWillUseWithoutPay = $monthInfo['days_will_use_without_pay'] ?? 0;
+ // dd($daysWillUse,$daysWillUseWithoutPay,$daysInMonth);
+ foreach ($daysInMonth as $item) {
+ list($year, $month, $day) = explode('-', $item['date']);
+ $period = $item['period'];
+ $value = ($period === 'ALL') ? 1.0 : 0.5;
+
+ if ($period === 'ALL' && $daysWillUse == 0.5) {
+ // Chỉ còn 0.5 phép, chia thành 2 bản ghi: 1 phép, 1 không phép
+ // Ưu tiên phép cho buổi sáng (S), không phép cho buổi chiều (C)
+ Notes::create([
+ 'n_user_id' => $ticket->user_id,
+ 'n_day' => $day,
+ 'n_month' => $month,
+ 'n_year' => $year,
+ 'n_time_type' => 'S',
+ 'n_reason' => $onleave,
+ 'n_note' => $ticket->reason
+ ]);
+ Notes::create([
+ 'n_user_id' => $ticket->user_id,
+ 'n_day' => $day,
+ 'n_month' => $month,
+ 'n_year' => $year,
+ 'n_time_type' => 'C',
+ 'n_reason' => $leaveWithoutPay,
+ 'n_note' => $ticket->reason
+ ]);
+ $daysWillUse = 0;
+ $daysWillUseWithoutPay -= 0.5;
+ } elseif ($daysWillUse > 0) {
+ // Dùng ngày phép trước
+ $use = min($daysWillUse, $value);
+ Notes::create([
+ 'n_user_id' => $ticket->user_id,
+ 'n_day' => $day,
+ 'n_month' => $month,
+ 'n_year' => $year,
+ 'n_time_type' => $period,
+ 'n_reason' => $onleave,
+ 'n_note' => $ticket->reason
+ ]);
+ $daysWillUse -= $use;
+ } elseif ($daysWillUseWithoutPay > 0) {
+ // Hết phép, chuyển sang không phép
+ $use = min($daysWillUseWithoutPay, $value);
+ Notes::create([
+ 'n_user_id' => $ticket->user_id,
+ 'n_day' => $day,
+ 'n_month' => $month,
+ 'n_year' => $year,
+ 'n_time_type' => $period,
+ 'n_reason' => $leaveWithoutPay,
+ 'n_note' => $ticket->reason
+ ]);
+ $daysWillUseWithoutPay -= $use;
+ }
+ // Nếu cả hai đều hết thì thôi, không tạo nữa
+ }
+ }
+ }
+ } else {
+ //Đủ phép
+ foreach ($dataListPeriod as $result) {
+ list($year, $month, $day) = explode('-', $result['date']);
+ Notes::create([
+ 'n_user_id' => $ticket->user_id,
+ 'n_day' => $day,
+ 'n_month' => $month,
+ 'n_year' => $year,
+ 'n_time_type' => $result['period'],
+ 'n_reason' => $onleave, // có phép
+ 'n_note' => $ticket->reason
+ ]);
+ }
+ }
+
+ $yearCheck = Carbon::parse($endDate)->year;
+ // Check giá trị ld_day_total của bảng leave_days thuộc user id đó với giá trị của list item note trong bảng notes của user id đó
+ $leaveDaysInfo = LeaveDays::where('ld_user_id', $ticket->user_id)
+ ->where('ld_year', $yearCheck)
+ ->first();
+ if ($leaveDaysInfo) {
+ // Tính tổng số ngày nghỉ có phép đã sử dụng trong năm
+ $totalUsedLeaveDays = Notes::join('categories', function ($join) {
+ $join->on('notes.n_time_type', '=', 'categories.c_code')
+ ->where('categories.c_type', 'TIME_TYPE');
+ })
+ ->where('n_user_id', $ticket->user_id)
+ ->where('n_year', $yearCheck)
+ ->where('n_reason', 'ONLEAVE')
+ ->sum('categories.c_value');
+
+ // Tính tổng số ngày phép được cấp
+ $totalAllocatedDays = $leaveDaysInfo->ld_day_total +
+ $leaveDaysInfo->ld_additional_day +
+ $leaveDaysInfo->ld_special_leave_day;
+
+ // Tính số ngày vượt quá và làm tròn lên
+ $excessDays = $totalUsedLeaveDays - $totalAllocatedDays;
+ $roundedExcessDays = ceil($excessDays); // Làm tròn lên số nguyên gần nhất
+
+ // Kiểm tra nếu số ngày đã sử dụng vượt quá số ngày được cấp
+ if ($roundedExcessDays > 0) {
+ Log::warning("User ID: {$ticket->user_id} has used more leave days ({$totalUsedLeaveDays}) than allocated ({$totalAllocatedDays})");
+
+ // Cập nhật cột ld_day_total với số ngày đã làm tròn
+ if ($roundedExcessDays > 0) {
+ $leaveDaysInfo->ld_day_total += $roundedExcessDays;
+ $leaveDaysInfo->save();
+
+ Log::info("Updated leave days for User ID: {$ticket->user_id}. Added {$roundedExcessDays} days (rounded from {$excessDays})");
+ }
+ }
+ }
+ } else if ($ticket->type == "WFH") {
+ $dataListPeriod = $this->getAllPeriod($ticket->start_date, $ticket->start_period, $ticket->end_date, $ticket->end_period);
+ foreach ($dataListPeriod as $result) {
list($year, $month, $day) = explode('-', $result['date']);
Notes::create([
'n_user_id' => $ticket->user_id,
@@ -339,7 +857,8 @@ class TicketController extends Controller
'n_note' => $ticket->reason
]);
- if ($ticket->type == "WFH") {
+
+ //WFH - start tracking
$type = $result['period'];
$date = Carbon::create($year, $month, $day)->setTimezone(env('TIME_ZONE'));
@@ -369,6 +888,7 @@ class TicketController extends Controller
'created_at' => $end->setTimezone('UTC')
]
]);
+ //WFH - end tracking
}
}
@@ -424,6 +944,112 @@ class TicketController extends Controller
return response()->json(['message' => "failed", 'status' => false]);
}
+ 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ỉ') . "
";
+ }
+
+ if ($isSaturdayWorkDay) {
+ $results[] = ['date' => $date->toDateString(), 'period' => "S"];
+ }
+
+ continue;
+ }
+ // Skip Sundays entirely
+ else if ($date->dayOfWeek === Carbon::SUNDAY) {
+ continue;
+ }
+ }
+
+ if ($date->isSameDay($startDate)) {
+ //If the start date is morning, add afternoon
+ if ($startDate->isSameDay($endDate)) { // Nghỉ trong cùng 1 ngày
+ if ($startPeriod == $endPeriod) { // Cùng 1 buổi (S hoặc C)
+ $results[] = ['date' => $date->toDateString(), 'period' => $startPeriod];
+ } else { // Khác buổi (S đến C) -> cả ngày
+ $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
+ }
+ } else { // Ngày bắt đầu khác ngày kết thúc
+ if ($startPeriod == $morning->c_code) { // Bắt đầu từ sáng -> tính cả ngày
+ $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
+ } else { // Bắt đầu từ chiều -> tính buổi chiều
+ $results[] = ['date' => $date->toDateString(), 'period' => $startPeriod]; // Là $afternoon->c_code
+ }
+ }
+ } elseif ($date->isSameDay($endDate)) { // Ngày kết thúc (khác ngày bắt đầu)
+ if ($endPeriod == $afternoon->c_code) { // Kết thúc vào buổi chiều -> tính cả ngày
+ $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
+ } else { // Kết thúc vào buổi sáng -> tính buổi sáng
+ $results[] = ['date' => $date->toDateString(), 'period' => $endPeriod]; // Là $morning->c_code
+ }
+ } else { // Những ngày ở giữa
+ $results[] = ['date' => $date->toDateString(), 'period' => $all_day->c_code];
+ }
+ }
+
+ // Returns results
+ return $results;
+ }
+
private function getAllPeriod($startDate, $startPeriod, $endDate, $endPeriod)
{
//Create an array to contain the results
@@ -488,4 +1114,33 @@ class TicketController extends Controller
//Returns results
return $results;
}
+
+ /**
+ * Tính tổng số ngày nghỉ từ mảng các khoảng thời gian.
+ * 'ALL' = 1 ngày, 'S'/'C' = 0.5 ngày.
+ *
+ * @param array $dataListPeriod Mảng các khoảng thời gian nghỉ [['date' => 'Y-m-d', 'period' => 'ALL|S|C'], ...]
+ * @return float Tổng số ngày nghỉ
+ */
+ private function calculateTotalLeaveDays(array $dataListPeriod): float
+ {
+ $totalDays = 0.0;
+
+ foreach ($dataListPeriod as $periodData) {
+ if (isset($periodData['period'])) {
+ switch ($periodData['period']) {
+ case 'ALL':
+ $totalDays += 1.0;
+ break;
+ case 'S': // Buổi sáng
+ case 'C': // Buổi chiều
+ $totalDays += 0.5;
+ break;
+ // Có thể thêm default case để xử lý lỗi nếu cần
+ }
+ }
+ }
+
+ return $totalDays;
+ }
}
diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php
index cddc744..b33b165 100644
--- a/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php
+++ b/BACKEND/Modules/Admin/app/Http/Controllers/TimekeepingController.php
@@ -152,8 +152,6 @@ class TimekeepingController extends Controller
return response()->json(['status' => true, 'message' => 'Add successfully']);
}
-
-
public function updateCacheMonth(Request $request)
{
$month = $request->month;
@@ -180,6 +178,71 @@ class TimekeepingController extends Controller
$note = Notes::find($id);
if ($note) {
+ $n_month = $note->n_month;
+ $n_year = $note->n_year;
+
+ if ($note->n_reason == "ONLEAVE") {
+ // Get note reason ONLEAVE by $n_month, $n_year not include $note->id & include $note->n_user_id
+ // $onleave = Notes::getNotesByMonthAndYearAndUserId($n_month, $n_year, $note->n_user_id, $note->id);
+
+ // Get note reason LEAVE_WITHOUT_PAY by $n_month, $n_year & include $note->n_user_id
+ $leaveWithoutPay = Notes::getNotesByMonthAndYearAndUserIdAndReason($n_month, $n_year, $note->n_user_id, 'LEAVE_WITHOUT_PAY');
+
+ if (count($leaveWithoutPay) > 0) {
+ $deletedValue = ($note->n_time_type === 'ALL') ? 1.0 : 0.5;
+ $needUpdate = $deletedValue;
+ // dd($needUpdate, $leaveWithoutPay);
+ foreach ($leaveWithoutPay as $lwNote) {
+ if ($needUpdate <= 0) break;
+
+ if ($lwNote->n_time_type === 'ALL') {
+ if ($needUpdate == 1.0) {
+ // Chuyển cả note ALL thành phép
+ $lwNote->update(['n_reason' => 'ONLEAVE']);
+ $needUpdate = 0;
+ break;
+ } else { // $needUpdate == 0.5
+ // Tách ALL thành 2 note S và C, chuyển S thành phép, C giữ không phép
+ Notes::create([
+ 'n_user_id' => $lwNote->n_user_id,
+ 'n_day' => $lwNote->n_day,
+ 'n_month' => $lwNote->n_month,
+ 'n_year' => $lwNote->n_year,
+ 'n_time_type' => 'S',
+ 'n_reason' => 'ONLEAVE',
+ 'n_note' => $lwNote->n_note
+ ]);
+ Notes::create([
+ 'n_user_id' => $lwNote->n_user_id,
+ 'n_day' => $lwNote->n_day,
+ 'n_month' => $lwNote->n_month,
+ 'n_year' => $lwNote->n_year,
+ 'n_time_type' => 'C',
+ 'n_reason' => 'LEAVE_WITHOUT_PAY',
+ 'n_note' => $lwNote->n_note
+ ]);
+ $lwNote->delete();
+ $needUpdate = 0;
+ break;
+ }
+ } else {
+ // Nếu $lwNote->n_time_type == 'S' hoặc 'C' => 0.5
+ if ($needUpdate == 1.0) {
+ // Chuyển cả note ALL thành phép
+ $lwNote->update(['n_reason' => 'ONLEAVE']);
+ $needUpdate -= 0.5;
+ } else { // $needUpdate == 0.5
+ // S hoặc C, chỉ cần chuyển đúng 0.5 ngày
+ $lwNote->update(['n_reason' => 'ONLEAVE']);
+ $needUpdate = 0;
+ break;
+ }
+ }
+ }
+ } else {
+ // Khi note phép và k tồn tại nghỉ không phép => phép + dồn cho tháng sau
+ }
+ }
$note->delete();
$this->createOrUpdateRecordForCurrentMonth($month, $year);
return response()->json(['message' => 'Delete success', 'status' => true]);
@@ -206,10 +269,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 b561918..8f9ee2c 100755
--- a/BACKEND/Modules/Admin/routes/api.php
+++ b/BACKEND/Modules/Admin/routes/api.php
@@ -174,6 +174,9 @@ Route::middleware('api')
Route::get('/all-files', [ProfileController::class, 'listFiles'])->middleware('check.permission:admin.hr.staff.accountant');
Route::post('/update-profile', [ProfileController::class, 'updateProfile'])->middleware('check.permission:admin.hr.staff.accountant');
Route::get('/delete-profile-file', [ProfileController::class, 'removeFile'])->middleware('check.permission:admin.hr.staff.accountant');
+ Route::get('/files', [ProfileController::class, 'getFiles'])->middleware('check.permission:admin.hr.staff.accountant');
+ Route::post('/upload-files', [ProfileController::class, 'uploadFiles'])->middleware('check.permission:admin.hr.staff.accountant');
+ Route::delete('/files/{id}', [ProfileController::class, 'deleteFile'])->middleware('check.permission:admin.hr.staff.accountant');
});
Route::group([
diff --git a/BACKEND/app/Console/Commands/AddMonthlyLeaveDaysCommand.php b/BACKEND/app/Console/Commands/AddMonthlyLeaveDaysCommand.php
new file mode 100644
index 0000000..f691d15
--- /dev/null
+++ b/BACKEND/app/Console/Commands/AddMonthlyLeaveDaysCommand.php
@@ -0,0 +1,24 @@
+argument('month');
+ $year = $this->argument('year');
+ AddMonthlyLeaveDays::dispatch($month, $year);
+ }
+}
\ No newline at end of file
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/Kernel.php b/BACKEND/app/Console/Kernel.php
index 6d56e12..d8fa86b 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;
@@ -24,7 +25,7 @@ class Kernel extends ConsoleKernel
// ->dailyAt('18:00');
// Chạy command vào ngày 31/12 lúc 23:59:59 mỗi năm
- $schedule->command('initialize:leavedays')->yearlyOn(12, 31, '23:59:59');
+ // $schedule->command('initialize:leavedays')->yearlyOn(12, 31, '23:59:59');
$schedule->command('leave:deduct')->yearlyOn(3, 31, '23:59:59');
// Chạy buổi sáng lúc 12:00
@@ -32,6 +33,9 @@ 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 00:01 ngày đầu tiên của mỗi tháng
+ $schedule->command('add:monthly-leavedays')->monthlyOn(1, '00:01');
}
/**
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..4c58b9f
--- /dev/null
+++ b/BACKEND/app/Jobs/AddMonthlyLeaveDays.php
@@ -0,0 +1,69 @@
+month = $month ?? Carbon::now()->month;
+ $this->year = $year ?? Carbon::now()->year;
+ }
+
+ public function handle(): void
+ {
+ $users = User::get();
+
+ foreach ($users as $user) {
+ $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 {
+ // Kiểm tra nếu số ngày phép hiện tại nhỏ hơn tháng hiện tại
+ if ($leaveDay->ld_day_total < $this->month) {
+ // Cập nhật số ngày phép bằng với tháng hiện tại
+ $oldDays = $leaveDay->ld_day_total;
+ $leaveDay->ld_day_total = $this->month;
+
+ // Xử lý ghi chú
+ $newNote = "Cập nhật ngày phép đến tháng " . $this->month;
+ if (!empty($leaveDay->ld_note)) {
+ // Nếu đã có ghi chú, thêm ghi chú mới vào và xuống dòng
+ $leaveDay->ld_note = $leaveDay->ld_note . "\n" . $newNote;
+ } else {
+ // Nếu chưa có ghi chú, gán ghi chú mới
+ $leaveDay->ld_note = $newNote;
+ }
+ $leaveDay->save();
+ }
+ }
+ }
+ }
+}
diff --git a/BACKEND/app/Jobs/DeductLeaveDays.php b/BACKEND/app/Jobs/DeductLeaveDays.php
index 86dc3b0..2d4bd4a 100644
--- a/BACKEND/app/Jobs/DeductLeaveDays.php
+++ b/BACKEND/app/Jobs/DeductLeaveDays.php
@@ -36,7 +36,7 @@ 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;
@@ -59,11 +59,11 @@ class DeductLeaveDays implements ShouldQueue
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) {
+ if ($existingData->ld_additional_day > $totalLeaveDaysByMonth->leave_days) {
LeaveDays::where('ld_year', $this->year)
->where('ld_user_id', $user->id)
->update([
- 'ld_date_additional' => $totalLeaveDaysByMonth->leave_days,
+ 'ld_additional_day' => $totalLeaveDaysByMonth->leave_days,
]);
}
} else {
@@ -71,7 +71,7 @@ class DeductLeaveDays implements ShouldQueue
LeaveDays::where('ld_year', $this->year)
->where('ld_user_id', $user->id)
->update([
- 'ld_date_additional' => "0",
+ 'ld_additional_day' => "0",
]);
}
}
diff --git a/BACKEND/app/Jobs/InitializeLeaveDays.php b/BACKEND/app/Jobs/InitializeLeaveDays.php
index 3c545b5..1d7569f 100644
--- a/BACKEND/app/Jobs/InitializeLeaveDays.php
+++ b/BACKEND/app/Jobs/InitializeLeaveDays.php
@@ -34,7 +34,7 @@ class InitializeLeaveDays implements ShouldQueue
public function handle(): void
{
$users = User::get();
- $ld_day = 12;
+ $ld_day_total = 12;
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 +51,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,9 +71,9 @@ 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ũ';
@@ -82,9 +82,9 @@ class InitializeLeaveDays implements ShouldQueue
// 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' => $ld_day_total,
'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/Models/Files.php b/BACKEND/app/Models/Files.php
new file mode 100644
index 0000000..64be814
--- /dev/null
+++ b/BACKEND/app/Models/Files.php
@@ -0,0 +1,25 @@
+belongsTo(User::class);
+ }
+}
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..659fcda 100644
--- a/BACKEND/app/Models/Notes.php
+++ b/BACKEND/app/Models/Notes.php
@@ -11,7 +11,13 @@ 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',
];
/**
@@ -25,7 +31,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 +53,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/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..51b4754
--- /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_total', 'ld_day_total');
+ });
+ }
+
+ public function down()
+ {
+ Schema::table('leave_days', function (Blueprint $table) {
+ $table->renameColumn('ld_day_total', 'ld_day_total');
+ });
+ }
+}
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_03_27_131933_create_files_table.php b/BACKEND/database/migrations/2025_03_27_131933_create_files_table.php
new file mode 100644
index 0000000..0951ec1
--- /dev/null
+++ b/BACKEND/database/migrations/2025_03_27_131933_create_files_table.php
@@ -0,0 +1,32 @@
+id();
+ $table->string('name');
+ $table->string('url');
+ $table->string('type');
+ $table->text('description')->nullable();
+ $table->foreignId('user_id')->constrained('users')->onDelete('cascade');
+ $table->timestamps();
+ });
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ Schema::dropIfExists('files');
+ }
+};
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/seeders/DatabaseSeeder.php b/BACKEND/database/seeders/DatabaseSeeder.php
index a9f4519..c067f12 100755
--- a/BACKEND/database/seeders/DatabaseSeeder.php
+++ b/BACKEND/database/seeders/DatabaseSeeder.php
@@ -18,5 +18,10 @@ class DatabaseSeeder extends Seeder
// 'name' => 'Test User',
// 'email' => 'test@example.com',
// ]);
+
+ $this->call([
+ UserSeeder::class,
+ FileSeeder::class,
+ ]);
}
}
diff --git a/BACKEND/resources/views/emails/file_upload_notification.blade.php b/BACKEND/resources/views/emails/file_upload_notification.blade.php
new file mode 100644
index 0000000..3bbad65
--- /dev/null
+++ b/BACKEND/resources/views/emails/file_upload_notification.blade.php
@@ -0,0 +1,72 @@
+
+
+
Xin chào {{ $user->name }},
+ +{{ $description }} ở hệ thống APAC Tech.
+Note: {{ $note }}
+Vui lòng kiểm tra ngay thông tin bằng cách nhấn nút bên dưới:
+ +Trân trọng,
Đội ngũ APAC Tech
Month {lastmonth}
}- - {itemDay.reason_name} ({itemDay.time_type_name}) {itemDay.day} - /{itemDay.month} + - {itemDay.reason_name} ({itemDay.time_type_name}) {itemDay.day}/ + {itemDay.month}
0 ? 'block' : 'none', + }} + > + {'Phép năm:'}{' '} + + {ld_day_total} + +
+0 ? 'block' : 'none', + }} + > + {'Phép năm cũ:'}{' '} + + {ld_additional_day} + +
+0 ? 'block' : 'none', + }} + > + {'Phép đặc biệt:'}{' '} + + {ld_special_leave_day} + +
{totalDayOff}
++ {'Nghỉ phép:'}{' '} + + {totalOnLeave} + +
++ {'Không phép:'}{' '} + + {totalLeaveWithoutPay} + +
+