5.5 KiB
Fix Payroll Parallel 4 — 3 Bugs
Bug 1: LWOP Not Captured When Attendance Status is "Absent"
Problem
Ana Regina has 2 LWOP (Apr 27 + May 9). May 9 attendance record has status=absent instead of on_leave. The PayrollService only checks $att->status === 'on_leave' to look up leave type, so it misses the leave application and counts it as a generic absence instead.
Impact: The payslip shows 1 LWOP instead of 2. The deduction amount may be the same (both are unpaid), but the breakdown is wrong.
Current Code (PayrollService.php ~line 137)
if ($att && $att->status === 'on_leave' && $att->leaveApplication) {
$isPaidLeave = $att->leaveApplication->leaveType->is_paid ?? false;
} else {
// Fallback: check leave applications table
foreach ($leaveApps as $leave) { ... }
}
Fix
The fallback already checks $leaveApps, but it only runs when the attendance status is NOT on_leave. The issue is that when status=absent AND a leave app exists, the system counts it as an absence (line ~247) instead of recognizing it as a leave.
Change: In the "Did NOT work" block, before counting absences, also check if a leave application exists for that date:
} else {
// Check if there's an approved leave for this date (even if status is 'absent')
$leaveForDate = null;
foreach ($leaveApps as $leave) {
if ($currentDate->betweenIncluded($leave->start_date, $leave->end_date)) {
$leaveForDate = $leave;
break;
}
}
if ($leaveForDate) {
$isLeaveForDatePaid = $leaveForDate->leaveType->is_paid ?? false;
if ($isLeaveForDatePaid) {
$paidLeavesAmount += $actualDailyRate;
$daysWorked++;
$paidLeavesCount++;
}
// If unpaid (LWOP), don't count as absence — it's tracked as LWOP
// The absence deduction still applies since LWOP is unpaid
} else if (!$isRestDay && !$isSpecialHoliday) {
$absencesCount++;
}
}
Important
This fix ensures LWOP is properly recognized even when the attendance record says "absent".
Bug 2: Night Differential Too High for Rank & File
Problem
Client says ND is "too high" for daily wage employees. Current formula compounds ND with the day multiplier:
ND = ND_hours × hourlyRate × dayMultiplier × 10%
On a regular holiday (multiplier 2.0), this doubles the ND premium. Client expects flat 10%.
Example (Dinh Chavez, ₱765/day)
- Hourly: ₱765 / 8 = ₱95.625
- Current ND/hr on holiday: ₱95.625 × 2.0 × 10% = ₱19.13 (too high)
- Expected ND/hr: ₱95.625 × 10% = ₱9.56 (flat)
Fix (PayrollService.php ~line 220)
// BEFORE:
$ndRegAmount = $ndRegHours * ($hourlyRate * $dayMultiplier * 0.10);
$ndOtAmount = $ndOtHours * ($hourlyRate * $dayMultiplier * $otRateMultiplier * 0.10);
// AFTER:
$ndRegAmount = $ndRegHours * ($hourlyRate * 0.10);
$ndOtAmount = $ndOtHours * ($hourlyRate * $otRateMultiplier * 0.10);
Remove $dayMultiplier from ND calculation entirely. ND is a flat 10% of the base hourly rate.
Warning
Verify with client: DOLE technically compounds ND with day premium. But client explicitly says it's too high, so we follow their business rule.
Bug 3: Holiday Pay Should Be Prorated by Hours Worked (Rank & File)
Problem
Daily employees who worked partial hours on a holiday get the full day premium. Example:
- Dinh worked 7.28h on May 1 (not full 8h shift)
- System gives premium = ₱765 × 1.0 = ₱765 (full day)
- Client expects: premium = (7.28/8) × ₱765 = ₱696.15 (prorated)
Current Code (PayrollService.php ~line 177-186)
$earnedDaily = $actualDailyRate * $dayMultiplier; // Full daily rate
$earnedBasicSalary += $actualDailyRate; // Full basic
$premium = ($earnedDaily - $actualDailyRate); // Full premium
Fix
For daily wage employees, prorate based on actual hours worked vs standard shift hours:
if ($didWork) {
$standardHours = ($att->shift && $att->shift->working_hours > 0)
? (float)$att->shift->working_hours : 8.0;
$hoursWorked = min((float)$att->total_hours, $standardHours); // Cap at standard
$hoursRatio = $isDailyWage ? ($hoursWorked / $standardHours) : 1.0;
$earnedDaily = $actualDailyRate * $dayMultiplier * $hoursRatio;
if ($isDailyWage) {
$earnedBasicSalary += $actualDailyRate * $hoursRatio;
} else {
$earnedBasicSalary += $actualDailyRate;
}
if ($dayMultiplier > 1.0) {
$premium = ($earnedDaily - ($actualDailyRate * $hoursRatio));
if ($isRegularHoliday || $isSpecialHoliday) {
$holidayPay += $premium;
}
}
}
Important
This only applies to daily wage employees. Monthly employees always get the full daily rate regardless of hours (they're salaried).
Files to Modify
[MODIFY] app/Services/PayrollService.php
- Line ~137-149: Fix leave detection for absent-status records
- Line ~220-221: Remove dayMultiplier from ND formula
- Line ~177-188: Prorate holiday premium for daily employees
- Line ~227-249: Fix LWOP/absence detection in "Did NOT work" block
Verification
- Ana Regina: 2 LWOP should show in payslip
- Dinh Chavez: ND amount should be lower (flat 10%)
- Dinh Chavez: Holiday premium prorated to 7.28/8 of daily rate
- Monthly employees (Paul, Mart): unchanged behavior
- Run payroll for full cut-off and compare with client's manual computation