feat(attendance): add GPS summary dashboard card, punch source filter, and per-card GPS Google Maps indicators
This commit is contained in:
@@ -119,6 +119,26 @@ class AttendanceRecordController extends Controller
|
||||
$query->where('date', '<=', $request->date_to);
|
||||
}
|
||||
|
||||
// Handle punch source / GPS filter
|
||||
if ($request->has('source') && ! empty($request->source) && $request->source !== 'all') {
|
||||
if ($request->source === 'gps') {
|
||||
$query->where(function ($q) {
|
||||
$q->whereNotNull('clock_in_latitude')
|
||||
->orWhereNotNull('clock_out_latitude');
|
||||
});
|
||||
} elseif ($request->source === 'zkteco') {
|
||||
$query->where(function ($q) {
|
||||
$q->where('clock_in_source', 'zkteco')
|
||||
->orWhere('clock_out_source', 'zkteco');
|
||||
});
|
||||
} else {
|
||||
$query->where(function ($q) use ($request) {
|
||||
$q->where('clock_in_source', $request->source)
|
||||
->orWhere('clock_out_source', $request->source);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Handle sorting
|
||||
if ($request->has('sort_field') && ! empty($request->sort_field)) {
|
||||
$sortField = $request->sort_field;
|
||||
@@ -138,9 +158,11 @@ class AttendanceRecordController extends Controller
|
||||
$attendanceRecords->getCollection()->transform(function ($record) {
|
||||
if ($record->employee) {
|
||||
$rawAvatar = $record->employee->getRawOriginal('avatar');
|
||||
$record->employee->avatar = check_file($rawAvatar)
|
||||
? get_file($rawAvatar)
|
||||
: asset('storage/avatars/avatar.png');
|
||||
$avatarUrl = null;
|
||||
if ($rawAvatar) {
|
||||
$avatarUrl = getImagePath($rawAvatar);
|
||||
}
|
||||
$record->employee->avatar_url = $avatarUrl;
|
||||
}
|
||||
|
||||
// Add leave type information for on_leave records
|
||||
@@ -169,12 +191,18 @@ class AttendanceRecordController extends Controller
|
||||
$statsRecords = AttendanceRecord::whereIn('created_by', $companyUserIds)->get();
|
||||
|
||||
$todayStats = [
|
||||
'present' => $statsRecords->where('status', 'present')->count(),
|
||||
'on_leave' => LeaveApplication::whereIn('employee_id', function ($q) use ($companyUserIds) {
|
||||
$q->select('user_id')->from('employees')->whereIn('created_by', $companyUserIds);
|
||||
})->where('status', 'approved')->count(),
|
||||
'late_arrivals' => $statsRecords->where('is_late', true)->count(),
|
||||
'overtime' => $statsRecords->where('overtime_hours', '>', 0)->count(),
|
||||
'present' => $statsRecords->where('status', 'present')->count(),
|
||||
'on_leave' => LeaveApplication::whereIn('employee_id', function ($q) use ($companyUserIds) {
|
||||
$q->select('user_id')->from('employees')->whereIn('created_by', $companyUserIds);
|
||||
})->where('status', 'approved')->count(),
|
||||
'late_arrivals' => $statsRecords->where('is_late', true)->count(),
|
||||
'overtime' => $statsRecords->where('overtime_hours', '>', 0)->count(),
|
||||
'gps_clock_ins' => $statsRecords->filter(function ($r) {
|
||||
return !empty($r->clock_in_latitude) || !empty($r->clock_out_latitude);
|
||||
})->count(),
|
||||
'biometric_clock_ins' => $statsRecords->filter(function ($r) {
|
||||
return $r->clock_in_source === 'zkteco' || $r->clock_out_source === 'zkteco';
|
||||
})->count(),
|
||||
];
|
||||
} else {
|
||||
$today = Carbon::today();
|
||||
@@ -184,12 +212,18 @@ class AttendanceRecordController extends Controller
|
||||
->get();
|
||||
|
||||
$todayStats = [
|
||||
'present' => $todayRecords->where('status', 'present')->count(),
|
||||
'on_leave' => LeaveApplication::whereIn('employee_id', function ($q) use ($companyUserIds) {
|
||||
$q->select('user_id')->from('employees')->whereIn('created_by', $companyUserIds);
|
||||
})->where('status', 'approved')->whereDate('start_date', '<=', $today)->whereDate('end_date', '>=', $today)->count(),
|
||||
'late_arrivals' => $todayRecords->where('is_late', true)->count(),
|
||||
'overtime' => $todayRecords->where('overtime_hours', '>', 0)->count(),
|
||||
'present' => $todayRecords->where('status', 'present')->count(),
|
||||
'on_leave' => LeaveApplication::whereIn('employee_id', function ($q) use ($companyUserIds) {
|
||||
$q->select('user_id')->from('employees')->whereIn('created_by', $companyUserIds);
|
||||
})->where('status', 'approved')->whereDate('start_date', '<=', $today)->whereDate('end_date', '>=', $today)->count(),
|
||||
'late_arrivals' => $todayRecords->where('is_late', true)->count(),
|
||||
'overtime' => $todayRecords->where('overtime_hours', '>', 0)->count(),
|
||||
'gps_clock_ins' => $todayRecords->filter(function ($r) {
|
||||
return !empty($r->clock_in_latitude) || !empty($r->clock_out_latitude);
|
||||
})->count(),
|
||||
'biometric_clock_ins' => $todayRecords->filter(function ($r) {
|
||||
return $r->clock_in_source === 'zkteco' || $r->clock_out_source === 'zkteco';
|
||||
})->count(),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -204,7 +238,7 @@ class AttendanceRecordController extends Controller
|
||||
'employees' => $this->getFilteredEmployees(),
|
||||
'branches' => $branches,
|
||||
'hasSampleFile' => file_exists(storage_path('uploads/sample/sample-attendance-record.xlsx')),
|
||||
'filters' => $request->all(['search', 'employee_id', 'branch_id', 'status', 'date_from', 'date_to', 'sort_field', 'sort_direction', 'per_page']),
|
||||
'filters' => $request->all(['search', 'employee_id', 'branch_id', 'status', 'source', 'date_from', 'date_to', 'sort_field', 'sort_direction', 'per_page']),
|
||||
'todayStats' => $todayStats,
|
||||
'leaveTypes' => \App\Models\LeaveType::whereIn('created_by', getCompanyAndUsersId())->where('status', 'active')->get(['id', 'name']),
|
||||
]);
|
||||
|
||||
Reference in New Issue
Block a user