From e476c2768a91848512b62bbfa0a080008bddff52 Mon Sep 17 00:00:00 2001 From: antv Date: Wed, 8 Jul 2026 15:29:13 +0700 Subject: [PATCH] feature: user list --- .../Controllers/Admin/AdminController.php | 56 +- app/Http/Controllers/AuthController.php | 2 +- app/Http/Controllers/User/UserController.php | 4 +- app/Http/Middleware/Admin/AdminMiddleware.php | 2 +- .../ForceChangePasswordMiddleware.php | 2 +- app/Http/Middleware/User/MemberMiddleware.php | 2 +- app/Models/AddCard.php | 5 + app/Models/User.php | 12 - app/Services/Admin/AdminService.php | 85 ++- .../Admin/Contracts/AdminServiceInterface.php | 2 + app/Services/Auth/AuthService.php | 6 +- app/Services/User/UserService.php | 12 +- config/constants.php | 33 ++ database/seeders/MockDataSeeder.php | 86 ++- refactor.php | 54 +- .../admin/users/partials/stats.blade.php | 56 +- .../admin/users/partials/table.blade.php | 35 +- .../views/components/ranking-item.blade.php | 16 +- resources/views/components/sidebar.blade.php | 4 +- .../components/user-filter-form.blade.php | 8 +- .../views/components/user-form.blade.php | 15 +- .../views/components/user-page.blade.php | 109 +++- resources/views/layouts/app.blade.php | 10 + .../views/user/change_password.blade.php | 4 +- .../user/partials/ranking_list.blade.php | 36 +- routes/admin.php | 2 + tests/Feature/AdminUserListStatsTest.php | 559 ++++++++++++++++-- tests/Feature/LoginNotificationTest.php | 56 +- tests/Feature/PasswordMd5Test.php | 14 +- tests/Feature/PasswordValidationTest.php | 8 +- tests/Feature/SendThankCardTest.php | 16 +- tests/Feature/UserDashboardLayoutTest.php | 64 +- 32 files changed, 1091 insertions(+), 284 deletions(-) create mode 100644 config/constants.php diff --git a/app/Http/Controllers/Admin/AdminController.php b/app/Http/Controllers/Admin/AdminController.php index e00fcd5..da982d0 100644 --- a/app/Http/Controllers/Admin/AdminController.php +++ b/app/Http/Controllers/Admin/AdminController.php @@ -18,18 +18,23 @@ class AdminController extends Controller { $selectedMonth = $request->input('month', Carbon::now()->format('Y-m')); $search = $request->input('search'); - $status = $request->input('status', (string)User::STATUS_ACTIVE); + $status = $request->input('status', (string)config('constants.STATUS_ACTIVE')); $role = $request->input('role'); $department = $request->input('department'); $flagSend = $request->input('flag_send'); - ['users' => $users, 'topReceivedUser' => $topReceivedUser, 'topSentUser' => $topSentUser] - = $this->adminService->getUserListWithStats($selectedMonth, $search, $status, $role, $department, $flagSend); + [ + 'users' => $users, + 'topReceivedUser' => $topReceivedUser, + 'topSentUser' => $topSentUser, + 'topReceivedUsers' => $topReceivedUsers, + 'topSentUsers' => $topSentUsers + ] = $this->adminService->getUserListWithStats($selectedMonth, $search, $status, $role, $department, $flagSend); if ($request->ajax() || $request->wantsJson()) { return response()->json([ 'title' => view('admin.users.partials.title', compact('selectedMonth'))->render(), - 'stats' => view('admin.users.partials.stats', compact('topReceivedUser', 'topSentUser'))->render(), + 'stats' => view('admin.users.partials.stats', compact('topReceivedUser', 'topSentUser', 'topReceivedUsers', 'topSentUsers'))->render(), 'table' => view('admin.users.partials.table', compact('users'))->render(), ]); } @@ -39,6 +44,8 @@ class AdminController extends Controller 'selectedMonth', 'topReceivedUser', 'topSentUser', + 'topReceivedUsers', + 'topSentUsers', 'search', 'status', 'role', @@ -58,9 +65,9 @@ class AdminController extends Controller 'msnv' => 'required|integer|unique:user,msnv', 'name' => 'required|string|max:255', 'mail' => 'required|email|unique:user,mail', - 'departments' => 'required|integer|min:1|max:5', + 'departments' => 'required|string|in:' . implode(',', config('constants.DEPARTMENTS')), 'password' => 'required|string', - 'role' => 'required|in:' . User::ROLE_MEMBER . ',' . User::ROLE_ADMIN, + 'role' => 'required|in:' . config('constants.ROLE_MEMBER') . ',' . config('constants.ROLE_ADMIN'), ]); $this->adminService->createUser($request->only('msnv', 'name', 'mail', 'departments', 'password', 'role')); @@ -75,17 +82,30 @@ class AdminController extends Controller $administrations = $this->adminService->getUserTransactions($msnv, $selectedMonth); - return view('admin.users.edit', compact('user', 'administrations', 'selectedMonth')); + $addCards = \App\Models\AddCard::with('sellerUser') + ->where('buyer', $user->msnv) + ->orderBy('date', 'desc') + ->orderBy('id', 'desc') + ->get(); + + $admins = User::where('role', config('constants.ROLE_ADMIN')) + ->where('status', config('constants.STATUS_ACTIVE')) + ->get(); + + return view('admin.users.edit', compact('user', 'administrations', 'selectedMonth', 'addCards', 'admins')); } public function update(Request $request, $msnv) { $request->validate([ - 'num_card' => 'nullable|integer|min:1', - 'flag_send' => 'nullable|boolean', + 'card' => 'required|integer|min:0', + 'role' => 'required|in:' . config('constants.ROLE_MEMBER') . ',' . config('constants.ROLE_ADMIN'), + 'status' => 'required|in:' . config('constants.STATUS_INACTIVE') . ',' . config('constants.STATUS_ACTIVE'), + 'flag_send' => 'required|in:' . config('constants.FLAG_SEND_DISABLED') . ',' . config('constants.FLAG_SEND_ENABLED'), + 'first_login' => 'required|in:' . config('constants.FIRST_LOGIN_FALSE') . ',' . config('constants.FIRST_LOGIN_TRUE'), ]); - $this->adminService->updateUser($msnv, $request->only('num_card', 'flag_send')); + $this->adminService->updateUser($msnv, $request->only('card', 'role', 'status', 'flag_send', 'first_login')); return redirect()->back()->with('success', __('messages.user_update_success')); } @@ -97,6 +117,22 @@ class AdminController extends Controller return redirect()->route('admin.users.index')->with('success', __('messages.user_deactivate_success')); } + public function updateAddCard(Request $request, $id) + { + $request->validate([ + 'num_card' => 'required|integer|min:1', + 'seller' => 'required|exists:user,msnv', + 'date' => 'required|date', + ]); + + try { + $this->adminService->updateAddCard(intval($id), $request->only('num_card', 'seller', 'date')); + return redirect()->back()->with('success', 'Cập nhật lịch sử cấp phát thẻ thành công.'); + } catch (\Exception $e) { + return redirect()->back()->withErrors(['error' => $e->getMessage()]); + } + } + public function resetCards() { $this->adminService->resetAllCards(); diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index 7f1cac0..41605d8 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -62,7 +62,7 @@ class AuthController extends Controller \Illuminate\Support\Facades\RateLimiter::clear($throttleKey); $user = $result['user']; - if ($user->role != \App\Models\User::ROLE_ADMIN && $user->first_login == \App\Models\User::FIRST_LOGIN_TRUE) { + if ($user->role != config('constants.ROLE_ADMIN') && $user->first_login == config('constants.FIRST_LOGIN_TRUE')) { return redirect()->route('login')->with('dialog_first_login', true); } diff --git a/app/Http/Controllers/User/UserController.php b/app/Http/Controllers/User/UserController.php index 9e05626..052d7c9 100644 --- a/app/Http/Controllers/User/UserController.php +++ b/app/Http/Controllers/User/UserController.php @@ -48,7 +48,7 @@ class UserController extends Controller $request->validate([ 'receiver' => [ 'required', - \Illuminate\Validation\Rule::exists('user', 'msnv')->where('status', \App\Models\User::STATUS_ACTIVE) + \Illuminate\Validation\Rule::exists('user', 'msnv')->where('status', config('constants.STATUS_ACTIVE')) ], 'amount' => 'required|integer|min:1|max:' . Administration::MAX_SEND_CARD_PER_MONTH, ]); @@ -84,7 +84,7 @@ class UserController extends Controller $user = Auth::user(); $this->userService->updatePassword($user, $request->password); - $route = $user->role == \App\Models\User::ROLE_ADMIN ? 'admin.dashboard' : 'user.dashboard'; + $route = $user->role == config('constants.ROLE_ADMIN') ? 'admin.dashboard' : 'user.dashboard'; return redirect()->route($route)->with('success', __('messages.password_change_success')); } } diff --git a/app/Http/Middleware/Admin/AdminMiddleware.php b/app/Http/Middleware/Admin/AdminMiddleware.php index d569a7f..85b7581 100644 --- a/app/Http/Middleware/Admin/AdminMiddleware.php +++ b/app/Http/Middleware/Admin/AdminMiddleware.php @@ -12,7 +12,7 @@ class AdminMiddleware { public function handle(Request $request, Closure $next): Response { - if (Auth::check() && Auth::user()->role == User::ROLE_ADMIN && Auth::user()->status == User::STATUS_ACTIVE) { + if (Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN') && Auth::user()->status == config('constants.STATUS_ACTIVE')) { return $next($request); } return redirect()->route('login'); diff --git a/app/Http/Middleware/ForceChangePasswordMiddleware.php b/app/Http/Middleware/ForceChangePasswordMiddleware.php index 7e71d1b..3152a52 100644 --- a/app/Http/Middleware/ForceChangePasswordMiddleware.php +++ b/app/Http/Middleware/ForceChangePasswordMiddleware.php @@ -12,7 +12,7 @@ class ForceChangePasswordMiddleware { public function handle(Request $request, Closure $next): Response { - if (Auth::check() && Auth::user()->role != User::ROLE_ADMIN && Auth::user()->first_login == User::FIRST_LOGIN_TRUE) { + if (Auth::check() && Auth::user()->role != config('constants.ROLE_ADMIN') && Auth::user()->first_login == config('constants.FIRST_LOGIN_TRUE')) { if (!$request->routeIs('user.change_password') && !$request->routeIs('user.update_password') && !$request->routeIs('logout')) { return redirect()->route('user.change_password'); } diff --git a/app/Http/Middleware/User/MemberMiddleware.php b/app/Http/Middleware/User/MemberMiddleware.php index 4f900fc..2baee42 100644 --- a/app/Http/Middleware/User/MemberMiddleware.php +++ b/app/Http/Middleware/User/MemberMiddleware.php @@ -12,7 +12,7 @@ class MemberMiddleware { public function handle(Request $request, Closure $next): Response { - if (Auth::check() && in_array(Auth::user()->role, [User::ROLE_MEMBER, User::ROLE_ADMIN]) && Auth::user()->status == User::STATUS_ACTIVE) { + if (Auth::check() && in_array(Auth::user()->role, [config('constants.ROLE_MEMBER'), config('constants.ROLE_ADMIN')]) && Auth::user()->status == config('constants.STATUS_ACTIVE')) { return $next($request); } return redirect()->route('login'); diff --git a/app/Models/AddCard.php b/app/Models/AddCard.php index 0706ebe..8076cdb 100644 --- a/app/Models/AddCard.php +++ b/app/Models/AddCard.php @@ -19,4 +19,9 @@ class AddCard extends Model 'seller', 'date', ]; + + public function sellerUser() + { + return $this->belongsTo(User::class, 'seller', 'msnv'); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 15f1895..119acea 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -10,18 +10,6 @@ class User extends Authenticatable { use HasFactory, Notifiable; - const ROLE_MEMBER = 0; - const ROLE_ADMIN = 1; - - const STATUS_INACTIVE = 0; - const STATUS_ACTIVE = 1; - - const FLAG_SEND_DISABLED = 0; - const FLAG_SEND_ENABLED = 1; - - const FIRST_LOGIN_FALSE = 0; - const FIRST_LOGIN_TRUE = 1; - protected $table = 'user'; public $timestamps = false; diff --git a/app/Services/Admin/AdminService.php b/app/Services/Admin/AdminService.php index ee53128..b004add 100644 --- a/app/Services/Admin/AdminService.php +++ b/app/Services/Admin/AdminService.php @@ -35,8 +35,21 @@ class AdminService implements AdminServiceInterface ->whereBetween('date', [$startOfMonth, $endOfMonth]) ]); - $topReceivedUser = (clone $usersQuery)->where('status', User::STATUS_ACTIVE)->orderByDesc('total_received')->first(); - $topSentUser = (clone $usersQuery)->where('status', User::STATUS_ACTIVE)->orderByDesc('total_sent')->first(); + $activeUsers = (clone $usersQuery)->where('status', config('constants.STATUS_ACTIVE'))->get(); + + $maxReceived = $activeUsers->max('total_received') ?? 0; + $maxSent = $activeUsers->max('total_sent') ?? 0; + + $topReceivedUsers = $maxReceived > 0 + ? $activeUsers->filter(fn($u) => $u->total_received == $maxReceived)->values() + : collect(); + + $topSentUsers = $maxSent > 0 + ? $activeUsers->filter(fn($u) => $u->total_sent == $maxSent)->values() + : collect(); + + $topReceivedUser = $topReceivedUsers->first(); + $topSentUser = $topSentUsers->first(); if (!is_null($status) && $status !== '') { $usersQuery->where('status', intval($status)); @@ -47,7 +60,7 @@ class AdminService implements AdminServiceInterface } if (!is_null($department) && $department !== '') { - $usersQuery->where('departments', intval($department)); + $usersQuery->where('departments', $department); } if (!is_null($flagSend) && $flagSend !== '') { @@ -62,10 +75,9 @@ class AdminService implements AdminServiceInterface ->orWhereRaw('LOWER(mail) LIKE ?', ['%' . strtolower($search) . '%']); }); } - $users = $usersQuery->paginate(20)->withQueryString(); - return compact('users', 'topReceivedUser', 'topSentUser'); + return compact('users', 'topReceivedUser', 'topSentUser', 'topReceivedUsers', 'topSentUsers'); } public function getUserTransactions(string $msnv, string $selectedMonth): LengthAwarePaginator @@ -89,10 +101,10 @@ class AdminService implements AdminServiceInterface 'pass' => md5($data['password']), 'departments' => $data['departments'], 'role' => $data['role'], - 'status' => User::STATUS_ACTIVE, + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 0, - 'flag_send' => User::FLAG_SEND_DISABLED, - 'first_login' => $data['role'] == User::ROLE_ADMIN ? User::FIRST_LOGIN_FALSE : User::FIRST_LOGIN_TRUE, + 'flag_send' => config('constants.FLAG_SEND_DISABLED'), + 'first_login' => $data['role'] == config('constants.ROLE_ADMIN') ? config('constants.FIRST_LOGIN_FALSE') : config('constants.FIRST_LOGIN_TRUE'), ]); } @@ -101,17 +113,24 @@ class AdminService implements AdminServiceInterface $user = User::where('msnv', $msnv)->firstOrFail(); DB::transaction(function () use ($data, $user) { - if (!empty($data['num_card'])) { + $newCard = intval($data['card']); + $oldCard = intval($user->card); + + if ($newCard > $oldCard) { + $diff = $newCard - $oldCard; AddCard::create([ 'buyer' => $user->msnv, - 'num_card' => $data['num_card'], + 'num_card' => $diff, 'seller' => Auth::user()->msnv, 'date' => Carbon::today(), ]); - $user->card += $data['num_card']; } - $user->flag_send = isset($data['flag_send']) ? User::FLAG_SEND_ENABLED : User::FLAG_SEND_DISABLED; + $user->card = $newCard; + $user->role = intval($data['role']); + $user->status = intval($data['status']); + $user->flag_send = intval($data['flag_send']); + $user->first_login = intval($data['first_login']); $user->save(); }); } @@ -119,12 +138,50 @@ class AdminService implements AdminServiceInterface public function deactivateUser(string $msnv): void { $user = User::where('msnv', $msnv)->firstOrFail(); - $user->status = User::STATUS_INACTIVE; + $user->status = config('constants.STATUS_INACTIVE'); $user->save(); } public function resetAllCards(): void { - User::where('status', User::STATUS_ACTIVE)->update(['card' => 0]); + User::where('status', config('constants.STATUS_ACTIVE'))->update(['card' => 0]); + } + + public function updateAddCard(int $id, array $data): void + { + $addCard = AddCard::findOrFail($id); + + + $currentMonth = Carbon::now()->format('Y-m'); + $recordMonth = Carbon::parse($addCard->date)->format('Y-m'); + + if ($recordMonth !== $currentMonth) { + throw new \Exception('Không cho phép chỉnh sửa dữ liệu card của các tháng trước.'); + } + + $newDateMonth = Carbon::parse($data['date'])->format('Y-m'); + if ($newDateMonth !== $currentMonth) { + throw new \Exception('Chỉ được phép chỉnh sửa dữ liệu của tháng hiện tại.'); + } + + $user = User::where('msnv', $addCard->buyer)->firstOrFail(); + + DB::transaction(function () use ($addCard, $user, $data) { + $oldNumCard = intval($addCard->num_card); + $newNumCard = intval($data['num_card']); + $diff = $newNumCard - $oldNumCard; + + if ($user->card + $diff < 0) { + throw new \Exception('Số lượng card cập nhật không hợp lệ vì tổng số card của user không được nhỏ hơn 0.'); + } + + $user->card += $diff; + $user->save(); + + $addCard->num_card = $newNumCard; + $addCard->seller = $data['seller']; + $addCard->date = $data['date']; + $addCard->save(); + }); } } \ No newline at end of file diff --git a/app/Services/Admin/Contracts/AdminServiceInterface.php b/app/Services/Admin/Contracts/AdminServiceInterface.php index e66bcfc..032165f 100644 --- a/app/Services/Admin/Contracts/AdminServiceInterface.php +++ b/app/Services/Admin/Contracts/AdminServiceInterface.php @@ -24,4 +24,6 @@ interface AdminServiceInterface public function deactivateUser(string $msnv): void; public function resetAllCards(): void; + + public function updateAddCard(int $id, array $data): void; } \ No newline at end of file diff --git a/app/Services/Auth/AuthService.php b/app/Services/Auth/AuthService.php index 67df2b9..b5d87d2 100644 --- a/app/Services/Auth/AuthService.php +++ b/app/Services/Auth/AuthService.php @@ -20,7 +20,7 @@ class AuthService implements AuthServiceInterface // Log the user in manually Auth::login($user); $request->session()->regenerate(); - if ($user->status != User::STATUS_ACTIVE) { + if ($user->status != config('constants.STATUS_ACTIVE')) { Auth::logout(); return ['success' => false, 'error_key' => 'permission', 'error_msg' => 'Bạn không có quyền truy cập. Vui lòng liên hệ quản trị viên']; } @@ -37,11 +37,11 @@ class AuthService implements AuthServiceInterface public function getRedirectRouteForUser(User $user): string { - if ($user->role == User::ROLE_ADMIN) { + if ($user->role == config('constants.ROLE_ADMIN')) { return route('admin.dashboard'); } - if ($user->first_login == User::FIRST_LOGIN_TRUE) { + if ($user->first_login == config('constants.FIRST_LOGIN_TRUE')) { return route('user.change_password'); } diff --git a/app/Services/User/UserService.php b/app/Services/User/UserService.php index f595a91..53b7d7f 100644 --- a/app/Services/User/UserService.php +++ b/app/Services/User/UserService.php @@ -27,19 +27,19 @@ class UserService implements UserServiceInterface public function getOtherActiveUsers(User $currentUser): Collection { - return User::where('status', User::STATUS_ACTIVE) + return User::where('status', config('constants.STATUS_ACTIVE')) ->where('msnv', '!=', $currentUser->msnv) ->get(); } public function sendThankcards(User $sender, string $receiverMsnv, int $amount): void { - if ($sender->flag_send == User::FLAG_SEND_DISABLED) { + if ($sender->flag_send == config('constants.FLAG_SEND_DISABLED')) { throw new \RuntimeException(__('messages.error.no_send_permission')); } $receiver = User::where('msnv', $receiverMsnv)->first(); - if (!$receiver || $receiver->status != User::STATUS_ACTIVE) { + if (!$receiver || $receiver->status != config('constants.STATUS_ACTIVE')) { throw new \RuntimeException(__('messages.error.receiver_invalid')); } @@ -91,7 +91,7 @@ class UserService implements UserServiceInterface public function updatePassword(User $user, string $newPassword): void { $user->pass = md5($newPassword); - $user->first_login = User::FIRST_LOGIN_FALSE; + $user->first_login = config('constants.FIRST_LOGIN_FALSE'); $user->save(); } @@ -109,7 +109,7 @@ class UserService implements UserServiceInterface ->sum('sent'); // Calculate current rank (based on received) - $rankings = User::where('status', User::STATUS_ACTIVE) + $rankings = User::where('status', config('constants.STATUS_ACTIVE')) ->addSelect([ 'score' => Administration::selectRaw('COALESCE(SUM(received), 0)') ->whereColumn('msnv', 'user.msnv') @@ -156,7 +156,7 @@ class UserService implements UserServiceInterface $column = $type === 'sent' ? 'sent' : 'received'; - $allActiveUsers = User::where('status', User::STATUS_ACTIVE) + $allActiveUsers = User::where('status', config('constants.STATUS_ACTIVE')) ->addSelect([ 'score' => Administration::selectRaw('COALESCE(SUM(' . $column . '), 0)') ->whereColumn('msnv', 'user.msnv') diff --git a/config/constants.php b/config/constants.php new file mode 100644 index 0000000..2743af2 --- /dev/null +++ b/config/constants.php @@ -0,0 +1,33 @@ + 0, + 'ROLE_ADMIN' => 1, + + 'STATUS_INACTIVE' => 0, + 'STATUS_ACTIVE' => 1, + + 'FLAG_SEND_DISABLED' => 0, + 'FLAG_SEND_ENABLED' => 1, + + 'FIRST_LOGIN_FALSE' => 0, + 'FIRST_LOGIN_TRUE' => 1, + + 'DEPARTMENTS' => [ + 'BIZ-IID - Internet Infra Business Division / BIZ-IID - Sales Enterprise Team (HCM)', + 'BIZ-ITOVN - Vietnam Business Division / BIZ-ITOVN - Ho Chi Minh', + 'BIZ-SSD - Smart Solutions Business Division / BIZ-SSD - Ho Chi Minh', + 'CoE - Center of Excellence / CoE - Data (Ho Chi Minh)', + 'CoE - Center of Excellence / CoE - R&D (Ho Chi Minh)', + 'CoE - Center of Excellence / CoE - UI/UX (Ho Chi Minh)', + 'DL-ITOHCM - ITO Delivery Division (HCM Branch)', + 'DL-ITOHCM - ITO Delivery Division (HCM Branch) / DL-ITOHCM - BrSE Team', + 'DL-ITOHCM - ITO Delivery Division (HCM Branch) / DL-ITOHCM-DU1 - Delivery Unit 1', + 'DL-ITOHCM - ITO Delivery Division (HCM Branch) / DL-ITOHCM-DU2 - Delivery Unit 2', + 'DL-ITOHCM - ITO Delivery Division (HCM Branch) / DL-ITOHCM-DU3 - Delivery Unit 3', + 'DL-ITOHCM - ITO Delivery Division (HCM Branch) / DL-ITOHCM-DU4 - Delivery Unit 4', + 'DL-SSD - Smart Solutions Delivery Division / DL-SSD - Ho Chi Minh', + 'FC-HRD - Human Resources Division / FC-HRD - Ho Chi Minh', + 'FC-RMD - Risk Management Division / FC-RMD - Ho Chi Minh' + ] +]; diff --git a/database/seeders/MockDataSeeder.php b/database/seeders/MockDataSeeder.php index f4b21b5..93037ce 100644 --- a/database/seeders/MockDataSeeder.php +++ b/database/seeders/MockDataSeeder.php @@ -35,11 +35,11 @@ class MockDataSeeder extends Seeder 'mail' => 'admin@runsystem.net', 'pass' => $password, 'departments' => 1, - 'role' => User::ROLE_ADMIN, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 0, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); User::create([ @@ -48,20 +48,20 @@ class MockDataSeeder extends Seeder 'mail' => 'admin2@runsystem.net', 'pass' => $password, 'departments' => 1, - 'role' => User::ROLE_ADMIN, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 0, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); // Active Users (Initialize with specific users needed for logs/transactions) $users = [ - ['msnv' => 1001, 'name' => 'Nguyễn Văn A', 'mail' => 'nguyenvana@runsystem.net', 'card' => 10, 'flag' => User::FLAG_SEND_ENABLED, 'first_login' => User::FIRST_LOGIN_FALSE], - ['msnv' => 1002, 'name' => 'Trần Thị Ngọc', 'mail' => 'tranthingoc@runsystem.net', 'card' => 3, 'flag' => User::FLAG_SEND_ENABLED, 'first_login' => User::FIRST_LOGIN_FALSE], - ['msnv' => 1003, 'name' => 'Lê Kim Thư', 'mail' => 'lekiemthu@runsystem.net', 'card' => 6, 'flag' => User::FLAG_SEND_ENABLED, 'first_login' => User::FIRST_LOGIN_TRUE], // First-time login - ['msnv' => 1004, 'name' => 'Phòng Nhân Sự', 'mail' => 'phongnhansu@runsystem.net', 'card' => 0, 'flag' => User::FLAG_SEND_DISABLED, 'first_login' => User::FIRST_LOGIN_FALSE], // Disabled sending permission - ['msnv' => 1005, 'name' => 'Bản Giang', 'mail' => 'bangiang@runsystem.net', 'card' => 36, 'flag' => User::FLAG_SEND_ENABLED, 'first_login' => User::FIRST_LOGIN_FALSE], + ['msnv' => 1001, 'name' => 'Nguyễn Văn A', 'mail' => 'nguyenvana@runsystem.net', 'card' => 10, 'flag' => config('constants.FLAG_SEND_ENABLED'), 'first_login' => config('constants.FIRST_LOGIN_FALSE')], + ['msnv' => 1002, 'name' => 'Trần Thị Ngọc', 'mail' => 'tranthingoc@runsystem.net', 'card' => 3, 'flag' => config('constants.FLAG_SEND_ENABLED'), 'first_login' => config('constants.FIRST_LOGIN_FALSE')], + ['msnv' => 1003, 'name' => 'Lê Kim Thư', 'mail' => 'lekiemthu@runsystem.net', 'card' => 6, 'flag' => config('constants.FLAG_SEND_ENABLED'), 'first_login' => config('constants.FIRST_LOGIN_TRUE')], // First-time login + ['msnv' => 1004, 'name' => 'Phòng Nhân Sự', 'mail' => 'phongnhansu@runsystem.net', 'card' => 0, 'flag' => config('constants.FLAG_SEND_DISABLED'), 'first_login' => config('constants.FIRST_LOGIN_FALSE')], // Disabled sending permission + ['msnv' => 1005, 'name' => 'Bản Giang', 'mail' => 'bangiang@runsystem.net', 'card' => 36, 'flag' => config('constants.FLAG_SEND_ENABLED'), 'first_login' => config('constants.FIRST_LOGIN_FALSE')], ]; // Generate additional users to reach exactly 100 member users @@ -76,8 +76,8 @@ class MockDataSeeder extends Seeder 'name' => $randomName, 'mail' => 'dev' . str_pad($i, 3, '0', STR_PAD_LEFT) . '@runsystem.net', 'card' => rand(0, 30), - 'flag' => rand(0, 5) > 0 ? User::FLAG_SEND_ENABLED : User::FLAG_SEND_DISABLED, - 'first_login' => rand(0, 4) == 0 ? User::FIRST_LOGIN_TRUE : User::FIRST_LOGIN_FALSE + 'flag' => rand(0, 5) > 0 ? config('constants.FLAG_SEND_ENABLED') : config('constants.FLAG_SEND_DISABLED'), + 'first_login' => rand(0, 4) == 0 ? config('constants.FIRST_LOGIN_TRUE') : config('constants.FIRST_LOGIN_FALSE') ]; } @@ -88,8 +88,8 @@ class MockDataSeeder extends Seeder 'mail' => $u['mail'], 'pass' => $password, 'departments' => rand(1, 5), - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => $u['card'], 'flag_send' => $u['flag'], 'first_login' => $u['first_login'], @@ -115,11 +115,11 @@ class MockDataSeeder extends Seeder 'mail' => 'nghiduy@runsystem.net', 'pass' => $password, 'departments' => 2, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_INACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_INACTIVE'), 'card' => 0, - 'flag_send' => User::FLAG_SEND_DISABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_DISABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); // 2. CREATE CARD ADDITION LOGS (add_card table) @@ -195,5 +195,49 @@ class MockDataSeeder extends Seeder 'date' => $t['date'] ]); } + + // 4. CREATE 10 USERS FOR TIE-RANKING IN CURRENT MONTH + $currentMonth = Carbon::now()->format('Y-m'); + $currentDate = Carbon::now()->format('Y-m-d'); + $depts = config('constants.DEPARTMENTS'); + + for ($i = 1; $i <= 10; $i++) { + $tieMsnv = 8000 + $i; + User::create([ + 'msnv' => $tieMsnv, + 'name' => "Top 1 User {$i}", + 'mail' => "top1_user_{$i}@runsystem.net", + 'pass' => $password, + 'departments' => $depts[($i - 1) % count($depts)], + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 100, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + $senderMsnv = 8000 + $i; + $receiverMsnv = 8000 + ($i % 10 + 1); + + // Sender record + Administration::create([ + 'msnv' => $senderMsnv, + 'received' => 0, + 'sender' => null, + 'sent' => 100, + 'receiver' => $receiverMsnv, + 'date' => $currentDate, + ]); + + // Receiver record + Administration::create([ + 'msnv' => $receiverMsnv, + 'received' => 100, + 'sender' => $senderMsnv, + 'sent' => 0, + 'receiver' => null, + 'date' => $currentDate, + ]); + } } } diff --git a/refactor.php b/refactor.php index 4c7fd92..9a0d913 100644 --- a/refactor.php +++ b/refactor.php @@ -29,79 +29,79 @@ file_put_contents('app/Models/Administration.php', $content); // 3. Update app/Http/Middleware/AdminMiddleware.php $content = file_get_contents('app/Http/Middleware/AdminMiddleware.php'); $content = str_replace('use Illuminate\Support\Facades\Auth;', "use Illuminate\Support\Facades\Auth;\nuse App\Models\User;", $content); -$content = str_replace('Auth::user()->role == 1', 'Auth::user()->role == User::ROLE_ADMIN', $content); -$content = str_replace('Auth::user()->status == 1', 'Auth::user()->status == User::STATUS_ACTIVE', $content); +$content = str_replace('Auth::user()->role == 1', 'Auth::user()->role == config('constants.ROLE_ADMIN')', $content); +$content = str_replace('Auth::user()->status == 1', 'Auth::user()->status == config('constants.STATUS_ACTIVE')', $content); file_put_contents('app/Http/Middleware/AdminMiddleware.php', $content); // 4. Update app/Http/Middleware/MemberMiddleware.php $content = file_get_contents('app/Http/Middleware/MemberMiddleware.php'); $content = str_replace('use Illuminate\Support\Facades\Auth;', "use Illuminate\Support\Facades\Auth;\nuse App\Models\User;", $content); -$content = str_replace('Auth::user()->role == 0', 'Auth::user()->role == User::ROLE_MEMBER', $content); -$content = str_replace('Auth::user()->status == 1', 'Auth::user()->status == User::STATUS_ACTIVE', $content); +$content = str_replace('Auth::user()->role == 0', 'Auth::user()->role == config('constants.ROLE_MEMBER')', $content); +$content = str_replace('Auth::user()->status == 1', 'Auth::user()->status == config('constants.STATUS_ACTIVE')', $content); file_put_contents('app/Http/Middleware/MemberMiddleware.php', $content); // 5. Update app/Http/Middleware/ForceChangePasswordMiddleware.php $content = file_get_contents('app/Http/Middleware/ForceChangePasswordMiddleware.php'); $content = str_replace('use Illuminate\Support\Facades\Auth;', "use Illuminate\Support\Facades\Auth;\nuse App\Models\User;", $content); -$content = str_replace('Auth::user()->first_login == 1', 'Auth::user()->first_login == User::FIRST_LOGIN_TRUE', $content); +$content = str_replace('Auth::user()->first_login == 1', 'Auth::user()->first_login == config('constants.FIRST_LOGIN_TRUE')', $content); file_put_contents('app/Http/Middleware/ForceChangePasswordMiddleware.php', $content); // 6. Update app/Http/Controllers/AuthController.php $content = file_get_contents('app/Http/Controllers/AuthController.php'); $content = str_replace('use Illuminate\Support\Facades\Auth;', "use Illuminate\Support\Facades\Auth;\nuse App\Models\User;", $content); -$content = str_replace('$user->status != 1', '$user->status != User::STATUS_ACTIVE', $content); -$content = str_replace('$user->first_login == 1', '$user->first_login == User::FIRST_LOGIN_TRUE', $content); -$content = str_replace('$user->role == 1', '$user->role == User::ROLE_ADMIN', $content); +$content = str_replace('$user->status != 1', '$user->status != config('constants.STATUS_ACTIVE')', $content); +$content = str_replace('$user->first_login == 1', '$user->first_login == config('constants.FIRST_LOGIN_TRUE')', $content); +$content = str_replace('$user->role == 1', '$user->role == config('constants.ROLE_ADMIN')', $content); file_put_contents('app/Http/Controllers/AuthController.php', $content); // 7. Update app/Http/Controllers/AdminController.php $content = file_get_contents('app/Http/Controllers/AdminController.php'); -$content = str_replace("where('status', 1)", "where('status', User::STATUS_ACTIVE)", $content); -$content = str_replace("'role' => 'required|in:0,1'", "'role' => 'required|in:' . User::ROLE_MEMBER . ',' . User::ROLE_ADMIN", $content); -$content = str_replace("'status' => 1", "'status' => User::STATUS_ACTIVE", $content); -$content = str_replace("'flag_send' => 0", "'flag_send' => User::FLAG_SEND_DISABLED", $content); -$content = str_replace("'first_login' => 1", "'first_login' => User::FIRST_LOGIN_TRUE", $content); -$content = str_replace("\$request->has('flag_send') ? 1 : 0", "\$request->has('flag_send') ? User::FLAG_SEND_ENABLED : User::FLAG_SEND_DISABLED", $content); -$content = str_replace("\$user->status = 0", "\$user->status = User::STATUS_INACTIVE", $content); +$content = str_replace("where('status', 1)", "where('status', config('constants.STATUS_ACTIVE'))", $content); +$content = str_replace("'role' => 'required|in:0,1'", "'role' => 'required|in:' . config('constants.ROLE_MEMBER') . ',' . config('constants.ROLE_ADMIN')", $content); +$content = str_replace("'status' => 1", "'status' => config('constants.STATUS_ACTIVE')", $content); +$content = str_replace("'flag_send' => 0", "'flag_send' => config('constants.FLAG_SEND_DISABLED')", $content); +$content = str_replace("'first_login' => 1", "'first_login' => config('constants.FIRST_LOGIN_TRUE')", $content); +$content = str_replace("\$request->has('flag_send') ? 1 : 0", "\$request->has('flag_send') ? config('constants.FLAG_SEND_ENABLED') : config('constants.FLAG_SEND_DISABLED')", $content); +$content = str_replace("\$user->status = 0", "\$user->status = config('constants.STATUS_INACTIVE')", $content); file_put_contents('app/Http/Controllers/AdminController.php', $content); // 8. Update app/Http/Controllers/UserController.php $content = file_get_contents('app/Http/Controllers/UserController.php'); -$content = str_replace("where('status', 1)", "where('status', User::STATUS_ACTIVE)", $content); +$content = str_replace("where('status', 1)", "where('status', config('constants.STATUS_ACTIVE'))", $content); $content = str_replace("max:5'", "max:' . Administration::MAX_SEND_CARD_PER_MONTH", $content); -$content = str_replace("\$sender->flag_send == 0", "\$sender->flag_send == User::FLAG_SEND_DISABLED", $content); +$content = str_replace("\$sender->flag_send == 0", "\$sender->flag_send == config('constants.FLAG_SEND_DISABLED')", $content); $content = str_replace("> 5)", "> Administration::MAX_SEND_CARD_PER_MONTH)", $content); $content = str_replace("tối đa 5 card", 'tối đa " . Administration::MAX_SEND_CARD_PER_MONTH . " card', $content); -$content = str_replace("\$user->first_login = 0", "\$user->first_login = User::FIRST_LOGIN_FALSE", $content); -$content = str_replace("\$user->role == 1", "\$user->role == User::ROLE_ADMIN", $content); +$content = str_replace("\$user->first_login = 0", "\$user->first_login = config('constants.FIRST_LOGIN_FALSE')", $content); +$content = str_replace("\$user->role == 1", "\$user->role == config('constants.ROLE_ADMIN')", $content); file_put_contents('app/Http/Controllers/UserController.php', $content); // 9. Update app/Console/Commands/ResetCardsCommand.php $content = file_get_contents('app/Console/Commands/ResetCardsCommand.php'); -$content = str_replace("where('status', 1)", "where('status', User::STATUS_ACTIVE)", $content); +$content = str_replace("where('status', 1)", "where('status', config('constants.STATUS_ACTIVE'))", $content); file_put_contents('app/Console/Commands/ResetCardsCommand.php', $content); // 10. Update resources/views/layouts/app.blade.php $content = file_get_contents('resources/views/layouts/app.blade.php'); -$content = str_replace("Auth::user()->role == 1", "Auth::user()->role == \App\Models\User::ROLE_ADMIN", $content); +$content = str_replace("Auth::user()->role == 1", "Auth::user()->role == config('constants.ROLE_ADMIN')", $content); file_put_contents('resources/views/layouts/app.blade.php', $content); // 11. Update resources/views/admin/users/create.blade.php $content = file_get_contents('resources/views/admin/users/create.blade.php'); -$content = str_replace('value="0"', 'value="{{ \App\Models\User::ROLE_MEMBER }}"', $content); -$content = str_replace('value="1"', 'value="{{ \App\Models\User::ROLE_ADMIN }}"', $content); -$content = str_replace("old('role') == '0'", "old('role') == \App\Models\User::ROLE_MEMBER", $content); -$content = str_replace("old('role') == '1'", "old('role') == \App\Models\User::ROLE_ADMIN", $content); +$content = str_replace('value="0"', 'value="{{ config('constants.ROLE_MEMBER') }}"', $content); +$content = str_replace('value="1"', 'value="{{ config('constants.ROLE_ADMIN') }}"', $content); +$content = str_replace("old('role') == '0'", "old('role') == config('constants.ROLE_MEMBER')", $content); +$content = str_replace("old('role') == '1'", "old('role') == config('constants.ROLE_ADMIN')", $content); file_put_contents('resources/views/admin/users/create.blade.php', $content); // 12. Update resources/views/admin/users/edit.blade.php $content = file_get_contents('resources/views/admin/users/edit.blade.php'); -$content = str_replace("\$user->role == 1", "\$user->role == \App\Models\User::ROLE_ADMIN", $content); +$content = str_replace("\$user->role == 1", "\$user->role == config('constants.ROLE_ADMIN')", $content); file_put_contents('resources/views/admin/users/edit.blade.php', $content); // 13. Update resources/views/user/change_password.blade.php $content = file_get_contents('resources/views/user/change_password.blade.php'); -$content = str_replace("Auth::user()->first_login == 1", "Auth::user()->first_login == \App\Models\User::FIRST_LOGIN_TRUE", $content); +$content = str_replace("Auth::user()->first_login == 1", "Auth::user()->first_login == config('constants.FIRST_LOGIN_TRUE')", $content); file_put_contents('resources/views/user/change_password.blade.php', $content); echo "Refactored successfully!"; diff --git a/resources/views/admin/users/partials/stats.blade.php b/resources/views/admin/users/partials/stats.blade.php index 708b74b..1a298c2 100644 --- a/resources/views/admin/users/partials/stats.blade.php +++ b/resources/views/admin/users/partials/stats.blade.php @@ -1,45 +1,69 @@ -
-
+
+
- User nhận nhiều nhất - @if($topReceivedUser) - {{ $topReceivedUser->name }} - MSNV: {{ $topReceivedUser->msnv }} + User nhận nhiều nhất + @if(isset($topReceivedUsers) && $topReceivedUsers->isNotEmpty()) +
+ @foreach($topReceivedUsers as $user) +
+ {{ $user->name }} +
+ MSNV: {{ $user->msnv }} + @if($user->mail) + + Email: {{ $user->mail }} + @endif +
+
+ @endforeach +
@else Không có dữ liệu @endif
-
- {{ $topReceivedUser?->total_received ?? 0 }} +
+ {{ isset($topReceivedUsers) && $topReceivedUsers->isNotEmpty() ? $topReceivedUsers->first()->total_received : 0 }} Thẻ Nhận
-
-
+
+
- User gửi nhiều nhất - @if($topSentUser) - {{ $topSentUser->name }} - MSNV: {{ $topSentUser->msnv }} + User gửi nhiều nhất + @if(isset($topSentUsers) && $topSentUsers->isNotEmpty()) +
+ @foreach($topSentUsers as $user) +
+ {{ $user->name }} +
+ MSNV: {{ $user->msnv }} + @if($user->mail) + + Email: {{ $user->mail }} + @endif +
+
+ @endforeach +
@else Không có dữ liệu @endif
-
- {{ $topSentUser?->total_sent ?? 0 }} +
+ {{ isset($topSentUsers) && $topSentUsers->isNotEmpty() ? $topSentUsers->first()->total_sent : 0 }} Thẻ Gửi
diff --git a/resources/views/admin/users/partials/table.blade.php b/resources/views/admin/users/partials/table.blade.php index bc63edc..331ef08 100644 --- a/resources/views/admin/users/partials/table.blade.php +++ b/resources/views/admin/users/partials/table.blade.php @@ -11,19 +11,24 @@ + + + - @forelse($users as $u) + + @@ -42,19 +61,23 @@ @endif + - @empty - + @endforelse diff --git a/resources/views/components/ranking-item.blade.php b/resources/views/components/ranking-item.blade.php index 69335ea..25e5678 100644 --- a/resources/views/components/ranking-item.blade.php +++ b/resources/views/components/ranking-item.blade.php @@ -1,9 +1,9 @@ @props(['rank', 'name', 'team', 'avatar', 'score', 'isCurrent' => false, 'unit' => 'điểm'])
-
+
-
+
@if($rank == 1) 👑 1 @@ -17,17 +17,17 @@
-
- {{ $name }} -
- {{ $name }} - {{ $team }} +
+ {{ $name }} +
+ {{ $name }} + {{ $team }}
-
+
{{ number_format($score, 0, ',', '.') }} {{ $unit }}
diff --git a/resources/views/components/sidebar.blade.php b/resources/views/components/sidebar.blade.php index e447132..c5dea57 100644 --- a/resources/views/components/sidebar.blade.php +++ b/resources/views/components/sidebar.blade.php @@ -32,7 +32,7 @@ - @if(Auth::check() && Auth::user()->role == \App\Models\User::ROLE_ADMIN) + @if(Auth::check() && Auth::user()->role == config('constants.ROLE_ADMIN')) Quản lý User @@ -63,7 +63,7 @@
{{ Auth::check() ? (Auth::user()->name ?? 'Nguyễn Minh Anh') : 'Nguyễn Minh Anh' }} - {{ Auth::check() ? (Auth::user()->role == \App\Models\User::ROLE_ADMIN ? 'Administrator' : 'Product Team') : 'Product Team' }} + {{ Auth::check() ? (Auth::user()->role == config('constants.ROLE_ADMIN') ? 'Administrator' : 'Product Team') : 'Product Team' }}
diff --git a/resources/views/components/user-filter-form.blade.php b/resources/views/components/user-filter-form.blade.php index 9242659..9fe8243 100644 --- a/resources/views/components/user-filter-form.blade.php +++ b/resources/views/components/user-filter-form.blade.php @@ -38,11 +38,9 @@ diff --git a/resources/views/components/user-form.blade.php b/resources/views/components/user-form.blade.php index 052f81b..217521d 100644 --- a/resources/views/components/user-form.blade.php +++ b/resources/views/components/user-form.blade.php @@ -43,7 +43,7 @@

{{ $user->mail }}

- {{ $user->role == \App\Models\User::ROLE_ADMIN ? 'Admin' : 'Member' }} + {{ $user->role == config('constants.ROLE_ADMIN') ? 'Admin' : 'Member' }} Thẻ: {{ $user->card }} @@ -105,11 +105,10 @@
@error('departments'){{ $message }}@enderror
@@ -131,8 +130,8 @@
@error('role'){{ $message }}@enderror
diff --git a/resources/views/components/user-page.blade.php b/resources/views/components/user-page.blade.php index a85250e..1ea6d9b 100644 --- a/resources/views/components/user-page.blade.php +++ b/resources/views/components/user-page.blade.php @@ -1,4 +1,4 @@ -@props(['mode', 'user' => null, 'administrations' => null, 'selectedMonth' => null]) +@props(['mode', 'user' => null, 'administrations' => null, 'selectedMonth' => null, 'addCards' => null, 'admins' => null]) @if($mode === 'self' && $user)
@@ -29,11 +29,114 @@ @endif
- + @if(in_array($mode, ['edit', 'delete', 'self']) && $user && $administrations) -
+
+ + @if($mode === 'edit' && isset($addCards)) +
+
+

+ Lịch sử cấp phát thẻ +

+
+ +
+
Thao tác MSNV Nhân viênPhòng ban Đã Nhận Đã Gửi Số dư thẻ Quyền gửiVai trò Trạng tháiThao tác
+ Chỉnh sửa + {{ $u->msnv }}
@@ -31,6 +36,20 @@ {{ $u->mail }}
+ @php + $deptVal = $u->departments; + if (is_numeric($deptVal)) { + $idx = ((int)$deptVal) - 1; + $deptName = config('constants.DEPARTMENTS')[$idx] ?? 'Team'; + } else { + $deptName = $deptVal ?? 'Team'; + } + @endphp +
+ {{ $deptName }} +
+
+{{ $u->total_received }} {{ $u->total_sent }} {{ $u->card }} - @if($u->status == \App\Models\User::STATUS_ACTIVE) + @if($u->role == config('constants.ROLE_ADMIN')) + Admin + @else + Member + @endif + + @if($u->status == config('constants.STATUS_ACTIVE')) Đang làm việc @else Đã nghỉ việc @endif - Quản lý -
Chưa có user nào đang hoạt động.Chưa có user nào đang hoạt động.
+ + + + + + + + + + @forelse($addCards as $addCard) + @php + $isCurrentMonth = Carbon\Carbon::parse($addCard->date)->format('Y-m') === Carbon\Carbon::now()->format('Y-m'); + @endphp + + + + + + + @empty + + + + @endforelse + +
Ngày cấpNgười cấp (Admin)Số lượng thẻHành động
{{ Carbon\Carbon::parse($addCard->date)->format('d/m/Y') }} + @if($addCard->sellerUser) + {{ $addCard->sellerUser->name }} (MSNV: {{ $addCard->seller }}) + @else + MSNV: {{ $addCard->seller }} + @endif + +{{ $addCard->num_card }} + @if($isCurrentMonth) + + @else + Tháng trước + @endif +
+ Chưa có lịch sử cấp phát thẻ nào. +
+
+
+ @endif
@endif
+ +@if($mode === 'edit' && isset($addCards) && isset($admins)) + +
+ @csrf + @method('PUT') + +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+
+ + +@endif diff --git a/resources/views/layouts/app.blade.php b/resources/views/layouts/app.blade.php index 36086b4..e740e02 100644 --- a/resources/views/layouts/app.blade.php +++ b/resources/views/layouts/app.blade.php @@ -37,6 +37,16 @@
@endif + @if($errors->any()) +
+
    + @foreach($errors->all() as $error) +
  • {{ $error }}
  • + @endforeach +
+
+ @endif + @yield('content')
diff --git a/resources/views/user/change_password.blade.php b/resources/views/user/change_password.blade.php index 47fba9c..8f12822 100644 --- a/resources/views/user/change_password.blade.php +++ b/resources/views/user/change_password.blade.php @@ -16,7 +16,7 @@
@csrf
- @if(Auth::user()->first_login == \App\Models\User::FIRST_LOGIN_TRUE) + @if(Auth::user()->first_login == config('constants.FIRST_LOGIN_TRUE'))
@@ -63,7 +63,7 @@
- @if(Auth::user()->first_login != \App\Models\User::FIRST_LOGIN_TRUE) + @if(Auth::user()->first_login != config('constants.FIRST_LOGIN_TRUE')) Hủy bỏ @endif diff --git a/resources/views/user/partials/ranking_list.blade.php b/resources/views/user/partials/ranking_list.blade.php index c6e883f..dd08df9 100644 --- a/resources/views/user/partials/ranking_list.blade.php +++ b/resources/views/user/partials/ranking_list.blade.php @@ -2,17 +2,19 @@ @if(count($rankers) > 0)
@foreach($rankers as $item) + @php + $deptVal = $item->departments; + if (is_numeric($deptVal)) { + $idx = ((int)$deptVal) - 1; + $teamName = config('constants.DEPARTMENTS')[$idx] ?? 'Team'; + } else { + $teamName = $deptVal ?? 'Team'; + } + @endphp
+ @php + $currDeptVal = $currentUserRankItem->departments; + if (is_numeric($currDeptVal)) { + $idx = ((int)$currDeptVal) - 1; + $currTeamName = config('constants.DEPARTMENTS')[$idx] ?? 'Team'; + } else { + $currTeamName = $currDeptVal ?? 'Team'; + } + @endphp prefix('admin')-> Route::put('/users/{msnv}', [AdminController::class, 'update'])->name('users.update'); Route::delete('/users/{msnv}', [AdminController::class, 'destroy'])->name('users.destroy'); + Route::put('/add-cards/{id}', [AdminController::class, 'updateAddCard'])->name('add_cards.update'); + Route::post('/reset-cards', [AdminController::class, 'resetCards'])->name('reset_cards'); }); diff --git a/tests/Feature/AdminUserListStatsTest.php b/tests/Feature/AdminUserListStatsTest.php index 1041a52..9a09c62 100644 --- a/tests/Feature/AdminUserListStatsTest.php +++ b/tests/Feature/AdminUserListStatsTest.php @@ -30,11 +30,11 @@ class AdminUserListStatsTest extends TestCase 'mail' => 'usera@example.com', 'pass' => md5('password'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $userB = User::create([ @@ -43,11 +43,11 @@ class AdminUserListStatsTest extends TestCase 'mail' => 'userb@example.com', 'pass' => md5('password'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $selectedMonth = '2026-07'; @@ -110,11 +110,11 @@ class AdminUserListStatsTest extends TestCase 'mail' => 'usera@example.com', 'pass' => md5('password'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $selectedMonth = '2026-07'; @@ -167,11 +167,11 @@ class AdminUserListStatsTest extends TestCase 'mail' => 'alpha@example.com', 'pass' => md5('password'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); User::create([ @@ -180,11 +180,11 @@ class AdminUserListStatsTest extends TestCase 'mail' => 'beta@example.com', 'pass' => md5('password'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); // 2. Fetch statistics via AdminService with search for '2001' (msnv) @@ -211,11 +211,11 @@ class AdminUserListStatsTest extends TestCase 'mail' => 'active@example.com', 'pass' => md5('password'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); User::create([ @@ -224,11 +224,11 @@ class AdminUserListStatsTest extends TestCase 'mail' => 'inactive@example.com', 'pass' => md5('password'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_INACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_INACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_DISABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_DISABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); // 2. Fetch statistics via AdminService with status = '1' @@ -252,12 +252,12 @@ class AdminUserListStatsTest extends TestCase 'name' => 'ADMIN DEV ENABLED', 'mail' => 'admin_dev@example.com', 'pass' => md5('password'), - 'departments' => 1, - 'role' => User::ROLE_ADMIN, - 'status' => User::STATUS_ACTIVE, + 'departments' => config('constants.DEPARTMENTS')[0], + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); User::create([ @@ -265,12 +265,12 @@ class AdminUserListStatsTest extends TestCase 'name' => 'MEMBER HR DISABLED', 'mail' => 'member_hr@example.com', 'pass' => md5('password'), - 'departments' => 2, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'departments' => config('constants.DEPARTMENTS')[13], + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_DISABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_DISABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); // Filter by role = Admin ('1') @@ -279,8 +279,8 @@ class AdminUserListStatsTest extends TestCase $this->assertTrue($users->contains('msnv', 4001)); $this->assertFalse($users->contains('msnv', 4002)); - // Filter by department = HR ('2') - $stats2 = $this->adminService->getUserListWithStats('2026-07', null, '1', null, '2'); + // Filter by department = HR (config('constants.DEPARTMENTS')[13]) + $stats2 = $this->adminService->getUserListWithStats('2026-07', null, '1', null, config('constants.DEPARTMENTS')[13]); $users2 = collect($stats2['users']->items()); $this->assertTrue($users2->contains('msnv', 4002)); $this->assertFalse($users2->contains('msnv', 4001)); @@ -291,4 +291,483 @@ class AdminUserListStatsTest extends TestCase $this->assertTrue($users3->contains('msnv', 4001)); $this->assertFalse($users3->contains('msnv', 4002)); } + + public function test_admin_can_update_user_fields_including_card_role_status_flag_send_first_login(): void + { + $admin = User::create([ + 'msnv' => 9001, + 'name' => 'Admin User', + 'mail' => 'admin@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + $member = User::create([ + 'msnv' => 9002, + 'name' => 'Member User', + 'mail' => 'member@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 5, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + $response = $this->actingAs($admin)->put(route('admin.users.update', 9002), [ + 'card' => 20, // increased by 15 + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_INACTIVE'), + 'flag_send' => config('constants.FLAG_SEND_DISABLED'), + 'first_login' => config('constants.FIRST_LOGIN_TRUE'), + ]); + + $response->assertRedirect(); + + $member->refresh(); + $this->assertEquals(20, $member->card); + $this->assertEquals(config('constants.ROLE_ADMIN'), $member->role); + $this->assertEquals(config('constants.STATUS_INACTIVE'), $member->status); + $this->assertEquals(config('constants.FLAG_SEND_DISABLED'), $member->flag_send); + $this->assertEquals(config('constants.FIRST_LOGIN_TRUE'), $member->first_login); + + // Assert AddCard record was created with diff (15) + $this->assertDatabaseHas('add_card', [ + 'buyer' => 9002, + 'num_card' => 15, + 'seller' => 9001, + ]); + } + + public function test_user_list_stats_handles_ties_for_top_sender_receiver(): void + { + // Clear existing mock data first to have a clean slate for ties + User::truncate(); + Administration::truncate(); + + // 1. Create three users + $user1 = User::create([ + 'msnv' => 3001, + 'name' => 'User One', + 'mail' => 'user1@example.com', + 'pass' => md5('password'), + 'departments' => config('constants.DEPARTMENTS')[0], + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + $user2 = User::create([ + 'msnv' => 3002, + 'name' => 'User Two', + 'mail' => 'user2@example.com', + 'pass' => md5('password'), + 'departments' => config('constants.DEPARTMENTS')[0], + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + $user3 = User::create([ + 'msnv' => 3003, + 'name' => 'User Three', + 'mail' => 'user3@example.com', + 'pass' => md5('password'), + 'departments' => config('constants.DEPARTMENTS')[0], + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + $selectedMonth = '2026-07'; + $testDate = '2026-07-15'; + + // User 1 sends 5 cards to User 2 + Administration::create([ + 'msnv' => 3001, + 'received' => 0, + 'sender' => null, + 'sent' => 5, + 'receiver' => 3002, + 'date' => $testDate, + ]); + Administration::create([ + 'msnv' => 3002, + 'received' => 5, + 'sender' => 3001, + 'sent' => 0, + 'receiver' => null, + 'date' => $testDate, + ]); + + // User 3 sends 5 cards to User 1 + Administration::create([ + 'msnv' => 3003, + 'received' => 0, + 'sender' => null, + 'sent' => 5, + 'receiver' => 3001, + 'date' => $testDate, + ]); + Administration::create([ + 'msnv' => 3001, + 'received' => 5, + 'sender' => 3003, + 'sent' => 0, + 'receiver' => null, + 'date' => $testDate, + ]); + + // Now: + // User 1 has sent = 5, received = 5 + // User 2 has sent = 0, received = 5 + // User 3 has sent = 5, received = 0 + // Top senders should be: User 1 and User 3 (both sent 5) + // Top receivers should be: User 1 and User 2 (both received 5) + + $stats = $this->adminService->getUserListWithStats($selectedMonth); + + $this->assertCount(2, $stats['topReceivedUsers']); + $this->assertCount(2, $stats['topSentUsers']); + + $topReceivedMsnvs = $stats['topReceivedUsers']->pluck('msnv')->toArray(); + $this->assertContains(3001, $topReceivedMsnvs); + $this->assertContains(3002, $topReceivedMsnvs); + + $topSentMsnvs = $stats['topSentUsers']->pluck('msnv')->toArray(); + $this->assertContains(3001, $topSentMsnvs); + $this->assertContains(3003, $topSentMsnvs); + } + + public function test_user_list_stats_handles_10_way_tie_for_top_sender_receiver(): void + { + $users = []; + $selectedMonth = '2026-07'; + $testDate = '2026-07-15'; + + // Create 10 users + for ($i = 1; $i <= 10; $i++) { + $users[$i] = User::create([ + 'msnv' => 5000 + $i, + 'name' => "User Tenfold {$i}", + 'mail' => "user_tenfold_{$i}@example.com", + 'pass' => md5('password'), + 'departments' => config('constants.DEPARTMENTS')[0], + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + } + + // Each user sends 1 card to the next user, and user 10 sends to user 1 + for ($i = 1; $i <= 10; $i++) { + $senderMsnv = 5000 + $i; + $receiverMsnv = 5000 + ($i % 10 + 1); + + // Sender record + Administration::create([ + 'msnv' => $senderMsnv, + 'received' => 0, + 'sender' => null, + 'sent' => 1, + 'receiver' => $receiverMsnv, + 'date' => $testDate, + ]); + + // Receiver record + Administration::create([ + 'msnv' => $receiverMsnv, + 'received' => 1, + 'sender' => $senderMsnv, + 'sent' => 0, + 'receiver' => null, + 'date' => $testDate, + ]); + } + + // Run the service call + $stats = $this->adminService->getUserListWithStats($selectedMonth); + + // Assert all 10 users are returned in topReceivedUsers and topSentUsers + $this->assertCount(10, $stats['topReceivedUsers']); + $this->assertCount(10, $stats['topSentUsers']); + + // Verify all their MSNVs are present + for ($i = 1; $i <= 10; $i++) { + $msnv = 5000 + $i; + $this->assertContains($msnv, $stats['topReceivedUsers']->pluck('msnv')->toArray()); + $this->assertContains($msnv, $stats['topSentUsers']->pluck('msnv')->toArray()); + } + } + + public function test_update_add_card_successfully_updates_balance(): void + { + $admin = User::create([ + 'msnv' => 9001, + 'name' => 'Admin User', + 'mail' => 'admin@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + $member = User::create([ + 'msnv' => 9002, + 'name' => 'Member User', + 'mail' => 'member@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, // currently has 10 cards + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + // Created in the current month + $addCard = \App\Models\AddCard::create([ + 'buyer' => 9002, + 'num_card' => 5, + 'seller' => 9001, + 'date' => Carbon::now()->format('Y-m-d'), + ]); + + // Action: Update it from 5 to 3 (decrease of 2, new user balance should be 10 - 2 = 8) + $response = $this->actingAs($admin)->put(route('admin.add_cards.update', $addCard->id), [ + 'num_card' => 3, + 'seller' => 9001, + 'date' => Carbon::now()->format('Y-m-d'), + ]); + + $response->assertRedirect(); + + $addCard->refresh(); + $this->assertEquals(3, $addCard->num_card); + + $member->refresh(); + $this->assertEquals(8, $member->card); + } + + public function test_update_add_card_fails_if_past_month(): void + { + $admin = User::create([ + 'msnv' => 9001, + 'name' => 'Admin User', + 'mail' => 'admin@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + $member = User::create([ + 'msnv' => 9002, + 'name' => 'Member User', + 'mail' => 'member@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + // Created in a past month + $addCard = \App\Models\AddCard::create([ + 'buyer' => 9002, + 'num_card' => 5, + 'seller' => 9001, + 'date' => Carbon::now()->subMonth()->format('Y-m-d'), + ]); + + $response = $this->actingAs($admin)->put(route('admin.add_cards.update', $addCard->id), [ + 'num_card' => 3, + 'seller' => 9001, + 'date' => Carbon::now()->format('Y-m-d'), + ]); + + $response->assertRedirect(); + $response->assertSessionHasErrors('error'); + $this->assertTrue(session('errors')->has('error')); + $this->assertEquals('Không cho phép chỉnh sửa dữ liệu card của các tháng trước.', session('errors')->first('error')); + } + + public function test_update_add_card_fails_if_new_date_not_current_month(): void + { + $admin = User::create([ + 'msnv' => 9001, + 'name' => 'Admin User', + 'mail' => 'admin@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + $member = User::create([ + 'msnv' => 9002, + 'name' => 'Member User', + 'mail' => 'member@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + // Created in current month + $addCard = \App\Models\AddCard::create([ + 'buyer' => 9002, + 'num_card' => 5, + 'seller' => 9001, + 'date' => Carbon::now()->format('Y-m-d'), + ]); + + // Update with date in a past month + $response = $this->actingAs($admin)->put(route('admin.add_cards.update', $addCard->id), [ + 'num_card' => 3, + 'seller' => 9001, + 'date' => Carbon::now()->subMonth()->format('Y-m-d'), + ]); + + $response->assertRedirect(); + $response->assertSessionHasErrors('error'); + $this->assertEquals('Chỉ được phép chỉnh sửa dữ liệu của tháng hiện tại.', session('errors')->first('error')); + } + + public function test_update_add_card_fails_if_new_balance_negative(): void + { + $admin = User::create([ + 'msnv' => 9001, + 'name' => 'Admin User', + 'mail' => 'admin@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + $member = User::create([ + 'msnv' => 9002, + 'name' => 'Member User', + 'mail' => 'member@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 2, // has 2 cards left + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + // Created in current month + $addCard = \App\Models\AddCard::create([ + 'buyer' => 9002, + 'num_card' => 5, + 'seller' => 9001, + 'date' => Carbon::now()->format('Y-m-d'), + ]); + + // Decrease it from 5 to 1 (decrease of 4). Since member only has 2 cards, this would make balance -2 (2 - 4 = -2), which is illegal + $response = $this->actingAs($admin)->put(route('admin.add_cards.update', $addCard->id), [ + 'num_card' => 1, + 'seller' => 9001, + 'date' => Carbon::now()->format('Y-m-d'), + ]); + + $response->assertRedirect(); + $response->assertSessionHasErrors('error'); + $this->assertEquals('Số lượng card cập nhật không hợp lệ vì tổng số card của user không được nhỏ hơn 0.', session('errors')->first('error')); + } + + public function test_update_add_card_succeeds_for_older_transaction_in_current_month(): void + { + $admin = User::create([ + 'msnv' => 9001, + 'name' => 'Admin User', + 'mail' => 'admin@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 10, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + $member = User::create([ + 'msnv' => 9002, + 'name' => 'Member User', + 'mail' => 'member@example.com', + 'pass' => md5('password'), + 'departments' => 1, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), + 'card' => 15, // has 15 cards + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), + ]); + + // Transaction 1 (older) + $oldAddCard = \App\Models\AddCard::create([ + 'buyer' => 9002, + 'num_card' => 5, + 'seller' => 9001, + 'date' => Carbon::now()->format('Y-m-d'), + ]); + + // Transaction 2 (newest) + $newAddCard = \App\Models\AddCard::create([ + 'buyer' => 9002, + 'num_card' => 10, + 'seller' => 9001, + 'date' => Carbon::now()->format('Y-m-d'), + ]); + + // Try to update older Transaction 1: change num_card from 5 to 6 (increase of 1) + $response = $this->actingAs($admin)->put(route('admin.add_cards.update', $oldAddCard->id), [ + 'num_card' => 6, + 'seller' => 9001, + 'date' => Carbon::now()->format('Y-m-d'), + ]); + + $response->assertRedirect(); + $response->assertSessionHasNoErrors(); + + $oldAddCard->refresh(); + $this->assertEquals(6, $oldAddCard->num_card); + + $member->refresh(); + $this->assertEquals(16, $member->card); + } } diff --git a/tests/Feature/LoginNotificationTest.php b/tests/Feature/LoginNotificationTest.php index c299aae..736d515 100644 --- a/tests/Feature/LoginNotificationTest.php +++ b/tests/Feature/LoginNotificationTest.php @@ -29,11 +29,11 @@ class LoginNotificationTest extends TestCase 'mail' => 'test@example.com', 'pass' => md5('password123'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $response = $this->post('/login', [ @@ -88,11 +88,11 @@ class LoginNotificationTest extends TestCase 'mail' => 'test@example.com', 'pass' => md5('password123'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_INACTIVE, // 0 + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_INACTIVE'), // 0 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $response = $this->post('/login', [ @@ -128,11 +128,11 @@ class LoginNotificationTest extends TestCase 'mail' => 'member@example.com', 'pass' => md5('password123'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $response = $this->post('/login', [ @@ -153,11 +153,11 @@ class LoginNotificationTest extends TestCase 'mail' => 'admin@example.com', 'pass' => md5('password123'), 'departments' => 1, - 'role' => User::ROLE_ADMIN, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $response2 = $this->post('/login', [ @@ -180,11 +180,11 @@ class LoginNotificationTest extends TestCase 'mail' => 'first@example.com', 'pass' => md5('password123'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_TRUE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_TRUE'), ]); $response = $this->post('/login', [ @@ -205,11 +205,11 @@ class LoginNotificationTest extends TestCase 'mail' => 'first_admin@example.com', 'pass' => md5('password123'), 'departments' => 1, - 'role' => User::ROLE_ADMIN, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_TRUE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_TRUE'), ]); $response = $this->post('/login', [ @@ -233,11 +233,11 @@ class LoginNotificationTest extends TestCase 'mail' => 'test@example.com', 'pass' => md5('password123'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); // First 5 attempts fail diff --git a/tests/Feature/PasswordMd5Test.php b/tests/Feature/PasswordMd5Test.php index 0a21f06..5095ddd 100644 --- a/tests/Feature/PasswordMd5Test.php +++ b/tests/Feature/PasswordMd5Test.php @@ -30,11 +30,11 @@ class PasswordMd5Test extends TestCase 'mail' => 'test@example.com', 'pass' => md5('old_password'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_TRUE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_TRUE'), ]); $this->userService->updatePassword($user, 'new_secret_password'); @@ -42,7 +42,7 @@ class PasswordMd5Test extends TestCase $user->refresh(); $this->assertEquals(md5('new_secret_password'), $user->pass); - $this->assertEquals(User::FIRST_LOGIN_FALSE, $user->first_login); + $this->assertEquals(config('constants.FIRST_LOGIN_FALSE'), $user->first_login); } public function test_admin_service_create_user_stores_password_as_md5(): void @@ -53,7 +53,7 @@ class PasswordMd5Test extends TestCase 'mail' => 'created@example.com', 'departments' => 2, 'password' => 'admin_created_pass', - 'role' => User::ROLE_MEMBER, + 'role' => config('constants.ROLE_MEMBER'), ]; $this->adminService->createUser($data); @@ -62,6 +62,6 @@ class PasswordMd5Test extends TestCase $this->assertNotNull($user); $this->assertEquals(md5('admin_created_pass'), $user->pass); - $this->assertEquals(User::FIRST_LOGIN_TRUE, $user->first_login); + $this->assertEquals(config('constants.FIRST_LOGIN_TRUE'), $user->first_login); } } diff --git a/tests/Feature/PasswordValidationTest.php b/tests/Feature/PasswordValidationTest.php index 846cc14..33562ad 100644 --- a/tests/Feature/PasswordValidationTest.php +++ b/tests/Feature/PasswordValidationTest.php @@ -22,11 +22,11 @@ class PasswordValidationTest extends TestCase 'mail' => 'test@example.com', 'pass' => md5('password123'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_TRUE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_TRUE'), ]); } diff --git a/tests/Feature/SendThankCardTest.php b/tests/Feature/SendThankCardTest.php index dfdd462..a3a0eff 100644 --- a/tests/Feature/SendThankCardTest.php +++ b/tests/Feature/SendThankCardTest.php @@ -28,11 +28,11 @@ class SendThankCardTest extends TestCase 'mail' => 'sender@example.com', 'pass' => md5('password'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $this->receiver = User::create([ @@ -41,11 +41,11 @@ class SendThankCardTest extends TestCase 'mail' => 'receiver@example.com', 'pass' => md5('password'), 'departments' => 1, - 'role' => User::ROLE_MEMBER, - 'status' => User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 0, - 'flag_send' => User::FLAG_SEND_ENABLED, - 'first_login' => User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); } diff --git a/tests/Feature/UserDashboardLayoutTest.php b/tests/Feature/UserDashboardLayoutTest.php index 3f34aa7..6140f3a 100644 --- a/tests/Feature/UserDashboardLayoutTest.php +++ b/tests/Feature/UserDashboardLayoutTest.php @@ -17,11 +17,11 @@ class UserDashboardLayoutTest extends TestCase 'mail' => 'admin@runsystem.net', 'pass' => md5('password'), 'departments' => 1, - 'role' => \App\Models\User::ROLE_ADMIN, - 'status' => \App\Models\User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_ADMIN'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => \App\Models\User::FLAG_SEND_ENABLED, - 'first_login' => \App\Models\User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $this->actingAs($user); @@ -46,11 +46,11 @@ class UserDashboardLayoutTest extends TestCase 'mail' => 'usera@runsystem.net', 'pass' => md5('password'), 'departments' => 1, - 'role' => \App\Models\User::ROLE_MEMBER, - 'status' => \App\Models\User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => \App\Models\User::FLAG_SEND_ENABLED, - 'first_login' => \App\Models\User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $user2 = \App\Models\User::create([ @@ -59,11 +59,11 @@ class UserDashboardLayoutTest extends TestCase 'mail' => 'userb@runsystem.net', 'pass' => md5('password'), 'departments' => 2, - 'role' => \App\Models\User::ROLE_MEMBER, - 'status' => \App\Models\User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => \App\Models\User::FLAG_SEND_ENABLED, - 'first_login' => \App\Models\User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); \App\Models\Administration::create([ @@ -101,11 +101,11 @@ class UserDashboardLayoutTest extends TestCase 'mail' => 'usera@runsystem.net', 'pass' => md5('password'), 'departments' => 1, - 'role' => \App\Models\User::ROLE_MEMBER, - 'status' => \App\Models\User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => \App\Models\User::FLAG_SEND_ENABLED, - 'first_login' => \App\Models\User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $this->actingAs($user); @@ -126,11 +126,11 @@ class UserDashboardLayoutTest extends TestCase 'mail' => "user{$char}@runsystem.net", 'pass' => md5('password'), 'departments' => 1, - 'role' => \App\Models\User::ROLE_MEMBER, - 'status' => \App\Models\User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => \App\Models\User::FLAG_SEND_ENABLED, - 'first_login' => \App\Models\User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); \App\Models\Administration::create([ @@ -163,11 +163,11 @@ class UserDashboardLayoutTest extends TestCase 'mail' => 'active@runsystem.net', 'pass' => md5('password'), 'departments' => 1, - 'role' => \App\Models\User::ROLE_MEMBER, - 'status' => \App\Models\User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => \App\Models\User::FLAG_SEND_ENABLED, - 'first_login' => \App\Models\User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); $inactiveUser = \App\Models\User::create([ @@ -176,11 +176,11 @@ class UserDashboardLayoutTest extends TestCase 'mail' => 'inactive@runsystem.net', 'pass' => md5('password'), 'departments' => 1, - 'role' => \App\Models\User::ROLE_MEMBER, - 'status' => \App\Models\User::STATUS_INACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_INACTIVE'), 'card' => 10, - 'flag_send' => \App\Models\User::FLAG_SEND_ENABLED, - 'first_login' => \App\Models\User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); \App\Models\Administration::create([ @@ -218,11 +218,11 @@ class UserDashboardLayoutTest extends TestCase 'mail' => 'usera@runsystem.net', 'pass' => md5('password'), 'departments' => 1, - 'role' => \App\Models\User::ROLE_MEMBER, - 'status' => \App\Models\User::STATUS_ACTIVE, + 'role' => config('constants.ROLE_MEMBER'), + 'status' => config('constants.STATUS_ACTIVE'), 'card' => 10, - 'flag_send' => \App\Models\User::FLAG_SEND_ENABLED, - 'first_login' => \App\Models\User::FIRST_LOGIN_FALSE, + 'flag_send' => config('constants.FLAG_SEND_ENABLED'), + 'first_login' => config('constants.FIRST_LOGIN_FALSE'), ]); \App\Models\Administration::create([