feature: user list

This commit is contained in:
antv
2026-07-08 15:29:13 +07:00
parent 7d9ba527a3
commit e476c2768a
32 changed files with 1091 additions and 284 deletions
+46 -10
View File
@@ -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();
+1 -1
View File
@@ -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);
}
+2 -2
View File
@@ -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'));
}
}
@@ -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');
@@ -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');
}
@@ -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');
+5
View File
@@ -19,4 +19,9 @@ class AddCard extends Model
'seller',
'date',
];
public function sellerUser()
{
return $this->belongsTo(User::class, 'seller', 'msnv');
}
}
-12
View File
@@ -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;
+71 -14
View File
@@ -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();
});
}
}
@@ -24,4 +24,6 @@ interface AdminServiceInterface
public function deactivateUser(string $msnv): void;
public function resetAllCards(): void;
public function updateAddCard(int $id, array $data): void;
}
+3 -3
View File
@@ -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');
}
+6 -6
View File
@@ -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')