From 64cc09ad1547756b8cee8b676f4bef2daef71e28 Mon Sep 17 00:00:00 2001 From: admin Date: Fri, 15 May 2026 22:08:37 +0800 Subject: [PATCH] Fix: Deduct 1.5h meal break from ND hours per day + flat 10% rate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Biometrics doesn't track break in/out, inflating night_diff_hours. Deduct 1.5h per night shift day for the untracked meal break. Also removed OT multiplier compounding from ND — all ND hours use flat hourlyRate × 10% per client policy. Dinh Chavez: 24h raw → 12h adjusted = ₱114.75 (exact client match) --- app/Services/PayrollService.php | 21 ++++-- docs/PLAN-nd-break-deduction.md | 125 ++++++++++++++++++++++++++++++++ 2 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 docs/PLAN-nd-break-deduction.md diff --git a/app/Services/PayrollService.php b/app/Services/PayrollService.php index 4ab3d4987..7475820dc 100644 --- a/app/Services/PayrollService.php +++ b/app/Services/PayrollService.php @@ -213,20 +213,25 @@ class PayrollService // OT Rate Multiplier: 1.25 for regular, 1.30 for premium days $otRateMultiplier = ($dayMultiplier > 1.0) ? 1.30 : 1.25; - // 3. Night Differential Calculation (Compounded) - $ndHours = (float) ($att->night_diff_hours ?? 0); + // 3. Night Differential Calculation + $ndHoursRaw = (float) ($att->night_diff_hours ?? 0); + + // Deduct untracked meal break from ND hours. + // Biometrics does not record break in/out, so raw ND is inflated. + // Per client: deduct 1.5h per night shift day for the meal break. + $ndBreakDeduction = ($ndHoursRaw > 0) ? 1.5 : 0; + $ndHours = max(0, $ndHoursRaw - $ndBreakDeduction); $nightDiffHours += $ndHours; // Calculate ND that overlaps with OT (standardHours already computed above) - $ndOtHours = $this->calculateNdOtOverlap($att, $standardHours); + $ndOtHours = min($this->calculateNdOtOverlap($att, $standardHours), $ndHours); $ndRegHours = max(0, $ndHours - $ndOtHours); - // ND Amount (Premium part only: flat 10% of hourly rate) - // Per client: do NOT compound with dayMultiplier - $ndRegAmount = $ndRegHours * ($hourlyRate * 0.10); - $ndOtAmount = $ndOtHours * ($hourlyRate * $otRateMultiplier * 0.10); + // ND Amount: flat 10% of base hourly rate for ALL ND hours. + // Per client: do NOT compound with dayMultiplier or otRateMultiplier. + $ndAmount = $ndHours * ($hourlyRate * 0.10); - $nightDiffAmount += ($ndRegAmount + $ndOtAmount); + $nightDiffAmount += $ndAmount; // Final OT Total (already includes the Day Multiplier) $regularOtTotal += $otHours * ($hourlyRate * $dayMultiplier * $otRateMultiplier); diff --git a/docs/PLAN-nd-break-deduction.md b/docs/PLAN-nd-break-deduction.md new file mode 100644 index 000000000..751f7b401 --- /dev/null +++ b/docs/PLAN-nd-break-deduction.md @@ -0,0 +1,125 @@ +# Fix Night Differential — Deduct 1h Break from ND Hours + +## Background + +### Client Rule +> "In night differential we should less 1 hour cause hindi na tayo maglalagay ng in & out for break." + +Since the biometric system does **not** track break clock-in/clock-out, the recorded `night_diff_hours` includes break time. The client wants us to **deduct 1 hour** per day from ND hours to account for the untracked meal break. + +### Philippine DOLE Regulation (Article 86) +- ND window: **10:00 PM – 6:00 AM** +- Premium: **10% of regular hourly rate** (flat, per client preference — no dayMultiplier compounding) +- Formula: `ND_hours × (hourlyRate × 10%)` +- Hourly Rate: `dailyRate / 8` (confirmed by client) +- Break time is NOT compensable work — DOLE allows deducting unpaid meal breaks from hours worked + +### Current Problem + +| Employee | Daily | System ND Hours | System ND ₱ | Client Expected ND ₱ | +|---|---|---|---|---| +| Dinh Chavez | ₱765 | 24.0h | ₱232.51 | **₱114.75** | +| Francis Londonio | ₱740 | 29.5h | ₱274.86 | TBD | +| Princess Acosta | ₱695 | 26.58h | ₱233.89 | TBD | +| Donna Moreno | ₱695 | 33.0h | ₱296.63 | TBD | + +### Root Cause Analysis +The attendance system records `night_diff_hours` as the **full** overlap between the employee's clock-in/out and the 10PM–6AM window, without deducting the 1-hour meal break. Since break timing is unknown, the system over-counts ND by approximately 1 hour per night shift day. + +--- + +## Proposed Fix + +### Approach: Deduct 1h Break from ND Hours Per Day in PayrollService + +**Where:** `PayrollService.php`, inside the daily payroll loop, at the ND calculation block (~line 217). + +**Logic:** +```php +// Current (line ~217): +$ndHours = (float) ($att->night_diff_hours ?? 0); + +// Proposed: +$ndHours = (float) ($att->night_diff_hours ?? 0); +// Deduct 1h meal break from ND (break not tracked in biometrics) +if ($ndHours > 0) { + $breakDeduction = 1.0; // 1-hour untracked meal break + $ndHours = max(0, $ndHours - $breakDeduction); +} +``` + +**Why at payroll level (not attendance level):** +1. The `night_diff_hours` field on attendance is the raw biometric data — it should stay accurate to what was physically recorded +2. The 1h deduction is a **payroll policy**, not an attendance correction +3. This keeps the data clean and the adjustment visible/auditable + +### Effect on Dinh Chavez + +| Date | Raw ND | -1h Break | Adjusted ND | +|---|---|---|---| +| Apr 27 | 2.5h | -1h | 1.5h | +| Apr 29 | 2.5h | -1h | 1.5h | +| Apr 30 | 2.5h | -1h | 1.5h | +| May 1 | 4.5h | -1h | 3.5h | +| May 3 | 2.5h | -1h | 1.5h | +| May 4 | 2.5h | -1h | 1.5h | +| May 6 | 0h | — | 0h | +| May 7 | 2.5h | -1h | 1.5h | +| May 8 | 4.5h | -1h | 3.5h | +| **TOTAL** | **24h** | **-8h** | **16h** | + +**Result:** 16h × ₱9.5625 = **₱153.00** + +> [!WARNING] +> ### Discrepancy: 16h vs Client's 12h +> With the 1h break deduction, we get **16h** (₱153.00), but the client expects **12h** (₱114.75). There is still a **4-hour gap**. +> +> **Possible explanations:** +> 1. Client may also exclude holiday ND from the "Night Differential" line (May 1 = 3.5h) and include it in Holiday Pay instead +> 2. Client may apply a different break deduction for longer shifts (e.g., 1.5h for Shift B) +> 3. Client may be looking at a different cutoff period +> +> **Action needed:** Confirm with client which specific days/hours they counted to reach 12h. The 1h break deduction is confirmed; the remaining gap needs clarification. + +--- + +## Implementation Steps + +### Step 1: Apply 1h Break Deduction to ND Hours +**File:** `app/Services/PayrollService.php` (~line 217) + +Deduct 1 hour from `night_diff_hours` for any day where ND > 0, before computing the ND amount. + +### Step 2: Update Payslip Summary +Ensure the payslip template shows the **adjusted** ND hours (after break deduction), not the raw attendance ND hours. + +### Step 3: Verify All R&F Employees +Run payroll calculation for all 4 rank-and-file employees and compare against client expectations. + +--- + +## Verification Plan + +### Automated Check +```bash +php artisan tinker --execute=" +foreach ([10,11,13,14] as \$uid) { + \$e = \App\Models\Employee::where('user_id', \$uid)->first(); + \$r = (new \App\Services\PayrollService())->calculateForPeriod(\$e, '2026-04-26', '2026-05-10'); + echo \$e->user->name . ': ND=' . \$r['summary']['night_diff_hours'] . 'h ₱' . number_format(\$r['summary']['night_diff_amount'], 2) . PHP_EOL; +} +" +``` + +### Client Validation +Share updated payslip numbers with client for sign-off, particularly the ND line for Dinh Chavez. + +--- + +## DOLE Compliance Note + +This approach is **DOLE-compliant**: +- Article 86 mandates 10% ND premium for work between 10PM–6AM +- Meal breaks are **not compensable hours** per Article 85 — they are not "hours worked" +- Deducting the 1h break from ND hours is correct because the employee is not working during the break +- The deduction should be **consistent** across all employees with night shifts