179 lines
5.0 KiB
PHP
179 lines
5.0 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\EmployeeAdvanceStatus;
|
|
use App\Enums\PayrollAdjustmentType;
|
|
use App\Enums\PayrollStatus;
|
|
use App\Models\Concerns\InteractsWithActivityLog;
|
|
use Illuminate\Database\Eloquent\Attributes\Appends;
|
|
use Illuminate\Database\Eloquent\Attributes\Guarded;
|
|
use Illuminate\Database\Eloquent\Attributes\Scope;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
#[Guarded(['id'])]
|
|
#[Appends([
|
|
'base_salary_formatted',
|
|
'bonus_amount_formatted',
|
|
'can_adjust',
|
|
'deduction_amount_formatted',
|
|
'employee_name',
|
|
'paid_at_formatted',
|
|
'status_label',
|
|
'total_amount_formatted',
|
|
])]
|
|
class Payroll extends Model
|
|
{
|
|
// 1. Use Trait
|
|
use HasFactory, InteractsWithActivityLog;
|
|
|
|
// 2. Casting
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'base_salary' => 'integer',
|
|
'bonus_amount' => 'integer',
|
|
'deduction_amount' => 'integer',
|
|
'paid_at' => 'datetime',
|
|
'status' => PayrollStatus::class,
|
|
'total_amount' => 'integer',
|
|
];
|
|
}
|
|
|
|
// 3. Scope (grouped by column, then alphabetical)
|
|
// Column Group: status
|
|
#[Scope]
|
|
protected function paid(Builder $query): void
|
|
{
|
|
$query->where('status', PayrollStatus::PAID);
|
|
}
|
|
|
|
#[Scope]
|
|
protected function unpaid(Builder $query): void
|
|
{
|
|
$query->where('status', PayrollStatus::UNPAID);
|
|
}
|
|
|
|
// 4. Attribute
|
|
public function baseSalaryFormatted(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => $this->base_salary !== null ? 'Rp '.number_format($this->base_salary, 0, ',', '.') : '-',
|
|
);
|
|
}
|
|
|
|
public function bonusAmountFormatted(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => 'Rp '.number_format($this->bonus_amount, 0, ',', '.'),
|
|
);
|
|
}
|
|
|
|
public function canAdjust(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => $this->status === PayrollStatus::UNPAID
|
|
&& $this->relationLoaded('payrollPeriod')
|
|
&& $this->payrollPeriod?->isOpen(),
|
|
);
|
|
}
|
|
|
|
public function deductionAmountFormatted(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => 'Rp '.number_format($this->deduction_amount, 0, ',', '.'),
|
|
);
|
|
}
|
|
|
|
public function employeeName(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => $this->employee?->user?->profile?->full_name
|
|
?? $this->employee?->user?->username,
|
|
);
|
|
}
|
|
|
|
public function paidAtFormatted(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => $this->paid_at?->translatedFormat('l, d F Y H:i'),
|
|
);
|
|
}
|
|
|
|
public function statusLabel(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => $this->status?->label(),
|
|
);
|
|
}
|
|
|
|
public function totalAmountFormatted(): Attribute
|
|
{
|
|
return Attribute::make(
|
|
get: fn () => 'Rp '.number_format($this->total_amount, 0, ',', '.'),
|
|
);
|
|
}
|
|
|
|
// 5. Other Methods
|
|
public function calculateAdvanceDeduction(): int
|
|
{
|
|
$outstanding = (int) EmployeeAdvance::query()
|
|
->where('employee_id', $this->employee_id)
|
|
->whereIn('status', [EmployeeAdvanceStatus::APPROVED, EmployeeAdvanceStatus::PARTIALLY_PAID])
|
|
->selectRaw('SUM(amount - paid_amount) as remaining')
|
|
->value('remaining');
|
|
|
|
return min($outstanding, (int) $this->base_salary + (int) $this->adjustments()
|
|
->where('type', PayrollAdjustmentType::BONUS)
|
|
->sum('amount'));
|
|
}
|
|
|
|
public function recalculateAmounts(): void
|
|
{
|
|
$bonusAmount = (int) $this->adjustments()
|
|
->where('type', PayrollAdjustmentType::BONUS)
|
|
->sum('amount');
|
|
|
|
$manualDeduction = (int) $this->adjustments()
|
|
->where('type', PayrollAdjustmentType::DEDUCTION)
|
|
->sum('amount');
|
|
|
|
$advanceDeduction = $this->calculateAdvanceDeduction();
|
|
|
|
$this->bonus_amount = $bonusAmount;
|
|
$this->deduction_amount = $advanceDeduction + $manualDeduction;
|
|
$this->total_amount = max(0, (int) $this->base_salary + $bonusAmount - $this->deduction_amount);
|
|
}
|
|
|
|
// 6. Relation
|
|
public function adjustments(): HasMany
|
|
{
|
|
return $this->hasMany(PayrollAdjustment::class);
|
|
}
|
|
|
|
public function cashTransaction(): BelongsTo
|
|
{
|
|
return $this->belongsTo(CashTransaction::class);
|
|
}
|
|
|
|
public function employee(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Employee::class);
|
|
}
|
|
|
|
public function paidBy(): BelongsTo
|
|
{
|
|
return $this->belongsTo(User::class, 'paid_by_id');
|
|
}
|
|
|
|
public function payrollPeriod(): BelongsTo
|
|
{
|
|
return $this->belongsTo(PayrollPeriod::class);
|
|
}
|
|
}
|