feat: Implement automated payroll closing with push notifications and refine payroll filtering and status management.

This commit is contained in:
Yoga Pangestu 2025-12-18 09:39:33 +07:00
parent 36004254c9
commit 40db1c006c
6 changed files with 36 additions and 5 deletions

View File

@ -3,7 +3,9 @@
namespace App\Console\Commands; namespace App\Console\Commands;
use App\Enums\IsPaid; use App\Enums\IsPaid;
use App\Jobs\StorePushNotification;
use App\Models\Payroll; use App\Models\Payroll;
use App\Models\PushNotification;
use Carbon\Carbon; use Carbon\Carbon;
use Illuminate\Console\Command; use Illuminate\Console\Command;
@ -23,14 +25,30 @@ public function handle(): void
$updatedCount = Payroll::where('period_month', $targetMonth) $updatedCount = Payroll::where('period_month', $targetMonth)
->where('is_paid', IsPaid::NOT_PAID) ->where('is_paid', IsPaid::NOT_PAID)
->update(['is_paid' => IsPaid::PAID]); ->update([
'is_paid' => IsPaid::PAID,
'paid_at' => now(),
]);
if ($updatedCount === 0) { if ($updatedCount === 0) {
$this->warn("Tidak ada payroll yang perlu ditutup untuk bulan {$targetMonth}."); $this->warn('Tidak ada payroll yang perlu ditutup untuk bulan '.formatDateLocalized($targetMonth, 'F Y').'.');
return; return;
} }
$this->info("Payroll untuk bulan {$targetMonth} berhasil ditandai selesai. Total: {$updatedCount}"); $userIds = Payroll::where('period_month', $targetMonth)
->where('is_paid', IsPaid::PAID)
->pluck('user_id')
->unique();
StorePushNotification::dispatch(
PushNotification::whereIn('user_id', $userIds)->get(),
[
'title' => 'Gaji Sudah Cair 💸',
'body' => 'Kerja kerasmu terbayar. Gaji bulan '.formatDateLocalized($targetMonth, 'F Y').' sudah kami transfer. Semoga harimu menyenangkan.',
],
);
$this->info('Payroll untuk bulan '.formatDateLocalized($targetMonth, 'F Y')." berhasil ditandai selesai. Total: {$updatedCount}");
} }
} }

View File

@ -24,6 +24,7 @@ public function handle(): void
$users = User::whereHas('employee') $users = User::whereHas('employee')
->active() ->active()
->withoutDeveloper()
->with('employee') ->with('employee')
->get(); ->get();

View File

@ -96,7 +96,7 @@ public function builder(): Builder
'payrolls.id', 'payrolls.id',
'payrolls.user_id', 'payrolls.user_id',
'period_month', 'period_month',
'base_salary', 'payrolls.base_salary',
'bonus', 'bonus',
'deduction', 'deduction',
'total_salary', 'total_salary',
@ -104,7 +104,7 @@ public function builder(): Builder
'paid_at', 'paid_at',
'payrolls.created_at' 'payrolls.created_at'
) )
->where('period_month', '!=', now()->format('Y-m')) ->paid()
->with(['user', 'user.employee']); ->with(['user', 'user.employee']);
} }
} }

View File

@ -54,7 +54,12 @@ public function mount()
private function loadPayrolls() private function loadPayrolls()
{ {
$this->payrolls = PayrollModel::with(['user', 'user.employee']) $this->payrolls = PayrollModel::with(['user', 'user.employee'])
->notPaid()
->where('period_month', now()->format('Y-m')) ->where('period_month', now()->format('Y-m'))
->whereHas('user', function ($query) {
$query->active()
->withoutDeveloper();
})
->get(); ->get();
} }

View File

@ -38,6 +38,12 @@ public function paid(Builder $query): void
$query->where('is_paid', IsPaid::PAID->value); $query->where('is_paid', IsPaid::PAID->value);
} }
#[Scope]
public function notPaid(Builder $query): void
{
$query->where('is_paid', IsPaid::NOT_PAID->value);
}
public function user(): BelongsTo public function user(): BelongsTo
{ {
return $this->belongsTo(User::class); return $this->belongsTo(User::class);

View File

@ -3,4 +3,5 @@
use Illuminate\Support\Facades\Schedule; use Illuminate\Support\Facades\Schedule;
Schedule::command('payroll:generate --current')->monthly(); Schedule::command('payroll:generate --current')->monthly();
Schedule::command('payroll:close --current')->lastDayOfMonth('23:59');
Schedule::command('stock-opname:generate')->monthlyOn(25, '00:00'); Schedule::command('stock-opname:generate')->monthlyOn(25, '00:00');