fix: employee self-service - open controller access and clock-in/out
- LeaveApplicationController: add employee branch to bypass can() check - AttendanceRecordController: add employee calendar view (own records only) + open clockIn/clockOut to employee type (no permission required) - PayslipController: add employee branch to show own payslips only - routes/web.php: remove permission:clock-in-out middleware from clock routes - employee-dashboard.tsx: always show clock in/out buttons for employees - Rebuild frontend assets
This commit is contained in:
@@ -22,6 +22,14 @@ class AttendanceRecordController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// Employee self-service: redirect to calendar but pass their own records only
|
||||
if ($user->type === 'employee') {
|
||||
// For employees, show only their own attendance in calendar view
|
||||
return redirect()->route('hr.attendance-records.calendar', $request->all());
|
||||
}
|
||||
|
||||
// Default to calendar view unless explicitly requesting list view
|
||||
// Added check to prevent redirecting if already on a sub-path or if it's not a GET request
|
||||
if ($request->isMethod('GET') && (!$request->has('view') || $request->input('view') !== 'list')) {
|
||||
@@ -460,7 +468,8 @@ class AttendanceRecordController extends Controller
|
||||
|
||||
public function clockIn(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('clock-in-out')) {
|
||||
// Allow employees with the permission OR any employee type user
|
||||
if (Auth::user()->can('clock-in-out') || Auth::user()->type === 'employee') {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
@@ -571,7 +580,8 @@ class AttendanceRecordController extends Controller
|
||||
|
||||
public function clockOut(Request $request)
|
||||
{
|
||||
if (Auth::user()->can('clock-in-out')) {
|
||||
// Allow employees with the permission OR any employee type user
|
||||
if (Auth::user()->can('clock-in-out') || Auth::user()->type === 'employee') {
|
||||
try {
|
||||
$validated = $request->validate([
|
||||
'employee_id' => 'required|exists:users,id',
|
||||
@@ -843,6 +853,157 @@ class AttendanceRecordController extends Controller
|
||||
|
||||
public function calendar(Request $request)
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
// Employee self-service: show only their own calendar
|
||||
if ($user->type === 'employee') {
|
||||
$month = $request->input('month', date('n'));
|
||||
$year = $request->input('year', date('Y'));
|
||||
|
||||
$startDate = \Carbon\Carbon::createFromDate($year, $month, 1)->startOfMonth();
|
||||
$endDate = \Carbon\Carbon::createFromDate($year, $month, 1)->endOfMonth();
|
||||
|
||||
$dates = [];
|
||||
for ($d = $startDate->copy(); $d->lte($endDate); $d->addDay()) {
|
||||
$dates[] = [
|
||||
'date' => $d->format('Y-m-d'),
|
||||
'day' => $d->format('d'),
|
||||
'day_name' => $d->format('D'),
|
||||
];
|
||||
}
|
||||
|
||||
$dailyRecords = \Illuminate\Support\Facades\DB::table('attendance_records')
|
||||
->leftJoin('shifts', 'attendance_records.shift_id', '=', 'shifts.id')
|
||||
->where('attendance_records.employee_id', $user->id)
|
||||
->whereBetween('attendance_records.date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
|
||||
->select(
|
||||
'attendance_records.id',
|
||||
'attendance_records.employee_id as user_id',
|
||||
'attendance_records.date',
|
||||
'attendance_records.clock_in',
|
||||
'attendance_records.clock_out',
|
||||
'attendance_records.notes',
|
||||
'attendance_records.status',
|
||||
'attendance_records.is_rest_day',
|
||||
'attendance_records.is_holiday',
|
||||
'attendance_records.is_late',
|
||||
'attendance_records.is_early_departure',
|
||||
'attendance_records.overtime_hours',
|
||||
'attendance_records.shift_id',
|
||||
'shifts.name as shift_name',
|
||||
'shifts.start_time as shift_start_time',
|
||||
'shifts.end_time as shift_end_time',
|
||||
'shifts.grace_period as shift_grace_period',
|
||||
'shifts.is_night_shift'
|
||||
)
|
||||
->get()
|
||||
->groupBy('user_id');
|
||||
|
||||
$approvedLeaves = LeaveApplication::whereIn('status', ['approved', 'pending'])
|
||||
->where('employee_id', $user->id)
|
||||
->where(function($q) use ($startDate, $endDate) {
|
||||
$q->whereBetween('start_date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
|
||||
->orWhereBetween('end_date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')]);
|
||||
})
|
||||
->get()
|
||||
->groupBy('employee_id');
|
||||
|
||||
$approvedOvertimes = \App\Models\OvertimeApplication::where('status', 'approved')
|
||||
->where('user_id', $user->id)
|
||||
->whereBetween('date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
|
||||
->get()
|
||||
->groupBy('user_id');
|
||||
|
||||
$holidays = \App\Models\Holiday::where(function($q) use ($startDate, $endDate) {
|
||||
$q->whereBetween('start_date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')])
|
||||
->orWhereBetween('end_date', [$startDate->format('Y-m-d'), $endDate->format('Y-m-d')]);
|
||||
})->get();
|
||||
|
||||
$globalSettings = settings();
|
||||
$workingDaysIndices = json_decode($globalSettings['working_days'] ?? '[1,2,3,4,5]', true);
|
||||
|
||||
// Build a single-user array using same mapping logic
|
||||
$employeeModel = \App\Models\Employee::where('user_id', $user->id)->with(['shift', 'designation', 'department'])->first();
|
||||
|
||||
$singleUser = (object)[
|
||||
'id' => $user->id,
|
||||
'name' => $user->name,
|
||||
'employee' => $employeeModel,
|
||||
];
|
||||
|
||||
// Reuse the same calendar logic by passing through the existing pipeline for one user
|
||||
$usersRaw = collect([$singleUser]);
|
||||
|
||||
$users = $usersRaw->map(function($u) use ($dates, $dailyRecords, $approvedLeaves, $approvedOvertimes, $workingDaysIndices, $holidays) {
|
||||
$grid = [];
|
||||
$userRecords = $dailyRecords->has($u->id) ? $dailyRecords[$u->id]->keyBy('date') : collect();
|
||||
|
||||
foreach ($dates as $d) {
|
||||
$dateKey = $d['date'];
|
||||
$dateObj = \Carbon\Carbon::parse($dateKey);
|
||||
$dateIndex = $dateObj->dayOfWeek;
|
||||
$isRestDay = !in_array($dateIndex, $workingDaysIndices);
|
||||
|
||||
$status = $isRestDay ? 'RD' : '--';
|
||||
$label = $isRestDay ? 'Rest Day' : 'Not Marked';
|
||||
$recordDetail = null;
|
||||
|
||||
if ($userRecords->has($dateKey)) {
|
||||
$record = $userRecords[$dateKey];
|
||||
$recordDetail = [
|
||||
'id' => $record->id,
|
||||
'clock_in' => $record->clock_in ? \Carbon\Carbon::parse($record->clock_in)->format('H:i') : null,
|
||||
'clock_out' => $record->clock_out ? \Carbon\Carbon::parse($record->clock_out)->format('H:i') : null,
|
||||
'notes' => $record->notes,
|
||||
'status' => $record->is_rest_day ? 'rest_day' : $record->status,
|
||||
'shift_name' => $record->shift_name ?? null,
|
||||
'shift_start_time' => $record->shift_start_time ?? null,
|
||||
'shift_end_time' => $record->shift_end_time ?? null,
|
||||
];
|
||||
$status = $record->is_late ? 'L' : ($record->clock_in ? 'P' : ($isRestDay ? 'RD' : 'A'));
|
||||
$label = $record->is_late ? 'Late' : ($record->clock_in ? 'Present' : ($isRestDay ? 'Rest Day' : 'Absent'));
|
||||
}
|
||||
|
||||
// Leave overlay
|
||||
if ((!$recordDetail || empty($recordDetail['clock_in'])) && $approvedLeaves->has($u->id)) {
|
||||
foreach ($approvedLeaves[$u->id] as $leave) {
|
||||
if ($dateObj->gte($leave->start_date) && $dateObj->lte($leave->end_date)) {
|
||||
$status = 'LV';
|
||||
$label = 'On Leave (' . ($leave->leaveType->name ?? '') . ')';
|
||||
$recordDetail = ['status' => 'on_leave', 'leave_application_id' => $leave->id];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$grid[$dateKey] = ['status' => $status, 'label' => $label, 'record' => $recordDetail];
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $u->id,
|
||||
'name' => $u->name,
|
||||
'employee_code' => $u->employee->employee_code ?? '',
|
||||
'designation' => substr($u->employee->designation->name ?? 'Staff', 0, 15),
|
||||
'department' => $u->employee->department->name ?? 'General',
|
||||
'shift' => $u->employee && $u->employee->shift ? [
|
||||
'name' => $u->employee->shift->name,
|
||||
'start_time' => $u->employee->shift->start_time,
|
||||
'end_time' => $u->employee->shift->end_time,
|
||||
] : null,
|
||||
'records' => $grid,
|
||||
];
|
||||
});
|
||||
|
||||
return Inertia::render('hr/attendance-records/calendar', [
|
||||
'users' => $users,
|
||||
'dates' => $dates,
|
||||
'departments'=> [],
|
||||
'branches' => [],
|
||||
'leaveTypes' => \App\Models\LeaveType::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->get(['id', 'name']),
|
||||
'filters' => $request->all(['month', 'year']),
|
||||
]);
|
||||
}
|
||||
|
||||
if (Auth::user()->can('manage-attendance-records')) {
|
||||
$month = $request->input('month', date('n'));
|
||||
$year = $request->input('year', date('Y'));
|
||||
|
||||
Reference in New Issue
Block a user