233 lines
8.5 KiB
PHP
233 lines
8.5 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\AttendanceRecord;
|
|
use App\Services\AttendanceService;
|
|
use App\Services\EmployeeService;
|
|
use Illuminate\Http\Request;
|
|
use Exception;
|
|
|
|
class AttendanceApiController extends Controller
|
|
{
|
|
protected EmployeeService $employeeService;
|
|
protected AttendanceService $attendanceService;
|
|
|
|
public function __construct(EmployeeService $employeeService, AttendanceService $attendanceService)
|
|
{
|
|
$this->employeeService = $employeeService;
|
|
$this->attendanceService = $attendanceService;
|
|
}
|
|
|
|
public function today(Request $request)
|
|
{
|
|
try {
|
|
$employee = $this->employeeService->resolveEmployee($request->user());
|
|
} catch (Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'data' => null,
|
|
'message' => 'Employee profile not found',
|
|
'errors' => null,
|
|
], 403);
|
|
}
|
|
|
|
$record = $this->attendanceService->getTodayRecord($employee);
|
|
|
|
$branchId = $employee->branch_id ?? $request->user()->branch_id ?? null;
|
|
$creatorId = $request->user()->type === 'company' ? $request->user()->id : $request->user()->created_by;
|
|
$activeLocation = \App\Models\AttendanceLocation::where('created_by', $creatorId)
|
|
->where('is_active', true)
|
|
->where(function ($q) use ($branchId) {
|
|
$q->whereNull('branch_id')->orWhere('branch_id', $branchId);
|
|
})
|
|
->first();
|
|
|
|
$geofence = null;
|
|
if ($activeLocation) {
|
|
$geofence = [
|
|
'latitude' => (float)$activeLocation->latitude,
|
|
'longitude' => (float)$activeLocation->longitude,
|
|
'radius' => (int)$activeLocation->radius_meters,
|
|
'name' => $activeLocation->name,
|
|
];
|
|
} else {
|
|
$branch = $branchId ? \App\Models\Branch::find($branchId) : null;
|
|
if ($branch && isset($branch->latitude, $branch->longitude)) {
|
|
$geofence = [
|
|
'latitude' => (float)$branch->latitude,
|
|
'longitude' => (float)$branch->longitude,
|
|
'radius' => (int)($branch->radius ?? 500),
|
|
'name' => $branch->name ?? 'Branch',
|
|
];
|
|
}
|
|
}
|
|
|
|
$formatDateTime = function ($date, $time) {
|
|
if (!$date || !$time) return null;
|
|
$cleanDate = explode('T', $date)[0];
|
|
$cleanDate = explode(' ', $cleanDate)[0];
|
|
return "{$cleanDate}T{$time}";
|
|
};
|
|
|
|
$data = [
|
|
'clocked_in' => $record ? !!$record->clock_in : false,
|
|
'clocked_out' => $record ? !!$record->clock_out : false,
|
|
'on_break' => $record ? (!empty($record->break_in) && empty($record->break_out)) : false,
|
|
'clock_in_time' => $record ? $formatDateTime($record->date, $record->clock_in) : null,
|
|
'clock_out_time' => $record ? $formatDateTime($record->date, $record->clock_out) : null,
|
|
'break_in_time' => $record ? $formatDateTime($record->date, $record->break_in) : null,
|
|
'break_out_time' => $record ? $formatDateTime($record->date, $record->break_out) : null,
|
|
'total_hours' => $record ? (float)($record->total_hours ?? 0) : 0,
|
|
'late_hours' => $record ? (float)($record->late_hours ?? 0) : 0,
|
|
'early_hours' => $record ? (float)($record->early_hours ?? 0) : 0,
|
|
'break_hours' => $record ? (float)($record->break_hours ?? 0) : 0,
|
|
'overtime_hours' => $record ? (float)($record->overtime_hours ?? 0) : 0,
|
|
'status' => $record->status ?? 'present',
|
|
'notes' => $record->notes ?? null,
|
|
'geofence' => $geofence,
|
|
];
|
|
|
|
if ($record) {
|
|
$data['id'] = $record->id;
|
|
$data['date'] = $record->date;
|
|
$data['clock_in'] = $record->clock_in;
|
|
$data['clock_out'] = $record->clock_out;
|
|
$data['break_in'] = $record->break_in;
|
|
$data['break_out'] = $record->break_out;
|
|
}
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $data,
|
|
'message' => null,
|
|
'errors' => null,
|
|
]);
|
|
}
|
|
|
|
public function clock(Request $request)
|
|
{
|
|
$action = $request->input('action') ?? $request->input('type');
|
|
$actionMap = [
|
|
'in' => 'clock_in',
|
|
'out' => 'clock_out',
|
|
'clock_in' => 'clock_in',
|
|
'clock_out' => 'clock_out',
|
|
'break_in' => 'break_in',
|
|
'break_out' => 'break_out',
|
|
];
|
|
|
|
$resolvedAction = $actionMap[$action] ?? $action;
|
|
$request->merge(['action' => $resolvedAction]);
|
|
|
|
$request->validate([
|
|
'action' => 'required|string|in:clock_in,clock_out,break_in,break_out',
|
|
'latitude' => 'nullable|numeric',
|
|
'longitude' => 'nullable|numeric',
|
|
'notes' => 'nullable|string',
|
|
'activity_note' => 'nullable|string',
|
|
'activity' => 'nullable|string',
|
|
]);
|
|
|
|
try {
|
|
$employee = $this->employeeService->resolveEmployee($request->user());
|
|
} catch (Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'data' => null,
|
|
'message' => 'Employee profile not found',
|
|
'errors' => null,
|
|
], 403);
|
|
}
|
|
|
|
$payload = array_merge($request->all(), ['action' => $resolvedAction]);
|
|
$result = $this->attendanceService->processClock($request->user(), $employee, $payload);
|
|
|
|
return response()->json([
|
|
'success' => $result['success'],
|
|
'data' => $result['data'],
|
|
'message' => $result['message'],
|
|
'errors' => null,
|
|
], $result['status_code']);
|
|
}
|
|
|
|
public function history(Request $request)
|
|
{
|
|
try {
|
|
$employee = $this->employeeService->resolveEmployee($request->user());
|
|
} catch (Exception $e) {
|
|
return response()->json([
|
|
'success' => false,
|
|
'data' => [],
|
|
'message' => 'Employee profile not found',
|
|
'errors' => null,
|
|
], 403);
|
|
}
|
|
|
|
$records = AttendanceRecord::where(function ($q) use ($employee, $request) {
|
|
$q->where('employee_id', $employee->id)
|
|
->orWhere('employee_id', $request->user()->id);
|
|
})
|
|
->orderBy('date', 'desc')
|
|
->limit(30)
|
|
->get();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => $records,
|
|
'message' => null,
|
|
'errors' => null,
|
|
]);
|
|
}
|
|
|
|
public function summary(Request $request)
|
|
{
|
|
$todayStr = \Carbon\Carbon::now('Asia/Manila')->format('Y-m-d');
|
|
$currentMonth = \Carbon\Carbon::now('Asia/Manila')->format('m');
|
|
|
|
// Employees on approved leave today
|
|
$offEmployees = \App\Models\LeaveApplication::with('user:id,name,avatar')
|
|
->where('status', 'approved')
|
|
->whereDate('start_date', '<=', $todayStr)
|
|
->whereDate('end_date', '>=', $todayStr)
|
|
->get()
|
|
->map(function ($app) {
|
|
return [
|
|
'id' => $app->user_id,
|
|
'name' => $app->user->name ?? 'Employee',
|
|
'avatar' => $app->user->avatar ?? null,
|
|
];
|
|
})
|
|
->unique('id')
|
|
->values();
|
|
|
|
// Employees with birthdays this month
|
|
$birthdayEmployees = \App\Models\Employee::with('user:id,name,avatar')
|
|
->whereNotNull('date_of_birth')
|
|
->whereMonth('date_of_birth', $currentMonth)
|
|
->get()
|
|
->map(function ($emp) {
|
|
return [
|
|
'id' => $emp->user_id,
|
|
'name' => $emp->user->name ?? $emp->name ?? 'Employee',
|
|
'avatar' => $emp->user->avatar ?? null,
|
|
'dob' => $emp->date_of_birth,
|
|
];
|
|
})
|
|
->values();
|
|
|
|
return response()->json([
|
|
'success' => true,
|
|
'data' => [
|
|
'off_today_count' => $offEmployees->count(),
|
|
'off_today_list' => $offEmployees,
|
|
'birthday_this_month_count' => $birthdayEmployees->count(),
|
|
'birthday_this_month_list' => $birthdayEmployees,
|
|
],
|
|
'message' => null,
|
|
'errors' => null,
|
|
]);
|
|
}
|
|
}
|