option('date') ? Carbon::parse($this->option('date'))->startOfDay() : Carbon::yesterday()->startOfDay(); $settings = app(HrSettings::class); $latePenalty = $settings->late_penalty_amount; $absentPenalty = $settings->absent_penalty_amount; $scheduledTime = $settings->scheduled_check_in_time; if ($latePenalty <= 0 && $absentPenalty <= 0) { $this->info('Denda keterlambatan dan bolos belum diatur (0). Lewatkan.'); return self::SUCCESS; } $scheduledCheckIn = Carbon::createFromFormat('Y-m-d H:i', $date->format('Y-m-d').' '.$scheduledTime); $employees = Employee::query() ->where('status', EmployeeStatus::ACTIVE) ->get(); $lateCount = 0; $absentCount = 0; $skippedLeave = 0; $skippedDuplicate = 0; foreach ($employees as $employee) { $hasLeave = LeaveRequest::approved() ->where('employee_id', $employee->id) ->whereDate('start_date', '<=', $date) ->whereDate('end_date', '>=', $date) ->exists(); if ($hasLeave) { $skippedLeave++; continue; } $attendance = Attendance::query() ->where('employee_id', $employee->id) ->whereDate('attendance_date', $date) ->first(); if ($attendance === null) { if ($absentPenalty <= 0) { continue; } $payroll = $this->findOpenPayroll($employee->id); if ($payroll === null) { continue; } $alreadyExists = $payroll->adjustments() ->where('attendance_id', null) ->where('type', PayrollAdjustmentType::DEDUCTION) ->where('description', 'like', "%Bolos {$date->format('d/m/Y')}%") ->exists(); if ($alreadyExists) { $skippedDuplicate++; continue; } $payroll->adjustments()->create([ 'type' => PayrollAdjustmentType::DEDUCTION, 'amount' => $absentPenalty, 'description' => "Bolos {$date->format('d/m/Y')}", 'created_by_id' => 1, ]); $payroll->load('adjustments'); $payroll->recalculateAmounts(); $payroll->save(); $absentCount++; continue; } if ($latePenalty <= 0) { continue; } $checkInTime = Carbon::parse($attendance->check_in_at); if ($checkInTime->lte($scheduledCheckIn)) { continue; } $payroll = $this->findOpenPayroll($employee->id); if ($payroll === null) { continue; } $alreadyExists = $payroll->adjustments() ->where('attendance_id', $attendance->id) ->where('type', PayrollAdjustmentType::DEDUCTION) ->exists(); if ($alreadyExists) { $skippedDuplicate++; continue; } $minutesLate = (int) $scheduledCheckIn->diffInMinutes($checkInTime); $payroll->adjustments()->create([ 'type' => PayrollAdjustmentType::DEDUCTION, 'amount' => $latePenalty, 'description' => "Terlambat {$date->format('d/m/Y')} ({$minutesLate} menit)", 'attendance_id' => $attendance->id, 'created_by_id' => 1, ]); $payroll->load('adjustments'); $payroll->recalculateAmounts(); $payroll->save(); $lateCount++; } $this->info("Selesai memproses tanggal {$date->format('d/m/Y')}:"); $this->info(" - Terlambat: {$lateCount} pegawai"); $this->info(" - Bolos: {$absentCount} pegawai"); $this->info(" - Lewati (cuti): {$skippedLeave} pegawai"); $this->info(" - Lewati (duplikat): {$skippedDuplicate} pegawai"); return self::SUCCESS; } private function findOpenPayroll(int $employeeId): ?Payroll { $period = PayrollPeriod::query() ->where('status', PayrollPeriodStatus::OPEN) ->first(); if ($period === null) { return null; } return Payroll::query() ->where('payroll_period_id', $period->id) ->where('employee_id', $employeeId) ->first(); } }