added leave request, profile and staff members controllers
This commit is contained in:
143
app/Http/Controllers/LeaveRequestController.php
Normal file
143
app/Http/Controllers/LeaveRequestController.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\LeaveRequest;
|
||||
use App\Models\StaffMember;
|
||||
use Illuminate\Http\Request;
|
||||
use Carbon\Carbon;
|
||||
|
||||
class LeaveRequestController extends Controller
|
||||
{
|
||||
|
||||
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = LeaveRequest::with('staff');
|
||||
|
||||
// Search by Staff Name
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->whereHas('staff', function($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
// Filter by Status
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->status);
|
||||
} else {
|
||||
// Default to showing pending requests first if no filter is applied
|
||||
$query->orderByRaw("FIELD(status, 'PENDING', 'APPROVED', 'REJECTED')");
|
||||
}
|
||||
|
||||
$leaveRequests = $query->orderBy('created_at', 'desc')->paginate(15);
|
||||
$leaveRequests->appends($request->query());
|
||||
|
||||
// KPIs for HR
|
||||
$pendingCount = LeaveRequest::where('status', 'PENDING')->count();
|
||||
$approvedThisMonth = LeaveRequest::where('status', 'APPROVED')
|
||||
->whereMonth('start_date', Carbon::now()->month)
|
||||
->whereYear('start_date', Carbon::now()->year)
|
||||
->count();
|
||||
|
||||
$data = [
|
||||
'leaveRequests' => $leaveRequests,
|
||||
'pendingCount' => $pendingCount,
|
||||
'approvedThisMonth' => $approvedThisMonth,
|
||||
];
|
||||
|
||||
return view('leave.index', $data);
|
||||
}
|
||||
public function myLeave()
|
||||
{
|
||||
// Find the staff profile of the currently logged-in user
|
||||
// Adjust this logic if you link Users to StaffMembers differently (e.g., via a staff_id foreign key)
|
||||
$staff = StaffMember::where('email', auth()->user()->email)->firstOrFail();
|
||||
|
||||
// Fetch their leave history, latest first
|
||||
$leaveRequests = LeaveRequest::where('staff_member_id', $staff->id)
|
||||
->orderBy('created_at', 'desc')
|
||||
->paginate(10);
|
||||
|
||||
$data = [
|
||||
'staff' => $staff,
|
||||
'leaveRequests' => $leaveRequests,
|
||||
];
|
||||
|
||||
return view('leave.my_leave', $data);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'staff_member_id' => 'required|exists:staff_members,id',
|
||||
'leave_type' => 'required|string',
|
||||
'start_date' => 'required|date',
|
||||
'end_date' => 'required|date|after_or_equal:start_date',
|
||||
'reason' => 'required|string',
|
||||
]);
|
||||
|
||||
$start = Carbon::parse($validated['start_date']);
|
||||
$end = Carbon::parse($validated['end_date']);
|
||||
|
||||
// Calculate total days excluding weekends
|
||||
$totalDays = $start->diffInDaysFiltered(function (Carbon $date) {
|
||||
return !$date->isWeekend();
|
||||
}, $end) + 1; // +1 to include both the start and end day
|
||||
|
||||
|
||||
if ($validated['leave_type'] === 'Annual') {
|
||||
$staff = StaffMember::find($validated['staff_member_id']);
|
||||
if ($staff->annual_leave_balance < $totalDays) {
|
||||
return response()->json([
|
||||
'success' => false,
|
||||
'message' => "Insufficient balance. Requested: $totalDays days. Available: {$staff->annual_leave_balance} days."
|
||||
], 422);
|
||||
}
|
||||
}
|
||||
|
||||
$validated['total_days'] = $totalDays;
|
||||
$validated['status'] = 'PENDING';
|
||||
|
||||
LeaveRequest::create($validated);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Leave request submitted successfully!']);
|
||||
}
|
||||
|
||||
|
||||
public function updateStatus(Request $request, $id)
|
||||
{
|
||||
$leaveRequest = LeaveRequest::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'status' => 'required|in:APPROVED,REJECTED',
|
||||
'admin_remarks' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validated['status'] === 'APPROVED' && $leaveRequest->status !== 'APPROVED') {
|
||||
if ($leaveRequest->leave_type === 'Annual') {
|
||||
$staff = $leaveRequest->staff;
|
||||
$staff->annual_leave_balance -= $leaveRequest->total_days;
|
||||
$staff->save();
|
||||
}
|
||||
}
|
||||
|
||||
if ($validated['status'] === 'REJECTED' && $leaveRequest->status === 'APPROVED') {
|
||||
if ($leaveRequest->leave_type === 'Annual') {
|
||||
$staff = $leaveRequest->staff;
|
||||
$staff->annual_leave_balance += $leaveRequest->total_days;
|
||||
$staff->save();
|
||||
}
|
||||
}
|
||||
|
||||
$leaveRequest->update([
|
||||
'status' => $validated['status'],
|
||||
'admin_remarks' => $validated['admin_remarks'],
|
||||
'approved_by' => auth()->id(), // Track who approved it
|
||||
]);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Leave status updated successfully!']);
|
||||
}
|
||||
}
|
||||
101
app/Http/Controllers/ProfileController.php
Normal file
101
app/Http/Controllers/ProfileController.php
Normal file
@@ -0,0 +1,101 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\StaffMember;
|
||||
use App\Models\StaffDependent;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class ProfileController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$staff = StaffMember::with('dependents')->where('email', auth()->user()->email)->firstOrFail();
|
||||
|
||||
return view('profile.index', compact('staff'));
|
||||
}
|
||||
|
||||
public function update(Request $request)
|
||||
{
|
||||
$staff = StaffMember::where('email', auth()->user()->email)->firstOrFail();
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:200',
|
||||
'phone' => 'nullable|string|max:200',
|
||||
'personal_email' => 'nullable|email|max:191',
|
||||
'location_country' => 'nullable|string|max:191',
|
||||
'birth_month' => 'nullable|string|size:2',
|
||||
'birth_day' => 'nullable|string|size:2',
|
||||
'photo' => 'nullable|image|mimes:jpg,jpeg,png|max:2048',
|
||||
]);
|
||||
// Combine month and day if both are provided (e.g., "12-25")
|
||||
if (!empty($request->birth_month) && !empty($request->birth_day)) {
|
||||
$validated['dob'] = $request->birth_month . '-' . $request->birth_day;
|
||||
} else {
|
||||
$validated['dob'] = null;
|
||||
}
|
||||
|
||||
// Clean up temporary keys
|
||||
unset($validated['birth_month'], $validated['birth_day'], $validated['photo']);
|
||||
|
||||
if ($request->hasFile('photo')) {
|
||||
if ($staff->profile_pic && Storage::disk('public')->exists($staff->profile_pic)) {
|
||||
Storage::disk('public')->delete($staff->profile_pic);
|
||||
}
|
||||
$validated['profile_pic'] = $request->file('photo')->store('staff_profiles', 'public');
|
||||
}
|
||||
|
||||
unset($validated['photo']);
|
||||
$staff->update($validated);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Profile updated successfully!']);
|
||||
}
|
||||
|
||||
// --- DEPENDENTS / EMERGENCY CONTACTS CRUD ---
|
||||
|
||||
public function storeDependent(Request $request)
|
||||
{
|
||||
$staff = StaffMember::where('email', auth()->user()->email)->firstOrFail();
|
||||
|
||||
$validated = $request->validate([
|
||||
'fullname' => 'required|string|max:255',
|
||||
'relationship' => 'required|string|max:192',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'medical_details' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$validated['staff_id'] = $staff->id;
|
||||
|
||||
StaffDependent::create($validated);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Emergency contact added successfully!']);
|
||||
}
|
||||
|
||||
public function updateDependent(Request $request, $id)
|
||||
{
|
||||
$staff = StaffMember::where('email', auth()->user()->email)->firstOrFail();
|
||||
$dependent = StaffDependent::where('staff_id', $staff->id)->findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'fullname' => 'required|string|max:255',
|
||||
'relationship' => 'required|string|max:192',
|
||||
'phone' => 'nullable|string|max:20',
|
||||
'medical_details' => 'nullable|string',
|
||||
]);
|
||||
|
||||
$dependent->update($validated);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Emergency contact updated successfully!']);
|
||||
}
|
||||
|
||||
public function destroyDependent($id)
|
||||
{
|
||||
$staff = StaffMember::where('email', auth()->user()->email)->firstOrFail();
|
||||
$dependent = StaffDependent::where('staff_id', $staff->id)->findOrFail($id);
|
||||
|
||||
$dependent->delete();
|
||||
|
||||
return redirect()->back()->with('success', 'Emergency contact removed.');
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class StaffController extends Controller
|
||||
{
|
||||
public function index()
|
||||
{
|
||||
$data = [
|
||||
'page_title' => 'Dashboard'
|
||||
];
|
||||
return view('staff.index', $data);
|
||||
}
|
||||
}
|
||||
151
app/Http/Controllers/StaffMembersController.php
Normal file
151
app/Http/Controllers/StaffMembersController.php
Normal file
@@ -0,0 +1,151 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\StaffMember;
|
||||
use App\Models\Department;
|
||||
use App\Models\Country;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Carbon\Carbon;
|
||||
|
||||
|
||||
class StaffMembersController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$query = StaffMember::query();
|
||||
|
||||
// 1. Search Filter
|
||||
if ($request->filled('search')) {
|
||||
$search = $request->search;
|
||||
$query->where(function($q) use ($search) {
|
||||
$q->where('name', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%")
|
||||
->orWhere('designation', 'like', "%{$search}%")
|
||||
->orWhere('phone', 'like', "%{$search}%");
|
||||
});
|
||||
}
|
||||
|
||||
// 2. Department Filter
|
||||
if ($request->filled('department_id')) {
|
||||
$query->where('department_id', $request->department_id);
|
||||
}
|
||||
|
||||
// 3. Status Filter
|
||||
if ($request->filled('status')) {
|
||||
$query->where('status', $request->status);
|
||||
}
|
||||
|
||||
$staffMembers = $query->orderBy('name', 'asc')->paginate(12);
|
||||
$staffMembers->appends($request->query());
|
||||
|
||||
// Fetch dynamic dropdown lists
|
||||
$departments = Department::orderBy('name', 'asc')->get();
|
||||
$countries = Country::orderBy('en_short_name', 'asc')->get();
|
||||
|
||||
$data = [
|
||||
'staffMembers' => $staffMembers,
|
||||
'departments' => $departments,
|
||||
'countries' => $countries,
|
||||
];
|
||||
|
||||
return view('staff.index', $data);
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:200',
|
||||
'designation' => 'required|string|max:191',
|
||||
'email' => 'required|email|max:200',
|
||||
'phone' => 'nullable|string|max:200',
|
||||
'department_id' => 'required|integer',
|
||||
'location_country' => 'nullable|string|max:191',
|
||||
'status' => 'required|string|max:20',
|
||||
'photo' => 'nullable|image|mimes:jpg,jpeg,png|max:2048',
|
||||
]);
|
||||
|
||||
if ($request->hasFile('photo')) {
|
||||
$validated['profile_pic'] = $request->file('photo')->store('staff_profiles', 'public');
|
||||
}
|
||||
|
||||
$validated['created_by'] = auth()->id() ?? 1;
|
||||
$validated['password'] = Hash::make('default_password');
|
||||
$validated['hire_date'] = now();
|
||||
|
||||
StaffMember::create($validated);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Staff member added successfully!']);
|
||||
}
|
||||
|
||||
public function update(Request $request, $id)
|
||||
{
|
||||
$staff = StaffMember::findOrFail($id);
|
||||
|
||||
$validated = $request->validate([
|
||||
'name' => 'required|string|max:200',
|
||||
'designation' => 'required|string|max:191',
|
||||
'email' => 'required|email|max:200',
|
||||
'phone' => 'nullable|string|max:200',
|
||||
'department_id' => 'required|integer',
|
||||
'location_country' => 'nullable|string|max:191',
|
||||
'status' => 'required|string|max:20',
|
||||
'photo' => 'nullable|image|mimes:jpg,jpeg,png|max:2048',
|
||||
]);
|
||||
|
||||
if ($request->hasFile('photo')) {
|
||||
if ($staff->profile_pic && Storage::disk('public')->exists($staff->profile_pic)) {
|
||||
Storage::disk('public')->delete($staff->profile_pic);
|
||||
}
|
||||
$validated['profile_pic'] = $request->file('photo')->store('staff_profiles', 'public');
|
||||
}
|
||||
|
||||
$validated['modified_by'] = auth()->id() ?? 1;
|
||||
|
||||
$staff->update($validated);
|
||||
|
||||
return response()->json(['success' => true, 'message' => 'Staff member updated successfully!']);
|
||||
}
|
||||
|
||||
public function destroy($id)
|
||||
{
|
||||
$staff = StaffMember::findOrFail($id);
|
||||
|
||||
if ($staff->profile_pic && Storage::disk('public')->exists($staff->profile_pic)) {
|
||||
Storage::disk('public')->delete($staff->profile_pic);
|
||||
}
|
||||
|
||||
$staff->delete();
|
||||
|
||||
return redirect()->back()->with('success', 'Staff member removed.');
|
||||
}
|
||||
|
||||
|
||||
public function getUpcomingBirthdays()
|
||||
{
|
||||
$today = Carbon::now();
|
||||
$thirtyDaysFromNow = Carbon::now()->addDays(30);
|
||||
|
||||
$currentMonthDay = $today->format('m-d');
|
||||
$futureMonthDay = $thirtyDaysFromNow->format('m-d');
|
||||
|
||||
if ($currentMonthDay <= $futureMonthDay) {
|
||||
// Simple range check within the same calendar year
|
||||
$upcomingStaff = StaffMember::whereBetween('dob', [$currentMonthDay, $futureMonthDay])
|
||||
->orderBy('dob', 'asc')
|
||||
->get();
|
||||
} else {
|
||||
// Handles the year-end crossover (e.g., looking from December into January)
|
||||
$upcomingStaff = StaffMember::where(function($query) use ($currentMonthDay, $futureMonthDay) {
|
||||
$query->where('dob', '>=', $currentMonthDay)
|
||||
->orWhere('dob', '<=', $futureMonthDay);
|
||||
})
|
||||
->orderBy('dob', 'asc')
|
||||
->get();
|
||||
}
|
||||
|
||||
return $upcomingStaff;
|
||||
}
|
||||
}
|
||||
24
app/Models/LeaveRequest.php
Normal file
24
app/Models/LeaveRequest.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class LeaveRequest extends Model
|
||||
{
|
||||
protected $casts = [
|
||||
'start_date' => 'date',
|
||||
'end_date' => 'date',
|
||||
];
|
||||
protected $guarded = ['id'];
|
||||
public function staff()
|
||||
{
|
||||
return $this->belongsTo(StaffMember::class, 'staff_member_id');
|
||||
}
|
||||
|
||||
// In StaffMember.php
|
||||
public function leaveRequests()
|
||||
{
|
||||
return $this->hasMany(LeaveRequest::class, 'staff_member_id');
|
||||
}
|
||||
}
|
||||
16
app/Models/StaffDependent.php
Normal file
16
app/Models/StaffDependent.php
Normal file
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class StaffDependent extends Model
|
||||
{
|
||||
protected $table = 'staff_dependents';
|
||||
protected $guarded = [];
|
||||
|
||||
public function staff()
|
||||
{
|
||||
return $this->belongsTo(StaffMember::class, 'staff_id');
|
||||
}
|
||||
}
|
||||
@@ -21,9 +21,16 @@ class StaffMember extends Authenticatable
|
||||
'password',
|
||||
];
|
||||
|
||||
public function leaveRequests() {
|
||||
return $this->hasMany(LeaveRequest::class, 'staff_member_id');
|
||||
}
|
||||
public function dependents() {
|
||||
return $this->hasMany(StaffDependent::class, 'staff_id');
|
||||
}
|
||||
|
||||
// 4. Cast dates correctly
|
||||
protected $casts = [
|
||||
'dob' => 'date',
|
||||
// 'dob' => 'date',
|
||||
'hire_date' => 'datetime',
|
||||
'password' => 'hashed', // Laravel 10+ password casting
|
||||
];
|
||||
|
||||
Reference in New Issue
Block a user