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 = \App\Models\AttendanceRecord::withoutGlobalScopes() ->with('shift') ->whereIn('employee_id', $usersRaw->pluck('id')) ->whereBetween('date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')]) ->get() ->groupBy(function($item) { return (int) $item->employee_id; }); $users = $usersRaw->map(function($user) use ($dates, $dailyShifts) { $grid = []; $userId = (int) $user->id; $userShifts = $dailyShifts->has($userId) ? $dailyShifts[$userId]->keyBy(function($item) { // Force the date to the beginning of the day in Y-m-d format for exact mapping return \Carbon\Carbon::parse($item->date)->startOfDay()->format('Y-m-d'); }) : 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 ($record->is_rest_day) { $status = 'RD'; $shiftName = 'Rest Day'; $shiftId = null; } else if ($record->shift) { // Map shift standard letters based on name $name = $record->shift->name; if (stripos($name, 'Morning') !== false) $status = 'M'; else if (stripos($name, 'Evening') !== false) $status = 'E'; else if (stripos($name, 'Night') !== false) $status = 'N'; else $status = strtoupper(substr($name, 0, 1)); $shiftName = $name; $shiftId = $record->shift->id; $startTime = $record->shift->start_time; $endTime = $record->shift->end_time; } else if ($user->employee && $user->employee->shift) { $name = $user->employee->shift->name; if (stripos($name, 'Morning') !== false) $status = 'M'; else if (stripos($name, 'Evening') !== false) $status = 'E'; else if (stripos($name, 'Night') !== false) $status = 'N'; else $status = strtoupper(substr($name, 0, 1)); $shiftName = $name; $shiftId = $user->employee->shift->id; $startTime = $user->employee->shift->start_time; $endTime = $user->employee->shift->end_time; } } else if ($user->employee && $user->employee->shift) { $name = $user->employee->shift->name; if (stripos($name, 'Morning') !== false) $status = 'M'; else if (stripos($name, 'Evening') !== false) $status = 'E'; else if (stripos($name, 'Night') !== false) $status = 'N'; else $status = strtoupper(substr($name, 0, 1)); $shiftName = $name; $shiftId = $user->employee->shift->id; $startTime = $user->employee->shift->start_time; $endTime = $user->employee->shift->end_time; } $grid[$dateKey] = [ 'status' => $status, 'shift_name' => $shiftName, 'shift_id' => $shiftId ?? '', 'start_time' => $startTime ?? null, 'end_time' => $endTime ?? null ]; } 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); $companyId = getCompanyId(creatorId()); foreach ($period as $date) { $isRestDay = $validated['is_rest_day'] ?? false; $targetShiftId = $isRestDay ? null : ($validated['shift_id'] ?? null); $record = \App\Models\AttendanceRecord::updateOrCreate( [ 'employee_id' => $validated['employee_id'], 'date' => $date->format('Y-m-d'), ], [ 'shift_id' => $targetShiftId, 'is_rest_day' => $isRestDay, 'status' => $isRestDay ? 'absent' : 'present', // Default status for scheduled shift 'attendance_policy_id' => ($validated['attendance_policy_id'] ?? null), 'created_by' => $companyId, // Ensure it's owned by the company for visibility ] ); \Illuminate\Support\Facades\Log::info("Shift Record Updated", [ 'id' => $record->id, 'is_dirty' => $record->wasRecentlyCreated || $record->wasChanged(), 'data' => $record->toArray() ]); } return redirect()->back()->with('success', __('Shifts assigned successfully.')); } return redirect()->back()->with('error', __('Permission Denied.')); } }