can('manage-shifts')) { $query = Shift::with(['creator'])->where(function ($q) { if (Auth::user()->can('manage-any-shifts')) { $q->whereIn('created_by', getCompanyAndUsersId()); } elseif (Auth::user()->can('manage-own-shifts')) { $q->where('created_by', Auth::id()); } else { $q->whereRaw('1 = 0'); } }); // Handle search if ($request->has('search') && !empty($request->search)) { $query->where(function ($q) use ($request) { $q->where('name', 'like', '%' . $request->search . '%') ->orWhere('description', 'like', '%' . $request->search . '%'); }); } // Handle status filter if ($request->has('status') && !empty($request->status) && $request->status !== 'all') { $query->where('status', $request->status); } // Handle shift type filter if ($request->has('shift_type') && !empty($request->shift_type) && $request->shift_type !== 'all') { if ($request->shift_type === 'night') { $query->where('is_night_shift', true); } else { $query->where('is_night_shift', false); } } // Handle sorting if ($request->has('sort_field') && !empty($request->sort_field)) { $sortField = $request->sort_field; $sortDirection = $request->sort_direction ?? 'asc'; if ($sortField === 'name') { $query->orderBy('name', $sortDirection); } else { $query->orderBy('created_at', 'desc'); } } else { $query->orderBy('created_at', 'desc'); } $shifts = $query->paginate($request->per_page ?? 9); // Stats always calculated from ALL records — never affected by filters or pagination $allShifts = Shift::where(function ($q) { if (Auth::user()->can('manage-any-shifts')) { $q->whereIn('created_by', getCompanyAndUsersId()); } elseif (Auth::user()->can('manage-own-shifts')) { $q->where('created_by', Auth::id()); } else { $q->whereRaw('1 = 0'); } }); $stats = [ 'total' => (clone $allShifts)->count(), 'active' => (clone $allShifts)->where('status', 'active')->count(), 'night' => (clone $allShifts)->where('is_night_shift', true)->count(), 'day' => (clone $allShifts)->where('is_night_shift', false)->count(), ]; return Inertia::render('hr/shifts/index', [ 'shifts' => $shifts, 'stats' => $stats, 'filters' => $request->all(['search', 'status', 'shift_type', 'sort_field', 'sort_direction', 'per_page']), ]); } else { return redirect()->back()->with('error', __('Permission Denied.')); } } public function store(Request $request) { $validated = $request->validate([ 'name' => 'required|string|max:255', 'description' => 'nullable|string', 'start_time' => 'required|date_format:H:i', 'end_time' => 'required|date_format:H:i', 'break_duration' => 'required|integer|min:0', 'break_start_time' => 'nullable|date_format:H:i', 'break_end_time' => 'nullable|date_format:H:i', 'grace_period' => 'required|integer|min:0', 'is_night_shift' => 'boolean', 'status' => 'nullable|in:active,inactive', ]); $validated['created_by'] = creatorId(); $validated['status'] = $validated['status'] ?? 'active'; $validated['is_night_shift'] = $validated['is_night_shift'] ?? false; // Check if shift with same name already exists $exists = Shift::where('name', $validated['name']) ->whereIn('created_by', getCompanyAndUsersId()) ->exists(); if ($exists) { return redirect()->back()->with('error', __('Shift with this name already exists.')); } Shift::create($validated); return redirect()->back()->with('success', __('Shift created successfully.')); } public function update(Request $request, $shiftId) { $shift = Shift::where('id', $shiftId) ->whereIn('created_by', getCompanyAndUsersId()) ->first(); if ($shift) { try { $validated = $request->validate([ 'name' => 'required|string|max:255', 'description' => 'nullable|string', 'start_time' => 'required|date_format:H:i', 'end_time' => 'required|date_format:H:i', 'break_duration' => 'required|integer|min:0', 'break_start_time' => 'nullable|date_format:H:i', 'break_end_time' => 'nullable|date_format:H:i', 'grace_period' => 'required|integer|min:0', 'is_night_shift' => 'boolean', 'status' => 'nullable|in:active,inactive', ]); // Check if shift with same name already exists (excluding current) $exists = Shift::where('name', $validated['name']) ->whereIn('created_by', getCompanyAndUsersId()) ->where('id', '!=', $shiftId) ->exists(); if ($exists) { return redirect()->back()->with('error', __('Shift with this name already exists.')); } $shift->update($validated); return redirect()->back()->with('success', __('Shift updated successfully')); } catch (\Exception $e) { return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update shift')); } } else { return redirect()->back()->with('error', __('Shift Not Found.')); } } public function destroy($shiftId) { $shift = Shift::where('id', $shiftId) ->whereIn('created_by', getCompanyAndUsersId()) ->first(); if ($shift) { try { $shift->delete(); return redirect()->back()->with('success', __('Shift deleted successfully')); } catch (\Exception $e) { return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to delete shift')); } } else { return redirect()->back()->with('error', __('Shift Not Found.')); } } public function toggleStatus($shiftId) { $shift = Shift::where('id', $shiftId) ->whereIn('created_by', getCompanyAndUsersId()) ->first(); if ($shift) { try { $shift->status = $shift->status === 'active' ? 'inactive' : 'active'; $shift->save(); return redirect()->back()->with('success', __('Shift status updated successfully')); } catch (\Exception $e) { return redirect()->back()->with('error', $e->getMessage() ?: __('Failed to update shift status')); } } else { return redirect()->back()->with('error', __('Shift Not Found.')); } } public function calendar(Request $request) { if (Auth::user()->can('manage-shifts')) { $month = $request->input('month', date('n')); $year = $request->input('year', date('Y')); $department_id = $request->input('department_id'); $search = $request->input('search'); $startDate = \Carbon\Carbon::createFromDate($year, $month, 1)->startOfMonth(); $endDate = \Carbon\Carbon::createFromDate($year, $month, 1)->endOfMonth(); // Setup dates header correctly (1 -> 31) $dates = []; for ($date = $startDate->copy(); $date->lte($endDate); $date->addDay()) { $dates[] = [ 'date' => $date->format('Y-m-d'), 'day' => $date->format('d'), 'day_name' => $date->format('D'), ]; } $query = \App\Models\User::with(['employee.department', 'employee.shift']) ->whereHas('employee', function($q) { $q->where('employee_status', 'active'); }); if ($department_id) { $query->whereHas('employee', function($q) use ($department_id) { $q->where('department_id', $department_id); }); } if ($search) { $query->where('name', 'like', "%{$search}%"); } $usersRaw = $query->get(); // Fetch daily shifts and REST DAY overrides dynamically directly from the Attendance engine records! $dailyShifts = \Illuminate\Support\Facades\DB::table('attendance_records') ->leftJoin('shifts', 'attendance_records.shift_id', '=', 'shifts.id') ->whereBetween('attendance_records.date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')]) ->select( 'attendance_records.employee_id as user_id', 'attendance_records.date', 'attendance_records.is_rest_day', 'attendance_records.shift_id', 'shifts.name as shift_name' ) ->get() ->groupBy('user_id'); $users = $usersRaw->map(function($user) use ($dates, $dailyShifts) { $grid = []; $userShifts = $dailyShifts->has($user->id) ? $dailyShifts[$user->id]->keyBy('date') : collect(); foreach ($dates as $d) { $dateKey = $d['date']; // Default to Rest Day as per user's Socratic resolution $status = 'RD'; $shiftName = 'Rest Day'; if ($userShifts->has($dateKey)) { $record = $userShifts[$dateKey]; // If attendance record specifically marks it as a globally tracked Rest Day if ($record->is_rest_day) { $status = 'RD'; $shiftName = 'Rest Day'; $shiftId = null; } else if ($record->shift_name) { // Extract shift safely $status = strtoupper(substr($record->shift_name, 0, 1)); $shiftName = $record->shift_name; $shiftId = $record->shift_id; } else if ($user->employee && $user->employee->shift) { // Record exists, but shift mapping is blank. Fallback to employee profile shift. $status = strtoupper(substr($user->employee->shift->name, 0, 1)); $shiftName = $user->employee->shift->name; $shiftId = $user->employee->shift->id; } } else if ($user->employee && $user->employee->shift) { // Completely blank record; safely map to their default assigned Employee Shift! // Removed hardcoded Sat/Sun filters because `attendance_records` strictly dictates actual weekend behavior now! $status = strtoupper(substr($user->employee->shift->name, 0, 1)); $shiftName = $user->employee->shift->name; $shiftId = $user->employee->shift->id; } $grid[$dateKey] = [ 'status' => $status, 'shift_name' => $shiftName, 'shift_id' => $shiftId ?? '' ]; } return [ 'id' => $user->id, 'name' => $user->name, 'employee_code' => $user->employee ? $user->employee->employee_id : '', 'designation' => ($user->employee && $user->employee->designation) ? $user->employee->designation->name : '', 'department' => ($user->employee && $user->employee->department) ? $user->employee->department->name : '', 'shifts' => $grid ]; }); $departments = \App\Models\Department::select('id', 'name')->get(); $allShifts = \App\Models\Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->get(); return Inertia::render('hr/shifts/calendar', [ 'users' => $users, 'dates' => $dates, 'departments' => $departments, 'shifts' => $allShifts, 'filters' => [ 'month' => $month, 'year' => $year, 'department_id' => $department_id, 'search' => $search ] ]); } return redirect()->back()->with('error', __('Permission Denied.')); } public function assignShift(Request $request) { if (Auth::user()->can('manage-shifts')) { $validated = $request->validate([ 'employee_id' => 'required|exists:users,id', 'start_date' => 'required|date', 'end_date' => 'required|date|after_or_equal:start_date', 'shift_id' => 'required_without:is_rest_day|nullable|exists:shifts,id', 'is_rest_day' => 'boolean', ]); $startDate = \Carbon\Carbon::parse($validated['start_date']); $endDate = \Carbon\Carbon::parse($validated['end_date']); $period = \Carbon\CarbonPeriod::create($startDate, $endDate); foreach ($period as $date) { \App\Models\AttendanceRecord::updateOrCreate( [ 'employee_id' => $validated['employee_id'], 'date' => $date->format('Y-m-d'), ], [ 'shift_id' => $validated['is_rest_day'] ? null : $validated['shift_id'], 'is_rest_day' => $validated['is_rest_day'] ?? false, 'status' => $validated['is_rest_day'] ? 'absent' : 'present', // Default status for scheduled shift 'created_by' => creatorId(), ] ); } return redirect()->back()->with('success', __('Shifts assigned successfully.')); } return redirect()->back()->with('error', __('Permission Denied.')); } }