# 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) ```php 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: ```php } 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) ```php // 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) ```php $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: ```php 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` 1. **Line ~137-149**: Fix leave detection for absent-status records 2. **Line ~220-221**: Remove dayMultiplier from ND formula 3. **Line ~177-188**: Prorate holiday premium for daily employees 4. **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