'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 = 'processing'; $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' => array_merge($salaryBreakdown['earnings'], [ 'meta' => [ 'daily_rate' => $salaryBreakdown['daily_rate'], 'pay_type' => $salaryBreakdown['pay_type'], ], 'summary' => $summary ]), '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, ]; } }