user_id)->first(); // Safe defaults if local profile is not totally configured $grossMonthly = $salaryData ? $salaryData->basic_salary : 20000; $payFrequency = $salaryData ? $salaryData->pay_frequency : 'monthly'; $dailyRate = $grossMonthly / 26; // Strictly mathematical translation for Daily Rate $isSemiMonthly = ($payFrequency === 'semi-monthly'); $divisor = $isSemiMonthly ? 2 : 1; // Base Salary for the period $periodBasicSalary = $grossMonthly / $divisor; // Daily rate strictly derived if not explicitly set $actualDailyRate = $grossMonthly / 26; // 2. Fetch Attendance $attendances = AttendanceRecord::where('employee_id', $employee->user_id) ->whereBetween('date', [$startDate, $endDate]) ->get(); $daysWorked = 0; $renderedHours = 0; $lateHours = 0; $absencesPaidCount = 0; $absencesCount = 0; $nightDiffHours = 0; // Expected working hours for 15-day range excluding Sundays = ~12 days based on actual ranges? $expectedHours = 0; $currentDate = \Carbon\Carbon::parse($startDate); $periodEndObj = \Carbon\Carbon::parse($endDate); while ($currentDate->lte($periodEndObj)) { $dateString = $currentDate->toDateString(); // Find if there's a physical database row for today $att = $attendances->firstWhere('date', $dateString) ?: $attendances->firstWhere('date', $dateString . ' 00:00:00'); if ($att && $att->is_rest_day) { // Safely isolated by the explicit Rest Day matrix $currentDate->addDay(); continue; } // Standard Working Day math $expectedHours += 9; if (!$att || $att->is_absent || $att->status === 'absent') { // If there's no log at all, or it explicitly says absent, they are absent. $absencesCount++; } else { $daysWorked++; if ($att->total_hours) { $renderedHours += $att->total_hours; } if ($att->is_late) { $lateHours += (float) ($att->late_hours ?? 0); // Exact decimal deduction from synced legacy log } // --- NIGHT DIFFERENTIAL ENGINE --- if (!empty($att->clock_in) && !empty($att->clock_out)) { try { $in = \Carbon\Carbon::parse($dateString . ' ' . $att->clock_in); $out = \Carbon\Carbon::parse($dateString . ' ' . $att->clock_out); // Handle Midnight Crossover if ($out->lt($in)) { $out->addDay(); } // DOLE Windows: 12:00AM-6:00AM (Today) and 10:00PM-6:00AM (Tonight -> Tomorrow) $windows = [ [\Carbon\Carbon::parse($dateString)->startOfDay(), \Carbon\Carbon::parse($dateString)->setTime(6, 0)], [\Carbon\Carbon::parse($dateString)->setTime(22, 0), \Carbon\Carbon::parse($dateString)->addDay()->setTime(6, 0)] ]; foreach ($windows as $window) { $overlapStart = $in->copy()->max($window[0]); $overlapEnd = $out->copy()->min($window[1]); if ($overlapEnd->gt($overlapStart)) { $nightDiffHours += $overlapEnd->diffInMinutes($overlapStart) / 60; } } } catch (\Exception $e) { // Skip unparseable malformed logs mechanically } } // --------------------------------- } $currentDate->addDay(); } // Apply Manager Exemptions if ($employee->employee_id === 'SEB2018-2008T') { $lateHours = 0; // Exempt from Late/Tardiness } // 3. Compute Attendance Deductions $absenceDeduction = $absencesCount * $actualDailyRate; $lateDeduction = $lateHours * ($actualDailyRate / 8); // 4. Compute Statutory Contributions (2025/2026 Tables on FULL GROSS then divided by 2) // SSS EE Share (4.5% of MSC up to P30k) $msc = min(floor($grossMonthly / 500) * 500, 30000); // Check exactly for native DB schema Overrides explicitly! // Wait: The screenshot shows standard monthly values but $salaryData defines them! $sssMonthly = $salaryData->sss_fixed ?? ($msc * 0.045); $philhealthMonthly = $salaryData->philhealth_fixed ?? ($grossMonthly * 0.025); if ($salaryData && is_null($salaryData->philhealth_fixed) && $grossMonthly <= 10000) $philhealthMonthly = 250; if ($salaryData && is_null($salaryData->philhealth_fixed) && $grossMonthly >= 100000) $philhealthMonthly = 2500; $pagibigMonthly = $salaryData->pagibig_fixed ?? (100 * $divisor); $sssCutoff = $sssMonthly / $divisor; $phCutoff = $philhealthMonthly / $divisor; $pagibigCutoff = $pagibigMonthly / $divisor; $tax = 0.00; // Zero tax shown in screenshot simulation // Night Differential Money Calculation (10% DOLE Premium) $hourlyRate = $actualDailyRate / 8; $nightDiffAmount = $nightDiffHours * ($hourlyRate * 0.10); $earnings = [ ['name' => 'Basic Salary', 'amount' => number_format($periodBasicSalary, 2, '.', '')], ['name' => 'Overtime (Regular)', 'amount' => number_format(0, 2, '.', '')], ['name' => 'Night Differential', 'amount' => number_format($nightDiffAmount, 2, '.', '')], ['name' => 'Holiday Pay', 'amount' => number_format(0, 2, '.', '')], ]; $deductions = []; if ($lateDeduction > 0) { $deductions[] = ['name' => 'Late Deduction', 'amount' => number_format($lateDeduction, 2, '.', '')]; } $deductions[] = ['name' => 'Absence Deduction', 'amount' => number_format($absenceDeduction, 2, '.', '')]; $deductions[] = ['name' => 'SSS Contribution (EE)', 'amount' => number_format($sssCutoff, 2, '.', '')]; $deductions[] = ['name' => 'PhilHealth Contribution (EE)', 'amount' => number_format($phCutoff, 2, '.', '')]; $deductions[] = ['name' => 'Pag-IBIG Contribution (EE)', 'amount' => number_format($pagibigCutoff, 2, '.', '')]; $deductions[] = ['name' => 'Withholding Tax', 'amount' => number_format($tax, 2, '.', '')]; $totalEarnings = $periodBasicSalary + $nightDiffAmount; $totalDeductions = $absenceDeduction + $lateDeduction + $sssCutoff + $phCutoff + $pagibigCutoff + $tax; $netPay = $totalEarnings - $totalDeductions; return [ 'gross_monthly' => $grossMonthly, 'pay_type' => $isSemiMonthly ? 'Semi-Monthly' : 'Monthly', 'daily_rate' => $actualDailyRate, 'basic_salary' => $periodBasicSalary, 'earnings' => $earnings, 'deductions' => $deductions, 'total_earnings' => $totalEarnings, 'total_deductions' => $totalDeductions, 'net_pay' => $netPay, 'summary' => [ 'days_worked' => $daysWorked, 'expected_hours' => number_format($expectedHours, 2), 'rendered_hours' => number_format($renderedHours, 2), 'night_diff' => number_format($nightDiffHours, 2), 'absences' => $absencesCount, 'absence_amount' => $absenceDeduction, 'leaves' => 0, 'reg_ot' => 0, 'night_diff' => 0, 'holiday' => 0, ] ]; } }