127 lines
3.8 KiB
PHP
127 lines
3.8 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use App\Models\Attendance;
|
|
use App\Models\AttendanceSetting;
|
|
use App\Models\User;
|
|
use Carbon\Carbon;
|
|
use Illuminate\Console\Command;
|
|
|
|
class SyncAttendanceDailyStatus extends Command
|
|
{
|
|
/**
|
|
* The name and signature of the console command.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $signature = 'attendance:sync-daily-status {--date= : Target date (Y-m-d), default H-1 berdasarkan timezone setting}';
|
|
|
|
/**
|
|
* The console command description.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected $description = 'Sinkron status absensi: isi alpha jika tidak absen, dan beri catatan jika check-out tidak ditemukan pada hari kerja efektif.';
|
|
|
|
/**
|
|
* Execute the console command.
|
|
*/
|
|
public function handle(): int
|
|
{
|
|
$setting = AttendanceSetting::query()->first();
|
|
|
|
if (!$setting) {
|
|
$this->error('Attendance setting belum tersedia. Jalankan seeder AttendanceSettingSeeder.');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$timezone = $setting->timezone ?: config('app.timezone', 'Asia/Jakarta');
|
|
$targetDate = $this->resolveTargetDate($timezone);
|
|
|
|
if (!$targetDate) {
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$effectiveWorkdays = collect($setting->effective_workdays ?? [])
|
|
->map(fn($day) => (int) $day)
|
|
->unique()
|
|
->values();
|
|
|
|
if ($effectiveWorkdays->isNotEmpty() && !$effectiveWorkdays->contains($targetDate->isoWeekday())) {
|
|
$this->info("Skip {$targetDate->toDateString()}: bukan hari kerja efektif.");
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
$users = User::query()
|
|
->where('status', 'aktif')
|
|
->select('id', 'name')
|
|
->get();
|
|
|
|
$createdAlpha = 0;
|
|
$notedMissingCheckout = 0;
|
|
|
|
foreach ($users as $user) {
|
|
$attendance = Attendance::query()
|
|
->where('user_id', $user->id)
|
|
->whereDate('date', $targetDate->toDateString())
|
|
->first();
|
|
|
|
if (!$attendance) {
|
|
Attendance::create([
|
|
'user_id' => $user->id,
|
|
'date' => $targetDate->toDateString(),
|
|
'status' => 'alpha',
|
|
'notes' => 'Tidak melakukan absensi pada hari kerja efektif.',
|
|
]);
|
|
$createdAlpha++;
|
|
continue;
|
|
}
|
|
|
|
if ($setting->require_checkout && $attendance->check_in && !$attendance->check_out) {
|
|
$attendance->update([
|
|
'notes' => $this->appendNote($attendance->notes, 'Absensi tidak lengkap: check-out tidak ditemukan.'),
|
|
]);
|
|
$notedMissingCheckout++;
|
|
}
|
|
}
|
|
|
|
$this->info("Sinkronisasi {$targetDate->toDateString()} selesai.");
|
|
$this->line("Alpha dibuat: {$createdAlpha}");
|
|
$this->line("Catatan check-out kosong: {$notedMissingCheckout}");
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function resolveTargetDate(string $timezone): ?Carbon
|
|
{
|
|
$dateOption = $this->option('date');
|
|
|
|
if (!$dateOption) {
|
|
return Carbon::now($timezone)->subDay()->startOfDay();
|
|
}
|
|
|
|
try {
|
|
return Carbon::createFromFormat('Y-m-d', (string) $dateOption, $timezone)->startOfDay();
|
|
} catch (\Throwable $e) {
|
|
$this->error('Format --date tidak valid. Gunakan Y-m-d, contoh: 2026-02-19');
|
|
return null;
|
|
}
|
|
}
|
|
|
|
private function appendNote(?string $existing, string $note): string
|
|
{
|
|
$current = trim((string) $existing);
|
|
|
|
if ($current === '') {
|
|
return $note;
|
|
}
|
|
|
|
if (str_contains($current, $note)) {
|
|
return $current;
|
|
}
|
|
|
|
return "{$current} | {$note}";
|
|
}
|
|
}
|