dstpabuaran.com/app/Services/Admin/Finance/PayrollPeriodService.php
Yoga Pangestu 6dbe9da581 feat: add payroll management features including payroll periods, adjustments, and payment processing
- Implemented routes for managing payroll periods, including current, close, and reopen functionalities.
- Added payroll payment and cancellation routes.
- Introduced payroll adjustments with store and delete functionalities.
- Created comprehensive feature tests for payroll management, covering authentication, CRUD operations, and business logic.
- Ensured proper handling of payroll adjustments and their impact on payroll totals.
- Developed tests for generating payrolls and managing payroll periods, ensuring accurate status transitions and data integrity.
2026-07-30 17:06:52 +07:00

151 lines
4.5 KiB
PHP

<?php
namespace App\Services\Admin\Finance;
use App\Enums\CashTransactionType;
use App\Enums\PayrollPeriodStatus;
use App\Enums\PayrollStatus;
use App\Models\CashAccount;
use App\Models\CashTransaction;
use App\Models\Payroll;
use App\Models\PayrollPeriod;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;
class PayrollPeriodService
{
public function getAll(): Collection
{
return PayrollPeriod::select('id', 'year', 'month', 'status', 'closed_at', 'created_at')
->withCount('payrolls')
->withSum('payrolls', 'total_amount')
->withSum('payrolls', 'bonus_amount')
->withSum('payrolls', 'deduction_amount')
->withCount(['payrolls as paid_count' => function ($q) {
$q->paid();
}])
->withCount(['payrolls as cancelled_count' => function ($q) {
$q->cancelled();
}])
->latest('year')
->latest('month')
->get();
}
public function getDetail(PayrollPeriod $period): PayrollPeriod
{
return $period->load([
'payrolls' => function ($query) {
$query->with(['employee.user.userProfile', 'payrollAdjustments'])
->orderBy('id');
},
]);
}
public function close(PayrollPeriod $period): PayrollPeriod
{
if ($period->status === PayrollPeriodStatus::CLOSED) {
throw ValidationException::withMessages([
'period' => 'Periode sudah ditutup.',
]);
}
$hasUnpaid = $period->payrolls()
->where('status', PayrollStatus::UNPAID)
->exists();
if ($hasUnpaid) {
throw ValidationException::withMessages([
'period' => 'Masih ada gaji yang belum dibayar.',
]);
}
$period->update([
'status' => PayrollPeriodStatus::CLOSED,
'closed_by_id' => auth()->id(),
'closed_at' => now(),
]);
return $period;
}
public function reopen(PayrollPeriod $period): PayrollPeriod
{
if ($period->status === PayrollPeriodStatus::OPEN) {
throw ValidationException::withMessages([
'period' => 'Periode sudah terbuka.',
]);
}
$period->update([
'status' => PayrollPeriodStatus::OPEN,
'closed_by_id' => null,
'closed_at' => null,
]);
return $period;
}
public function pay(Payroll $payroll): Payroll
{
if ($payroll->status === PayrollStatus::PAID) {
throw ValidationException::withMessages([
'payroll' => 'Gaji sudah dibayar.',
]);
}
if ($payroll->status === PayrollStatus::CANCELLED) {
throw ValidationException::withMessages([
'payroll' => 'Gaji sudah dibatalkan.',
]);
}
return DB::transaction(function () use ($payroll) {
$cashAccount = CashAccount::firstOrFail();
$newBalance = $cashAccount->balance + $payroll->total_amount;
$cashAccount->update(['balance' => $newBalance]);
$cashTransaction = CashTransaction::create([
'cash_account_id' => $cashAccount->id,
'created_by_id' => auth()->id(),
'amount' => $payroll->total_amount,
'balance_after' => $newBalance,
'type' => CashTransactionType::DEPOSIT,
'description' => 'Pembayaran gaji karyawan',
]);
$payroll->update([
'status' => PayrollStatus::PAID,
'cash_transaction_id' => $cashTransaction->id,
'paid_by_id' => auth()->id(),
'paid_at' => now(),
]);
return $payroll;
});
}
public function cancel(Payroll $payroll): Payroll
{
if ($payroll->status === PayrollStatus::PAID) {
throw ValidationException::withMessages([
'payroll' => 'Gaji yang sudah dibayar tidak dapat dibatalkan.',
]);
}
if ($payroll->status === PayrollStatus::CANCELLED) {
throw ValidationException::withMessages([
'payroll' => 'Gaji sudah dibatalkan.',
]);
}
$payroll->update([
'status' => PayrollStatus::CANCELLED,
]);
return $payroll;
}
}