fix(mobile): resolve user avatars, chat alignment, timestamp formatting, and payslips API

This commit is contained in:
2026-08-10 17:55:14 +08:00
parent 6fe1fe125e
commit 9597cbf99a
1468 changed files with 172818 additions and 65664 deletions

View File

@@ -252,8 +252,8 @@ class AttendanceRecordController extends Controller
// Get employee with shift and policy
$employee = Employee::where('user_id', $validated['employee_id'])->first();
if (!$employee || (!$employee->shift_id && empty($validated['shift_id']))) {
return redirect()->back()->with('error', __('Cannot process attendance: Employee has no shift assigned.'));
if (!$employee) {
return redirect()->back()->with('error', __('Employee profile not found.'));
}
// Working days check removed here to allow admins/HR to create
@@ -304,16 +304,12 @@ class AttendanceRecordController extends Controller
return redirect()->back()->with('error', __('Attendance record already exists for this employee and date.'));
}
// Use employee's assigned shift and policy, or get defaults
// Use employee's assigned shift or first active company shift
$shift = $employee && $employee->shift_id ?
Shift::find($employee->shift_id) :
Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
if (! $shift) {
return redirect()->back()->with('error', __('Active shift not found for employee. Please configure this in employee settings.'));
}
$validated['shift_id'] = $shift->id;
$validated['shift_id'] = $shift ? $shift->id : null;
$validated['created_by'] = creatorId();
$validated['is_holiday'] = $validated['is_holiday'] ?? ($validated['status'] === 'holiday');
$validated['is_rest_day'] = $validated['is_rest_day'] ?? ($validated['status'] === 'rest_day');
@@ -381,24 +377,21 @@ class AttendanceRecordController extends Controller
}
}
// Get employee with shift
// Get employee
$employee = \App\Models\Employee::where('user_id', $validated['employee_id'])->first();
if (!$employee || (!$employee->shift_id && !$attendanceRecord->shift_id)) {
return redirect()->back()->with('error', __('Cannot process attendance: Employee has no shift assigned.'));
if (!$employee) {
return redirect()->back()->with('error', __('Employee profile not found.'));
}
// Use employee's assigned shift
// PRESERVE the existing record shift_id (e.g. synced from legacy) — only fallback to employee default
// Preserve record shift_id, or fallback to employee shift_id, or company active shift
$shift = $attendanceRecord->shift_id
? Shift::find($attendanceRecord->shift_id)
: Shift::find($employee->shift_id);
: ($employee->shift_id
? Shift::find($employee->shift_id)
: Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first());
if (! $shift) {
return redirect()->back()->with('error', __('Active shift not found for employee. Please configure this in employee settings.'));
}
$validated['shift_id'] = $shift->id;
$validated['shift_id'] = $shift ? $shift->id : null;
// Handle balance deduction/restoration if status changed
if ($validated['status'] === 'on_leave' && ($attendanceRecord->status !== 'on_leave' || $attendanceRecord->leave_type_id != $validated['leave_type_id'])) {
@@ -580,19 +573,17 @@ class AttendanceRecordController extends Controller
return redirect()->back()->with('error', __('Employee profile not found.'));
}
// Use employee's assigned shift and policy, or get defaults
// Use employee's assigned shift or first active company shift
$shift = $employee->shift_id ?
Shift::find($employee->shift_id) :
Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
if (! $shift) {
return redirect()->back()->with('error', __('No active shift found. Please contact HR.'));
}
$shiftId = $shift ? $shift->id : null;
if ($existingRecord) {
$existingRecord->update([
'clock_in' => $now->format('H:i:s'),
'shift_id' => $shift->id,
'shift_id' => $shiftId,
'status' => 'present',
'clock_in_latitude' => $validated['latitude'] ?? null,
'clock_in_longitude' => $validated['longitude'] ?? null,
@@ -604,7 +595,7 @@ class AttendanceRecordController extends Controller
'employee_id' => $validated['employee_id'],
'date' => $today,
'clock_in' => $now->format('H:i:s'),
'shift_id' => $shift->id,
'shift_id' => $shiftId,
'is_weekend' => $today->isWeekend(),
'status' => 'present',
'created_by' => creatorId(),
@@ -631,6 +622,82 @@ class AttendanceRecordController extends Controller
}
}
public function breakIn(Request $request)
{
if (Auth::user()->can('clock-in-out') || Auth::user()->type === 'employee') {
try {
$validated = $request->validate([
'employee_id' => 'required|exists:users,id',
]);
$today = Carbon::today();
$now = Carbon::now('Asia/Manila');
$record = AttendanceRecord::where('employee_id', $validated['employee_id'])
->where('date', $today)
->first();
if (!$record || !$record->clock_in) {
return redirect()->back()->with('error', __('Must clock in before starting break.'));
}
if ($record->break_in) {
return redirect()->back()->with('error', __('Already started break today.'));
}
$record->update([
'break_in' => $now->format('H:i:s'),
]);
return redirect()->back()->with('success', __('Break started successfully.'));
} catch (\Exception $e) {
\Log::error('Break in failed: '.$e->getMessage());
return redirect()->back()->with('error', __('Failed to start break.'));
}
}
return redirect()->back()->with('error', __('Permission Denied.'));
}
public function breakOut(Request $request)
{
if (Auth::user()->can('clock-in-out') || Auth::user()->type === 'employee') {
try {
$validated = $request->validate([
'employee_id' => 'required|exists:users,id',
]);
$today = Carbon::today();
$now = Carbon::now('Asia/Manila');
$record = AttendanceRecord::where('employee_id', $validated['employee_id'])
->where('date', $today)
->first();
if (!$record || !$record->break_in) {
return redirect()->back()->with('error', __('Must start break before ending break.'));
}
if ($record->break_out) {
return redirect()->back()->with('error', __('Already ended break today.'));
}
$breakInTime = Carbon::parse($record->break_in);
$breakHours = round($now->diffInMinutes($breakInTime) / 60, 2);
$record->update([
'break_out' => $now->format('H:i:s'),
'break_hours' => $breakHours,
]);
return redirect()->back()->with('success', __('Break ended successfully.'));
} catch (\Exception $e) {
\Log::error('Break out failed: '.$e->getMessage());
return redirect()->back()->with('error', __('Failed to end break.'));
}
}
return redirect()->back()->with('error', __('Permission Denied.'));
}
public function clockOut(Request $request)
{
// Allow employees with the permission OR any employee type user
@@ -905,15 +972,10 @@ class AttendanceRecordController extends Controller
Shift::find($employeeModel->shift_id) :
Shift::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->first();
if (! $shift) {
$skipped++;
continue;
}
$record = AttendanceRecord::create([
'employee_id' => $employee->id,
'date' => $row['date'],
'shift_id' => $shift->id,
'shift_id' => $shift ? $shift->id : null,
'clock_in' => $row['clock_in'] ?? null,
'clock_out' => $row['clock_out'] ?? null,
'created_by' => creatorId(),
@@ -954,9 +1016,10 @@ class AttendanceRecordController extends Controller
$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'),
'date' => $d->format('Y-m-d'),
'day' => $d->format('d'),
'day_name' => $d->format('D'),
'day_name_full' => strtolower($d->format('l')),
];
}
@@ -970,6 +1033,8 @@ class AttendanceRecordController extends Controller
'attendance_records.date',
'attendance_records.clock_in',
'attendance_records.clock_out',
'attendance_records.break_in',
'attendance_records.break_out',
'attendance_records.notes',
'attendance_records.status',
'attendance_records.is_rest_day',
@@ -1024,17 +1089,34 @@ class AttendanceRecordController extends Controller
$users = $usersRaw->map(function($u) use ($dates, $dailyRecords, $approvedLeaves, $approvedOvertimes, $workingDaysIndices, $holidays) {
$grid = [];
$userRecords = $dailyRecords->has($u->id) ? $dailyRecords[$u->id]->keyBy('date') : collect();
$userRecords = $dailyRecords->has($u->id)
? $dailyRecords[$u->id]->keyBy(function($item) {
return \Carbon\Carbon::parse($item->date)->format('Y-m-d');
})
: collect();
$empRestDays = $employeeModel && !empty($employeeModel->rest_days) ? $employeeModel->rest_days : null;
$joiningDate = $employeeModel && $employeeModel->date_of_joining ? \Carbon\Carbon::parse($employeeModel->date_of_joining)->startOfDay() : null;
foreach ($dates as $d) {
$dateKey = $d['date'];
$dateObj = \Carbon\Carbon::parse($dateKey);
$dateObj = \Carbon\Carbon::parse($dateKey)->startOfDay();
$dateIndex = $dateObj->dayOfWeek;
$isRestDay = !in_array($dateIndex, $workingDaysIndices);
$dayNameLower = strtolower($d['day_name_full'] ?? $dateObj->format('l'));
if ($empRestDays) {
$isRestDay = in_array($dayNameLower, $empRestDays);
} else {
$isRestDay = !in_array($dateIndex, $workingDaysIndices);
}
$isPast = $dateObj->isBefore(\Carbon\Carbon::today());
$isBeforeJoining = $joiningDate && $dateObj->lt($joiningDate);
if ($isRestDay) {
if ($isBeforeJoining) {
$status = '--';
$label = 'Not Joined Yet';
} else if ($isRestDay) {
$status = 'RD';
$label = 'Rest Day';
} else if ($isPast) {
@@ -1052,6 +1134,8 @@ class AttendanceRecordController extends Controller
'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,
'break_in' => $record->break_in ? \Carbon\Carbon::parse($record->break_in)->format('H:i') : null,
'break_out' => $record->break_out ? \Carbon\Carbon::parse($record->break_out)->format('H:i') : null,
'notes' => $record->notes,
'status' => $record->is_rest_day ? 'rest_day' : $record->status,
'shift_name' => $record->shift_name ?? null,
@@ -1116,9 +1200,10 @@ class AttendanceRecordController extends Controller
$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'),
'date' => $date->format('Y-m-d'),
'day' => $date->format('d'),
'day_name' => $date->format('D'),
'day_name_full' => strtolower($date->format('l')),
];
}
@@ -1180,6 +1265,8 @@ class AttendanceRecordController extends Controller
'attendance_records.date',
'attendance_records.clock_in',
'attendance_records.clock_out',
'attendance_records.break_in',
'attendance_records.break_out',
'attendance_records.notes',
'attendance_records.status',
'attendance_records.is_rest_day',
@@ -1233,24 +1320,38 @@ class AttendanceRecordController extends Controller
$users = $usersRaw->map(function($user) use ($dates, $dailyRecords, $approvedLeaves, $approvedOvertimes, $workingDaysIndices, $holidays) {
$grid = [];
$userRecords = $dailyRecords->has($user->id) ? $dailyRecords[$user->id]->keyBy('date') : collect();
$userRecords = $dailyRecords->has($user->id)
? $dailyRecords[$user->id]->keyBy(function($item) {
return \Carbon\Carbon::parse($item->date)->format('Y-m-d');
})
: collect();
$empRestDays = $user->employee && !empty($user->employee->rest_days) ? $user->employee->rest_days : null;
$joiningDate = $user->employee && $user->employee->date_of_joining ? \Carbon\Carbon::parse($user->employee->date_of_joining)->startOfDay() : null;
foreach ($dates as $d) {
$dateKey = $d['date'];
// Default matrix values based on time mechanics and global settings
$dateObj = \Carbon\Carbon::parse($dateKey);
$dateObj = \Carbon\Carbon::parse($dateKey)->startOfDay();
$isPast = $dateObj->isBefore(\Carbon\Carbon::today());
$isBeforeJoining = $joiningDate && $dateObj->lt($joiningDate);
$dateIndex = $dateObj->dayOfWeek;
$dayNameLower = strtolower($d['day_name_full'] ?? $dateObj->format('l'));
$isWeekend = ($d['day_name'] === 'Sat' || $d['day_name'] === 'Sun');
$isRestDay = !in_array($dateIndex, $workingDaysIndices);
if ($isRestDay) {
if ($empRestDays) {
$isRestDay = in_array($dayNameLower, $empRestDays);
} else {
$isRestDay = !in_array($dateIndex, $workingDaysIndices);
}
if ($isBeforeJoining) {
$status = '--';
$label = 'Not Joined Yet';
} else if ($isRestDay) {
$status = 'RD';
$label = 'Rest Day';
} else if ($isWeekend) {
$status = 'W';
$label = 'Weekend';
} else {
if ($isPast) {
$status = 'A';
@@ -1285,6 +1386,8 @@ class AttendanceRecordController extends Controller
'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,
'break_in' => $record->break_in ? \Carbon\Carbon::parse($record->break_in)->format('H:i') : null,
'break_out' => $record->break_out ? \Carbon\Carbon::parse($record->break_out)->format('H:i') : null,
'notes' => $record->notes,
'status' => $record->is_rest_day ? 'rest_day' : $record->status,
'shift_name' => $record->shift_name ?? null,