196 lines
5.9 KiB
PHP
196 lines
5.9 KiB
PHP
<?php
|
|
|
|
namespace App\Jobs;
|
|
|
|
use App\Enums\PayrollAdjustmentType;
|
|
use App\Enums\PayrollStatus;
|
|
use App\Models\Attendance;
|
|
use App\Models\Employee;
|
|
use App\Models\Payroll;
|
|
use App\Models\PayrollAdjustment;
|
|
use App\Models\PayrollPeriod;
|
|
use App\Models\User;
|
|
use App\Settings\HRSettings;
|
|
use Carbon\CarbonInterface;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
class CheckAttendancePenaltiesJob implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
public int $tries = 3;
|
|
|
|
public function handle(): void
|
|
{
|
|
$yesterday = now()->subDay();
|
|
$dayOfWeek = (int) $yesterday->format('w');
|
|
|
|
if ($dayOfWeek === 0 || $dayOfWeek === 6) {
|
|
return;
|
|
}
|
|
|
|
$hrSettings = app(HRSettings::class);
|
|
|
|
if ($hrSettings->late_penalty_amount <= 0 && $hrSettings->absent_penalty_amount <= 0) {
|
|
return;
|
|
}
|
|
|
|
$cuttingDay = 5;
|
|
|
|
if ($yesterday->day < $cuttingDay) {
|
|
$periodMonth = $yesterday->month;
|
|
$periodYear = $yesterday->year;
|
|
} else {
|
|
$periodMonth = $yesterday->month + 1;
|
|
$periodYear = $yesterday->year;
|
|
if ($periodMonth > 12) {
|
|
$periodMonth = 1;
|
|
$periodYear++;
|
|
}
|
|
}
|
|
|
|
$period = PayrollPeriod::where('year', $periodYear)
|
|
->where('month', $periodMonth)
|
|
->first();
|
|
|
|
if (! $period) {
|
|
return;
|
|
}
|
|
|
|
$systemUser = User::first();
|
|
|
|
if (! $systemUser) {
|
|
return;
|
|
}
|
|
|
|
$employees = Employee::whereHas('user', fn ($q) => $q->where('is_active', true))
|
|
->where(fn ($q) => $q->whereNull('resign_date')->orWhere('resign_date', '>=', $yesterday->toDateString()))
|
|
->get();
|
|
|
|
foreach ($employees as $employee) {
|
|
$payroll = Payroll::where('payroll_period_id', $period->id)
|
|
->where('employee_id', $employee->id)
|
|
->where('status', PayrollStatus::UNPAID)
|
|
->first();
|
|
|
|
if (! $payroll) {
|
|
continue;
|
|
}
|
|
|
|
$attendance = Attendance::where('employee_id', $employee->id)
|
|
->where('attendance_date', $yesterday->toDateString())
|
|
->first();
|
|
|
|
if ($attendance) {
|
|
$this->handleLate($attendance, $payroll, $hrSettings, $systemUser, $yesterday);
|
|
} else {
|
|
$this->handleAbsent($payroll, $hrSettings, $systemUser, $yesterday, $employee);
|
|
}
|
|
}
|
|
}
|
|
|
|
private function handleLate(
|
|
Attendance $attendance,
|
|
Payroll $payroll,
|
|
HRSettings $hrSettings,
|
|
User $systemUser,
|
|
CarbonInterface $date
|
|
): void {
|
|
if ($hrSettings->late_penalty_amount <= 0) {
|
|
return;
|
|
}
|
|
|
|
$checkInTime = $attendance->check_in_at;
|
|
[$officeHour, $officeMinute] = explode(':', $hrSettings->scheduled_check_in_time);
|
|
$officeStart = $date->copy()->setTime((int) $officeHour, (int) $officeMinute, 0);
|
|
|
|
if (! $checkInTime || $checkInTime->lte($officeStart)) {
|
|
return;
|
|
}
|
|
|
|
$lateMinutes = (int) $checkInTime->diffInMinutes($officeStart);
|
|
|
|
$existing = PayrollAdjustment::where('payroll_id', $payroll->id)
|
|
->where('attendance_id', $attendance->id)
|
|
->where('type', PayrollAdjustmentType::DEDUCTION)
|
|
->exists();
|
|
|
|
if ($existing) {
|
|
return;
|
|
}
|
|
|
|
DB::transaction(function () use ($payroll, $attendance, $hrSettings, $systemUser, $date, $lateMinutes) {
|
|
PayrollAdjustment::create([
|
|
'payroll_id' => $payroll->id,
|
|
'created_by_id' => $systemUser->id,
|
|
'attendance_id' => $attendance->id,
|
|
'type' => PayrollAdjustmentType::DEDUCTION,
|
|
'amount' => $hrSettings->late_penalty_amount,
|
|
'description' => 'Denda keterlambatan '.$date->format('d/m/Y').' - '.$lateMinutes.' menit',
|
|
]);
|
|
|
|
$this->recalculatePayroll($payroll);
|
|
});
|
|
}
|
|
|
|
private function handleAbsent(
|
|
Payroll $payroll,
|
|
HRSettings $hrSettings,
|
|
User $systemUser,
|
|
CarbonInterface $date,
|
|
Employee $employee
|
|
): void {
|
|
if ($hrSettings->absent_penalty_amount <= 0) {
|
|
return;
|
|
}
|
|
|
|
$description = 'Denda ketidakhadiran '.$date->format('d/m/Y');
|
|
|
|
$existing = PayrollAdjustment::where('payroll_id', $payroll->id)
|
|
->where('type', PayrollAdjustmentType::DEDUCTION)
|
|
->where('description', $description)
|
|
->exists();
|
|
|
|
if ($existing) {
|
|
return;
|
|
}
|
|
|
|
DB::transaction(function () use ($payroll, $hrSettings, $systemUser, $description) {
|
|
PayrollAdjustment::create([
|
|
'payroll_id' => $payroll->id,
|
|
'created_by_id' => $systemUser->id,
|
|
'attendance_id' => null,
|
|
'type' => PayrollAdjustmentType::DEDUCTION,
|
|
'amount' => $hrSettings->absent_penalty_amount,
|
|
'description' => $description,
|
|
]);
|
|
|
|
$this->recalculatePayroll($payroll);
|
|
});
|
|
}
|
|
|
|
private function recalculatePayroll(Payroll $payroll): void
|
|
{
|
|
$payroll->refresh();
|
|
|
|
$bonuses = $payroll->payrollAdjustments()
|
|
->where('type', PayrollAdjustmentType::BONUS)
|
|
->sum('amount');
|
|
|
|
$deductions = $payroll->payrollAdjustments()
|
|
->where('type', PayrollAdjustmentType::DEDUCTION)
|
|
->sum('amount');
|
|
|
|
$payroll->update([
|
|
'bonus_amount' => $bonuses,
|
|
'deduction_amount' => $deductions,
|
|
'total_amount' => $payroll->base_salary + $bonuses - $deductions,
|
|
]);
|
|
}
|
|
}
|