all feature

This commit is contained in:
antv
2026-06-29 11:55:33 +07:00
parent 08d13336cd
commit 8a77324f89
59 changed files with 5587 additions and 145 deletions
@@ -0,0 +1,33 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\User;
class ResetCardsCommand extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'cards:reset';
/**
* The console command description.
*
* @var string
*/
protected $description = 'Reset toàn bộ thẻ của hệ thống về 0 vào đầu tháng';
/**
* Execute the console command.
*/
public function handle()
{
User::where('status', User::STATUS_ACTIVE)->update(['card' => 0]);
$this->info('Đã reset toàn bộ số lượng thẻ của nhân viên đang hoạt động về 0 thành công!');
}
}
+143
View File
@@ -0,0 +1,143 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Administration;
use App\Models\AddCard;
use Carbon\Carbon;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
class AdminController extends Controller
{
public function index(Request $request)
{
$selectedMonth = $request->input('month', Carbon::now()->format('Y-m'));
$startOfMonth = Carbon::parse($selectedMonth)->startOfMonth();
$endOfMonth = Carbon::parse($selectedMonth)->endOfMonth();
// 1. Calculate top stats from all active users
$allActiveUsers = User::where('status', User::STATUS_ACTIVE)->get()->map(function($user) use ($startOfMonth, $endOfMonth) {
$user->total_received = Administration::where('receiver', $user->msnv)
->whereBetween('date', [$startOfMonth, $endOfMonth])
->sum('received');
$user->total_sent = Administration::where('sender', $user->msnv)
->whereBetween('date', [$startOfMonth, $endOfMonth])
->sum('sent');
return $user;
});
$topReceivedUser = $allActiveUsers->sortByDesc('total_received')->first();
$topSentUser = $allActiveUsers->sortByDesc('total_sent')->first();
// 2. Paginate users for the list table (10 per page)
$users = User::where('status', User::STATUS_ACTIVE)->paginate(10)->withQueryString();
// 3. Compute stats only for the paginated users (to display in the table)
$users->getCollection()->transform(function($user) use ($startOfMonth, $endOfMonth) {
$user->total_received = Administration::where('receiver', $user->msnv)
->whereBetween('date', [$startOfMonth, $endOfMonth])
->sum('received');
$user->total_sent = Administration::where('sender', $user->msnv)
->whereBetween('date', [$startOfMonth, $endOfMonth])
->sum('sent');
return $user;
});
return view('admin.users.index', compact('users', 'selectedMonth', 'topReceivedUser', 'topSentUser'));
}
public function create()
{
return view('admin.users.create');
}
public function store(Request $request)
{
$request->validate([
'msnv' => 'required|unique:user,msnv',
'mail' => 'required|email|unique:user,mail',
'password' => 'required|min:6',
'role' => 'required|in:' . User::ROLE_MEMBER . ',' . User::ROLE_ADMIN
]);
User::create([
'msnv' => $request->msnv,
'mail' => $request->mail,
'pass' => Hash::make($request->password),
'role' => $request->role,
'status' => User::STATUS_ACTIVE,
'card' => 0,
'flag_send' => User::FLAG_SEND_DISABLED,
'first_login' => User::FIRST_LOGIN_TRUE
]);
return redirect()->route('admin.users.index')->with('success', 'Tạo user thành công!');
}
public function edit(Request $request, $msnv)
{
$user = User::where('msnv', $msnv)->firstOrFail();
$selectedMonth = $request->input('month', Carbon::now()->format('Y-m'));
$startOfMonth = Carbon::parse($selectedMonth)->startOfMonth();
$endOfMonth = Carbon::parse($selectedMonth)->endOfMonth();
$administrations = Administration::where(function($q) use ($msnv) {
$q->where('sender', $msnv)->orWhere('receiver', $msnv);
})
->whereBetween('date', [$startOfMonth, $endOfMonth])
->orderBy('date', 'desc')
->get();
return view('admin.users.edit', compact('user', 'administrations', 'selectedMonth'));
}
public function update(Request $request, $msnv)
{
$user = User::where('msnv', $msnv)->firstOrFail();
$request->validate([
'num_card' => 'nullable|integer|min:1',
'flag_send' => 'nullable|boolean'
]);
DB::transaction(function () use ($request, $user) {
if ($request->filled('num_card')) {
AddCard::create([
'buyer' => $user->msnv,
'num_card' => $request->num_card,
'seller' => auth()->user()->msnv,
'date' => Carbon::today()
]);
$user->card += $request->num_card;
}
$user->flag_send = $request->has('flag_send') ? User::FLAG_SEND_ENABLED : User::FLAG_SEND_DISABLED;
$user->save();
});
return redirect()->back()->with('success', 'Cập nhật thành công!');
}
public function destroy($msnv)
{
$user = User::where('msnv', $msnv)->firstOrFail();
$user->status = User::STATUS_INACTIVE;
$user->save();
return redirect()->route('admin.users.index')->with('success', 'Đã đánh dấu nhân viên nghỉ việc thành công!');
}
public function resetCards()
{
User::where('status', User::STATUS_ACTIVE)->update(['card' => 0]);
return redirect()->back()->with('success', 'Reset thẻ toàn hệ thống thành công!');
}
}
+66
View File
@@ -0,0 +1,66 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
class AuthController extends Controller
{
public function showLoginForm()
{
if (Auth::check()) {
return $this->redirectBasedOnRole(Auth::user());
}
return view('login');
}
public function login(Request $request)
{
$credentials = $request->validate([
'mail' => 'required|email',
'password' => 'required'
]);
if (Auth::attempt(['mail' => $credentials['mail'], 'password' => $credentials['password']])) {
$request->session()->regenerate();
$user = Auth::user();
if ($user->status != User::STATUS_ACTIVE) {
Auth::logout();
return back()->withErrors([
'mail' => 'Tài khoản của bạn đã bị khóa hoặc bạn đã nghỉ việc.',
]);
}
return $this->redirectBasedOnRole($user);
}
return back()->withErrors([
'mail' => 'Email hoặc mật khẩu không chính xác.',
]);
}
public function logout(Request $request)
{
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
return redirect('/login');
}
private function redirectBasedOnRole($user)
{
if ($user->first_login == User::FIRST_LOGIN_TRUE) {
return redirect()->route('user.change_password');
}
if ($user->role == User::ROLE_ADMIN) {
return redirect()->route('admin.dashboard');
}
return redirect()->route('user.dashboard');
}
}
+136
View File
@@ -0,0 +1,136 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Models\User;
use App\Models\Administration;
use Carbon\Carbon;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Auth;
class UserController extends Controller
{
public function index(Request $request)
{
$selectedMonth = $request->input('month', Carbon::now()->format('Y-m'));
$startOfMonth = Carbon::parse($selectedMonth)->startOfMonth();
$endOfMonth = Carbon::parse($selectedMonth)->endOfMonth();
$user = Auth::user();
// Lấy tất cả card nhận và gửi của user này
$administrations = Administration::where('msnv', $user->msnv)
->whereBetween('date', [$startOfMonth, $endOfMonth])
->orderBy('date', 'desc')
->get();
return view('user.dashboard', compact('administrations', 'selectedMonth', 'user'));
}
public function sendThankcards()
{
$users = User::where('status', User::STATUS_ACTIVE)->where('msnv', '!=', Auth::user()->msnv)->get();
return view('user.send', compact('users'));
}
public function storeThankcards(Request $request)
{
$request->validate([
'receiver' => 'required|exists:user,msnv',
'amount' => 'required|integer|min:1|max:' . Administration::MAX_SEND_CARD_PER_MONTH
]);
$sender = Auth::user();
$receiverMsnv = $request->receiver;
$amount = $request->amount;
if ($sender->flag_send == User::FLAG_SEND_DISABLED) {
return response()->json([
'success' => false,
'message' => 'Bạn hiện không có quyền gửi Thank Card.'
], 422);
}
$startOfMonth = Carbon::now()->startOfMonth();
$endOfMonth = Carbon::now()->endOfMonth();
// Tính tổng số lượng card ĐÃ gửi cho người này trong tháng
$cardsSentToThisUserThisMonth = Administration::where('msnv', $sender->msnv)
->where('receiver', $receiverMsnv)
->whereBetween('date', [$startOfMonth, $endOfMonth])
->sum('sent');
if ($cardsSentToThisUserThisMonth + $amount > Administration::MAX_SEND_CARD_PER_MONTH) {
return response()->json([
'success' => false,
'message' => "Một tháng bạn chỉ được gửi tối đa " . Administration::MAX_SEND_CARD_PER_MONTH . " card cho một người. Bạn đã gửi {$cardsSentToThisUserThisMonth} card cho nhân viên này."
], 422);
}
// Validate số lượng thẻ còn lại
if ($sender->card < $amount) {
return response()->json([
'success' => false,
'message' => 'Số lượng card của bạn không đủ. Vui lòng liên hệ Admin.'
], 422);
}
DB::transaction(function () use ($sender, $receiverMsnv, $amount) {
// 1. Insert thông tin người nhận
Administration::create([
'msnv' => $receiverMsnv,
'received' => $amount,
'sender' => $sender->msnv,
'sent' => 0,
'receiver' => null,
'date' => Carbon::today()
]);
// 2. Insert thông tin người gửi
Administration::create([
'msnv' => $sender->msnv,
'received' => 0,
'sender' => null,
'sent' => $amount,
'receiver' => $receiverMsnv,
'date' => Carbon::today()
]);
// 3. Trừ số card của người gửi
$sender->card -= $amount;
$sender->save();
// TODO: Bắn tin nhắn CO tới người nhận (Chatwork/System Notification)
});
return response()->json([
'success' => true,
'message' => 'Đã gửi Thank card thành công!'
]);
}
public function changePasswordForm()
{
return view('user.change_password');
}
public function updatePassword(Request $request)
{
$request->validate([
'password' => 'required|min:6|confirmed'
]);
$user = Auth::user();
$user->pass = Hash::make($request->password);
$user->first_login = User::FIRST_LOGIN_FALSE;
$user->save();
if ($user->role == User::ROLE_ADMIN) {
return redirect()->route('admin.dashboard')->with('success', 'Đổi mật khẩu thành công!');
}
return redirect()->route('user.dashboard')->with('success', 'Đổi mật khẩu thành công!');
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
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) {
return $next($request);
}
return redirect()->route('login');
}
}
@@ -0,0 +1,22 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
class ForceChangePasswordMiddleware
{
public function handle(Request $request, Closure $next): Response
{
if (Auth::check() && Auth::user()->first_login == User::FIRST_LOGIN_TRUE) {
if (!$request->routeIs('user.change_password') && !$request->routeIs('user.update_password') && !$request->routeIs('logout')) {
return redirect()->route('user.change_password');
}
}
return $next($request);
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Illuminate\Support\Facades\Auth;
use App\Models\User;
class MemberMiddleware
{
public function handle(Request $request, Closure $next): Response
{
if (Auth::check() && Auth::user()->role == User::ROLE_MEMBER && Auth::user()->status == User::STATUS_ACTIVE) {
return $next($request);
}
return redirect()->route('login');
}
}
+20
View File
@@ -0,0 +1,20 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class AddCard extends Model
{
use HasFactory;
protected $table = 'add_card';
protected $fillable = [
'buyer',
'num_card',
'seller',
'date',
];
}
+25
View File
@@ -0,0 +1,25 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
class Administration extends Model
{
use HasFactory;
const MAX_SEND_CARD_PER_MONTH = 5;
protected $table = 'administration';
protected $fillable = [
'msnv',
'received',
'sender',
'sent',
'receiver',
'date',
];
}
+31 -24
View File
@@ -2,48 +2,55 @@
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Database\Factories\UserFactory;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
class User extends Authenticatable
{
/** @use HasFactory<UserFactory> */
use HasFactory, Notifiable;
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
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';
protected $primaryKey = 'msnv';
public $incrementing = false;
protected $keyType = 'string';
protected $fillable = [
'name',
'email',
'password',
'msnv',
'mail',
'pass',
'role',
'status',
'card',
'flag_send',
'first_login',
];
/**
* The attributes that should be hidden for serialization.
*
* @var list<string>
*/
protected $hidden = [
'password',
'pass',
'remember_token',
];
/**
* Get the attributes that should be cast.
* Get the password for the user.
*
* @return array<string, string>
* @return string
*/
protected function casts(): array
public function getAuthPassword()
{
return [
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
return $this->pass;
}
}