Files
HRM-System/app/Models/PayrollRun.php
2026-04-20 00:49:05 +08:00

214 lines
6.7 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Factories\HasFactory;
class PayrollRun extends BaseModel
{
use HasFactory;
protected $fillable = [
'title',
'payroll_frequency',
'pay_period_start',
'pay_period_end',
'pay_date',
'total_gross_pay',
'total_deductions',
'total_net_pay',
'employee_count',
'status',
'notes',
'created_by',
];
protected $casts = [
'pay_period_start' => 'date:Y-m-d',
'pay_period_end' => 'date:Y-m-d',
'pay_date' => 'date:Y-m-d',
'total_gross_pay' => 'decimal:2',
'total_deductions' => 'decimal:2',
'total_net_pay' => 'decimal:2',
];
/**
* Get the payroll entries.
*/
public function payrollEntries()
{
return $this->hasMany(PayrollEntry::class);
}
/**
* Get the payslips through payroll entries.
*/
public function payslips()
{
return $this->hasManyThrough(Payslip::class, PayrollEntry::class);
}
/**
* Get the user who created the payroll run.
*/
public function creator()
{
return $this->belongsTo(User::class, 'created_by');
}
/**
* Calculate and update totals.
*/
public function calculateTotals()
{
$entries = $this->payrollEntries;
$this->total_gross_pay = $entries->sum('gross_pay');
$this->total_deductions = $entries->sum('total_deductions');
$this->total_net_pay = $entries->sum('net_pay');
$this->employee_count = $entries->count();
$this->save();
}
/**
* Process payroll for all employees.
*/
public function processPayroll()
{
if ($this->status !== 'draft') {
return false;
}
$this->status = 'draft';
$this->save();
try {
// Get all active employees
$employees = User::with('employee')->where('type', 'employee')
->whereIn('created_by', getCompanyAndUsersId())
->whereHas('employee', function ($q) {
$q->whereIn('employee_status', ['active', 'probation']);
})
->orderby('id', 'desc')
->get();
foreach ($employees as $employee) {
$this->processEmployeePayroll($employee);
}
$this->calculateTotals();
$this->status = 'completed';
$this->save();
return true;
} catch (\Exception $e) {
$this->status = 'draft';
$this->save();
throw $e;
}
}
/**
* Process payroll for individual employee.
*/
private function processEmployeePayroll($employee)
{
// Skip if entry already exists for this employee in this run
$existingEntry = PayrollEntry::where('payroll_run_id', $this->id)
->where('employee_id', $employee->id)
->exists();
if ($existingEntry) {
return;
}
// Call the unified DOLE compliant calculation engine
$service = new \App\Services\PayrollService();
$salaryBreakdown = $service->calculateForPeriod(
$employee->employee,
$this->pay_period_start->format('Y-m-d'),
$this->pay_period_end->format('Y-m-d')
);
if (!$salaryBreakdown || !isset($salaryBreakdown['basic_salary'])) {
return;
}
$summary = $salaryBreakdown['summary'] ?? [];
// Determine pure dynamically added component earnings (anything above basic)
$componentEarnings = $salaryBreakdown['total_earnings'] - $salaryBreakdown['basic_salary'];
// Persist payroll entry mapped to PayrollService output
PayrollEntry::create([
'payroll_run_id' => $this->id,
'employee_id' => $employee->id,
'basic_salary' => $salaryBreakdown['basic_salary'],
'component_earnings' => $componentEarnings,
'total_earnings' => $salaryBreakdown['total_earnings'],
'total_deductions' => $salaryBreakdown['total_deductions'],
'gross_pay' => $salaryBreakdown['total_earnings'],
'net_pay' => $salaryBreakdown['net_pay'],
'working_days' => ($summary['days_worked'] ?? 0) + ($summary['absences'] ?? 0),
'present_days' => $summary['days_worked'] ?? 0,
'full_present_days' => $summary['days_worked'] ?? 0,
'half_days' => 0,
'holiday_days' => $summary['holiday'] ?? 0,
'paid_leave_days' => $summary['leaves'] ?? 0,
'unpaid_leave_days' => 0,
'absent_days' => $summary['absences'] ?? 0,
'overtime_hours' => 0,
'overtime_amount' => $summary['reg_ot'] ?? 0,
'per_day_salary' => $salaryBreakdown['daily_rate'],
'unpaid_leave_deduction' => $summary['absence_amount'] ?? 0,
'earnings_breakdown' => $salaryBreakdown['earnings'],
'deductions_breakdown' => $salaryBreakdown['deductions'],
'created_by' => $this->created_by,
]);
}
/**
* Get employee leave data for pay period.
*/
private function getEmployeeLeaveData($employeeId)
{
$leaveApplications = \App\Models\LeaveApplication::where('employee_id', $employeeId)
->where('status', 'approved')
->where(function ($query) {
$query->whereBetween('start_date', [$this->pay_period_start, $this->pay_period_end])
->orWhereBetween('end_date', [$this->pay_period_start, $this->pay_period_end])
->orWhere(function ($q) {
$q->where('start_date', '<=', $this->pay_period_start)
->where('end_date', '>=', $this->pay_period_end);
});
})
->with('leaveType')
->get();
$paidLeaveDays = 0;
$unpaidLeaveDays = 0;
foreach ($leaveApplications as $leave) {
// Calculate days within pay period
$leaveStart = max($leave->start_date, $this->pay_period_start);
$leaveEnd = min($leave->end_date, $this->pay_period_end);
$leaveDays = $leaveStart->diffInDays($leaveEnd) + 1;
if ($leave->leaveType->is_paid) {
$paidLeaveDays += $leaveDays;
} else {
$unpaidLeaveDays += $leaveDays;
}
}
return [
'paid_leave_days' => $paidLeaveDays,
'unpaid_leave_days' => $unpaidLeaveDays,
];
}
}