10, 'search' => '', 'orderBy' => 'id', 'order' => 'desc', 'with_deleted' => false, ], $filterData); } public function getAttendanceQuery(): EloquentBuilder { return Attendance::with(['employee', 'logs']); } protected function filterSearchQuery(Builder|EloquentBuilder $query, string $searchedText): Builder { $searchable = "%$searchedText%"; return $query->where("{$this->table}.status", 'LIKE', $searchable); } public function getAll(array $filterData = []): Paginator { $filter = $this->getFilterData($filterData); $query = $this->getAttendanceQuery(); if (! $filter['with_deleted']) { $query->whereNull("{$this->table}.deleted_at"); } if (! empty($filter['search'])) { $query = $this->filterSearchQuery($query, $filter['search']); } return $query->orderBy($filter['orderBy'], $filter['order']) ->paginate($filter['perPage']); } public function getCount(array $filterData = []): int { $filter = $this->getFilterData($filterData); $query = $this->getQuery(); if (! $filter['with_deleted']) { $query->whereNull("{$this->table}.deleted_at"); } return $query->count(); } /** * CREATE ATTENDANCE + LOGS */ public function create(array $data): Attendance { return DB::transaction(function () use ($data) { $date = $data['date']; $employee = $data['employee_id']; // Always fetch today attendance or create new $attendance = Attendance::firstOrNew([ 'employee_id' => $employee, 'date' => $date, ]); $prepared = $this->prepareForDB($data, $attendance->exists ? $attendance : null); // Save attendance $attendance->fill($prepared)->save(); // Insert Punch Logs if (! empty($data['clock_in'])) { $this->insertLog($attendance, 'in', $data['clock_in']); } if (! empty($data['clock_out'])) { $this->insertLog($attendance, 'out', $data['clock_out']); } // Recalculate working hours after logs $this->recalculateWorkedHours($attendance); return $attendance; }); } /** * UPDATE ATTENDANCE + REBUILD LOGS */ public function update(int $id, array $data): object { return DB::transaction(function () use ($id, $data) { $attendance = Attendance::findOrFail($id); $prepared = $this->prepareForDB($data, $attendance); $attendance->update($prepared); // Remove old logs AttendanceLog::where('attendance_id', $id)->delete(); // Add new logs if (! empty($data['clock_in'])) { $this->insertLog($attendance, 'in', $data['clock_in']); } if (! empty($data['clock_out'])) { $this->insertLog($attendance, 'out', $data['clock_out']); } $this->recalculateWorkedHours($attendance); return $this->getById($id); }); } /** * MAP REQUEST TO DB FIELDS */ public function prepareForDB(array $data, ?object $item = null): array { $data = parent::prepareForDB($data, $item); if (! $item) { $data['created_at'] = now(); $data['restaurant_id'] = $this->getCurrentRestaurantId(); } else { $data['updated_at'] = now(); } if (isset($data['clock_in'])) { $data['first_clock_in'] = $data['clock_in']; unset($data['clock_in']); } if (isset($data['clock_out'])) { $data['last_clock_out'] = $data['clock_out']; unset($data['clock_out']); } if (isset($data['breaks']) && is_array($data['breaks'])) { $data['breaks'] = json_encode($data['breaks']); } if (isset($data['notes']) && is_array($data['notes'])) { $data['notes'] = json_encode($data['notes']); } return $data; } /** * INSERT SINGLE PUNCH LOG ENTRY */ private function insertLog(Attendance $attendance, string $type, string $time) { if (empty($time)) { return; } $dateTime = Carbon::parse($attendance->date.' '.$time)->format('Y-m-d H:i:s'); AttendanceLog::create([ 'attendance_id' => $attendance->id, 'employee_id' => $attendance->employee_id, 'restaurant_id' => $attendance->restaurant_id, 'type' => $type, // in or out 'punch_time' => $dateTime, 'created_at' => now(), ]); } /** * RECALCULATE TOTAL WORKED HOURS BASED ON ALL LOGS */ private function recalculateWorkedHours(Attendance $attendance) { $logs = AttendanceLog::where('attendance_id', $attendance->id) ->orderBy('punch_time') ->get(); if ($logs->count() < 2) { return; } $first = Carbon::parse($logs->first()->punch_time); $last = Carbon::parse($logs->last()->punch_time); $hours = round($first->diffInMinutes($last) / 60, 2); $attendance->update([ 'first_clock_in' => $first, 'last_clock_out' => $last, 'hours_worked' => $hours, ]); } protected function getExceptionMessages(): array { return [ static::MESSAGE_ITEM_DOES_NOT_EXIST_MESSAGE => 'Attendance does not exist.', static::MESSAGE_ITEM_COULD_NOT_BE_DELETED => 'Attendance could not be deleted.', ]; } }