243 lines
6.6 KiB
PHP
243 lines
6.6 KiB
PHP
<?php
|
|
|
|
namespace Modules\HRM\Repositories;
|
|
|
|
use App\Abstracts\EntityRepository;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Contracts\Pagination\Paginator;
|
|
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
|
|
use Illuminate\Database\Query\Builder;
|
|
use Illuminate\Support\Facades\DB;
|
|
use Modules\HRM\Models\Attendance;
|
|
use Modules\HRM\Models\AttendanceLog;
|
|
|
|
class AttendanceRepository extends EntityRepository
|
|
{
|
|
public string $table = Attendance::TABLE_NAME;
|
|
|
|
protected array $fillableColumns = [
|
|
'restaurant_id',
|
|
'employee_id',
|
|
'date',
|
|
'first_clock_in',
|
|
'last_clock_out',
|
|
'hours_worked',
|
|
'status',
|
|
'breaks',
|
|
'notes',
|
|
];
|
|
|
|
protected function getQuery(): Builder
|
|
{
|
|
return parent::getQuery();
|
|
}
|
|
|
|
protected function getFilterData(array $filterData = []): array
|
|
{
|
|
return array_merge([
|
|
'perPage' => 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.',
|
|
];
|
|
}
|
|
}
|