diff --git a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php
index 270bae2..df0e629 100644
--- a/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php
+++ b/BACKEND/Modules/Admin/app/Http/Controllers/TicketController.php
@@ -524,8 +524,8 @@ class TicketController extends Controller
$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)
- if ($remainingOnleaveDaysInMonth <= 0) {
+ // 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;
@@ -709,20 +709,35 @@ class TicketController extends Controller
$totalAllocated = 0;
if ($leaveDaysInfo) {
- // if ($leaveDaysInfo->ld_day_total > $month) {
- // $totalAllocated = $month;
- // } else {
+ $currentMonth = Carbon::now()->month;
$totalAllocated = $leaveDaysInfo->ld_day_total;
- // }
- // Nếu là duyệt ticket thì sẽ cộng thêm số ngày phép còn lại của tháng hiện tại
- // if ($isAccept) {
- if ($month >= $totalAllocated) {
- $totalAllocated += $month - $totalAllocated;
+ // 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;
+ }
}
- // }
- // bên hàm duyệt ticket sẽ check lại để + 1 ngày trước job để đảm bảo đủ ngày phép
+ // 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;
+ }
+ }
} else {
Log::warning("No LeaveDays record found for user ID: {$user->id}, year: {$year}. Assuming 0 allocated days.");
}
diff --git a/BACKEND/Modules/Auth/app/Http/Controllers/UserController.php b/BACKEND/Modules/Auth/app/Http/Controllers/UserController.php
index bb1e898..ff84f4d 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,36 @@ 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,7 +73,19 @@ 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(),
]);
$user_res = [
@@ -98,8 +139,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/Kernel.php b/BACKEND/app/Console/Kernel.php
index d8fa86b..f063914 100755
--- a/BACKEND/app/Console/Kernel.php
+++ b/BACKEND/app/Console/Kernel.php
@@ -25,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
diff --git a/BACKEND/app/Jobs/AddMonthlyLeaveDays.php b/BACKEND/app/Jobs/AddMonthlyLeaveDays.php
index 4c58b9f..38fd483 100644
--- a/BACKEND/app/Jobs/AddMonthlyLeaveDays.php
+++ b/BACKEND/app/Jobs/AddMonthlyLeaveDays.php
@@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
+use Modules\Admin\app\Models\Category;
class AddMonthlyLeaveDays implements ShouldQueue
{
@@ -18,6 +19,8 @@ class AddMonthlyLeaveDays implements ShouldQueue
protected $month;
protected $year;
+ private const ONLEAVE_PER_MONTH = 1; // Ngày phép cộng mỗi tháng
+
public function __construct($month = null, $year = null)
{
$this->month = $month ?? Carbon::now()->month;
@@ -29,6 +32,11 @@ class AddMonthlyLeaveDays implements ShouldQueue
$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();
@@ -46,11 +54,36 @@ class AddMonthlyLeaveDays implements ShouldQueue
]);
$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
+ // 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;
+
+ // 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();
+ }
+ }
+ }
+
+ // 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ậ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;
+ // Cộng mỗi tháng 1 ngày phép cho nhân viên
+ $leaveDay->ld_day_total += self::ONLEAVE_PER_MONTH;
// Xử lý ghi chú
$newNote = "Cập nhật ngày phép đến tháng " . $this->month;
diff --git a/BACKEND/app/Jobs/DeductLeaveDays.php b/BACKEND/app/Jobs/DeductLeaveDays.php
index 2d4bd4a..b5f9571 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
@@ -42,38 +41,19 @@ class DeductLeaveDays implements ShouldQueue
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_additional_day > $totalLeaveDaysByMonth->leave_days) {
- LeaveDays::where('ld_year', $this->year)
- ->where('ld_user_id', $user->id)
- ->update([
- 'ld_additional_day' => $totalLeaveDaysByMonth->leave_days,
- ]);
- }
- } 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_additional_day' => "0",
- ]);
- }
+ ->where('n_user_id', $user->id)
+ ->where('n_year', $this->year)
+ ->where('n_month', "<=", 3)
+ ->where('n_reason', 'ONLEAVE')
+ ->sum('categories.c_value');
+
+ $existingData->ld_additional_day = $usedOnleaveDaysTotal ?? 0;
+ $existingData->save();
}
}
}
diff --git a/BACKEND/app/Jobs/InitializeLeaveDays.php b/BACKEND/app/Jobs/InitializeLeaveDays.php
index 1d7569f..6ba8114 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_total = 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)
@@ -82,7 +83,7 @@ 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_total' => $ld_day_total,
+ '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_additional_day' => $ld_additional_day,
'ld_note' => $ld_note,
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_24_025714_add_onleave_permanent_categories_table.php b/BACKEND/database/migrations/2025_06_24_025714_add_onleave_permanent_categories_table.php
new file mode 100644
index 0000000..8aaa45f
--- /dev/null
+++ b/BACKEND/database/migrations/2025_06_24_025714_add_onleave_permanent_categories_table.php
@@ -0,0 +1,35 @@
+insert([
+ [
+ 'c_code' => 'PERMANENT',
+ 'c_name' => 'Phép cộng nhân viên chính thức',
+ 'c_type' => 'PERMANENT_ONLEAVE',
+ 'c_value' => 3,
+ 'c_active' => 1,
+ 'created_at' => now(),
+ 'updated_at' => now(),
+ ],
+ ]);
+ }
+
+ /**
+ * Reverse the migrations.
+ */
+ public function down(): void
+ {
+ DB::table('categories')->where('c_code', 'PERMANENT')->delete();
+ }
+};
diff --git a/FRONTEND/src/pages/TicketsManagement/TicketsManagement.tsx b/FRONTEND/src/pages/TicketsManagement/TicketsManagement.tsx
index f90a428..72f18da 100755
--- a/FRONTEND/src/pages/TicketsManagement/TicketsManagement.tsx
+++ b/FRONTEND/src/pages/TicketsManagement/TicketsManagement.tsx
@@ -654,12 +654,18 @@ const TicketsManagement = () => {
onClick={() => {
setIsRefuseConfirmOpen(false)
}}
+ disabled={disableBtn}
>
Cancel
diff --git a/FRONTEND/src/pages/UsersManagement/UsersManagement.module.css b/FRONTEND/src/pages/UsersManagement/UsersManagement.module.css
index 6b56ab4..e3d17a8 100644
--- a/FRONTEND/src/pages/UsersManagement/UsersManagement.module.css
+++ b/FRONTEND/src/pages/UsersManagement/UsersManagement.module.css
@@ -45,3 +45,37 @@
.dialogText {
color: light-dark(#2d353c, white);
}
+
+/* Thêm styles cho Modal xác nhận xóa */
+.deleteModal {
+ background-color: light-dark(white, #2d353c);
+ text-align: center;
+ border: solid 1px rgb(9, 132, 132);
+}
+
+.deleteModalTitle {
+ color: rgb(9, 132, 132);
+ font-weight: 600;
+ font-size: 1.2rem;
+ margin-bottom: 1rem;
+}
+
+.deleteModalContent {
+ color: light-dark(#2d353c, white);
+ margin-bottom: 1.5rem;
+}
+
+.deleteModalFooter {
+ display: flex;
+ justify-content: flex-end;
+ gap: 10px;
+ margin-top: 1rem;
+}
+
+.deleteButton {
+ background-color: rgb(9, 132, 132);
+}
+
+.deleteButton:hover {
+ background-color: rgb(9, 132, 132);
+}
\ No newline at end of file
diff --git a/FRONTEND/src/pages/UsersManagement/UsersManagement.tsx b/FRONTEND/src/pages/UsersManagement/UsersManagement.tsx
index ba35dc5..0ee82a6 100644
--- a/FRONTEND/src/pages/UsersManagement/UsersManagement.tsx
+++ b/FRONTEND/src/pages/UsersManagement/UsersManagement.tsx
@@ -13,6 +13,7 @@ import {
Group,
Modal,
MultiSelect,
+ Switch,
Text,
TextInput,
} from '@mantine/core'
@@ -24,28 +25,31 @@ const UsersManagement = () => {
const [users, setUsers] = useState([])
const [action, setAction] = useState('')
const [activeBtn, setActiveBtn] = useState(false)
- const [item, setItem] = useState({ id: 0 })
+ const [item, setItem] = useState({ id: 0, is_permanent: false })
const [disableBtn, setDisableBtn] = useState(false)
const [info, setInfo] = useState('')
+ const [isPermanentConfirmOpen, setIsPermanentConfirmOpen] =
+ useState(false)
+
const columns = [
{
name: 'id',
- size: '3%',
+ size: '5%',
header: 'ID',
},
{
name: 'name',
- size: '17%',
+ size: '20%',
header: 'Name',
},
{
name: 'email',
- size: '26%',
+ size: '25%',
header: 'Email',
},
{
name: 'permission',
- size: '10%',
+ size: '20%',
header: 'Permission',
render: (row: TUser) => {
if (row.permission.includes(',')) {
@@ -57,9 +61,21 @@ const UsersManagement = () => {
}
},
},
+ {
+ name: 'is_permanent',
+ size: '20%',
+ header: 'Employment Type',
+ render: (row: TUser) => {
+ return row.is_permanent ? (
+ Permanent
+ ) : (
+ Probation
+ )
+ },
+ },
{
name: '#',
- size: '5%',
+ size: '10%',
header: 'Action',
render: (row: TUser) => {
return (
@@ -68,6 +84,8 @@ const UsersManagement = () => {
className={classes.editIcon}
onClick={() => {
setAction('edit')
+ setItem(row)
+ form.reset()
form.setValues(row)
}}
width={20}
@@ -94,6 +112,7 @@ const UsersManagement = () => {
name: '',
email: '',
permission: '',
+ is_permanent: false,
},
})
@@ -128,6 +147,7 @@ const UsersManagement = () => {
const res = await update(createOrUpdateUser, values, getAll)
if (res === true) {
setAction('')
+ setIsPermanentConfirmOpen(false)
form.reset()
}
} catch (error) {
@@ -172,6 +192,7 @@ const UsersManagement = () => {
opened={action === 'add' || action === 'edit'}
onClose={() => {
setAction('')
+ setIsPermanentConfirmOpen(false)
form.reset()
}}
title={
@@ -183,9 +204,15 @@ const UsersManagement = () => {