validate([ 'date' => 'required|date', 'user_id' => 'nullable|exists:users,id' ]); $userId = $request->user_id ?: Auth::id(); // If not admin/hr, cannot query other users if ($request->user_id && $request->user_id != Auth::id()) { if (!in_array(Auth::user()->type, ['admin', 'hr', 'company'])) { abort(403); } } $record = AttendanceRecord::with('employee.user', 'employee.shift') ->whereHas('employee', function($q) use ($userId) { $q->where('user_id', $userId); }) ->where('date', $request->date) ->first(); if (!$record) { return response()->json(['message' => 'No attendance record found for this date.']); } if (!$record->clock_in || !$record->clock_out) { return response()->json(['message' => 'Attendance record is incomplete (missing clock in/out).']); } $emp = $record->employee; $shiftStart = $record->shift_start_time ?: ($emp->shift->start_time ?? null); $shiftEnd = $record->shift_end_time ?: ($emp->shift->end_time ?? null); if (!$shiftStart || !$shiftEnd) { return response()->json(['message' => 'No shift assigned for this record.']); } $parseTime = function($timeStr) { if (!$timeStr) return 0; $parts = explode(':', $timeStr); return ($parts[0] * 60) + ($parts[1] ?? 0); }; $outMins = $parseTime($record->clock_out); $sStart = $parseTime($shiftStart); $sEnd = $parseTime($shiftEnd); // Night shift logic if ($sEnd <= $sStart) { $sEnd += 1440; if ($outMins < $sStart) $outMins += 1440; } $overtimeMins = $outMins - $sEnd; if ($overtimeMins >= 60) { $otHours = round($overtimeMins / 60, 1); return response()->json([ 'has_overtime' => true, 'suggested_hours' => $otHours, 'message' => "Shift ended at {$shiftEnd}, clocked out at {$record->clock_out}. Auto-calculated: {$otHours} hours." ]); } return response()->json([ 'has_overtime' => false, 'message' => "Shift ended at {$shiftEnd}, clocked out at {$record->clock_out}. No significant overtime detected." ]); } public function index() { $user = Auth::user(); $query = OvertimeApplication::with('user.employee'); // If not admin/hr, only show own requests if (!in_array($user->type, ['admin', 'hr', 'company'])) { $query->where('user_id', $user->id); } $applications = $query->orderBy('date', 'desc')->get(); $users = []; if (in_array($user->type, ['admin', 'hr', 'company'])) { $users = \App\Models\User::where('type', 'employee')->select('id', 'name')->get(); } return Inertia::render('hr/overtime/index', [ 'applications' => $applications, 'can_approve' => in_array($user->type, ['admin', 'hr', 'company']), 'users' => $users ]); } public function store(Request $request) { $request->validate([ 'date' => 'required|date', 'requested_hours' => 'required|numeric|min:0.1', 'reason' => 'nullable|string', 'user_id' => 'nullable|exists:users,id' ]); $userId = Auth::id(); // If an admin/hr specifies a user_id, use it. if ($request->has('user_id') && $request->user_id != Auth::id()) { if (in_array(Auth::user()->type, ['admin', 'hr', 'company'])) { $userId = $request->user_id; } else { abort(403, 'Unauthorized to apply overtime for another employee.'); } } // Check if already requested for this date $exists = OvertimeApplication::where('user_id', $userId) ->where('date', $request->date) ->whereIn('status', ['pending', 'approved']) ->exists(); if ($exists) { if ($request->wantsJson()) { return response()->json(['message' => 'You already have a pending or approved overtime request for this date.'], 422); } return back()->with('error', 'You already have a pending or approved overtime request for this date.'); } OvertimeApplication::create([ 'user_id' => $userId, 'date' => $request->date, 'requested_hours' => $request->requested_hours, 'reason' => $request->reason, 'status' => 'pending' ]); if ($request->wantsJson()) { return response()->json(['message' => 'Overtime application submitted successfully.']); } return back()->with('success', 'Overtime application submitted successfully.'); } public function updateStatus(Request $request, OvertimeApplication $overtimeApplication) { if (!in_array(Auth::user()->type, ['admin', 'hr', 'company'])) { abort(403); } $request->validate([ 'status' => 'required|in:approved,rejected', 'approved_hours' => 'nullable|required_if:status,approved|numeric|min:0', 'admin_remarks' => 'nullable|string' ]); $overtimeApplication->update([ 'status' => $request->status, 'approved_hours' => $request->status === 'approved' ? $request->approved_hours : null, 'admin_remarks' => $request->admin_remarks ]); return back()->with('success', 'Overtime application ' . $request->status . ' successfully.'); } }