# Payroll Computation and Posting Change Log Date: September 14, 2026 System: Bigbys HRIS Scope: payroll live computation, finalization, accounting posting, gross/net presentation, holidays, overtime, incentives, retro adjustments, and tax classification ## 1. Purpose This document records the payroll review, the problems found, the corrections implemented, the database changes, validation performed, deployment instructions, and remaining work. Leave-related findings were intentionally excluded from implementation at the user's request. They remain documented as open work but no leave calculation code was changed as part of this correction. ## 2. Payroll Process Reviewed The review covered this flow: 1. Attendance and schedule data supply payable-day and deduction inputs. 2. Approved overtime requests supply payable OT minutes, rates, and amounts. 3. Holiday rules add employee-specific holiday credits and premiums. 4. Allowances, incentives, retro adjustments, contributions, loans, attendance deductions, and withholding tax are combined in the live payroll cache. 5. The live payroll row must be recalculated until `needs_recalc = 0`. 6. Finalization copies a valid live snapshot into finalized payroll. 7. Accounting posting creates a journal for one finalized payroll batch. 8. A repost reverses the active journal and creates a replacement while retaining the audit trail. ## 3. Findings and Current Status | Tracker ID | Area | Finding | BQFC status | | --- | --- | --- | --- | | BUG-042 | Finalization | Stale live rows could be finalized by bypassing `needs_recalc`. | Done | | BUG-043 | Holiday | Holidays were treated as blanket full paid days without enough employee-specific eligibility and work checks. | Done | | BUG-044 | Overtime | Ordinary OT could use `1.00`; rest-day and holiday combinations were unsafe. | Done | | BUG-045 | Overtime | Full start/end intervals could be paid without approved-hour caps or mapped break deductions. | Done | | BUG-046 | Retro | Retro linkage used a reusable live-cache ID and could be reapplied in another cutoff. | Done | | BUG-047 | Retro tax | Retro marked taxable was not consistently included in taxable compensation. | Done | | BUG-048 | Gross/net | Visible earnings did not consistently reconcile with incentive, retro, deductions, and net pay. | Done | | BUG-049 | Retro display | Negative retro corrections affected net pay but could be hidden from the breakdown. | Done | | BUG-050 | Incentive | Replacing an incentive could add the full amount repeatedly to legacy total salary. | Done | | BUG-051 | Arrears | Negative net converted to arrears was not clearly shown in the payroll breakdown. | Done | | BUG-052 | Leave | Partial-cutoff leave can use the full leave duration. | On-going; excluded | | BUG-053 | Leave | Archived approved leave may still affect payroll. | On-going; excluded | | BUG-054 | Leave | Different leave tables can produce inconsistent pay and absence classification. | On-going; excluded | | BUG-055 | Leave/holiday | Leave and holiday credit can overlap on one date. | On-going; excluded | | BUG-056 | Incentive history | Incentive edits did not have an immutable change history. | Done | | BUG-057 | Retro history | Pending deletion and reusable live linkage weakened the audit trail. | Done | | BUG-058 | Accounting posting | Date-range posting could mix batches; authorization and brand checks were incomplete. | Done | | BUG-059 | Accounting repost | Reposting permanently deleted the original journal and lines. | Done | | BUG-060 | Tax classification | Allowance, incentive, and retro taxability was not explicit and consistent. | Done | The tracker is maintained in the `Master Tracker` tab: https://docs.google.com/spreadsheets/d/1Bc15i6s4NSrX0W0CNX6aQVfLyOT6uPUBJ8hYcGB1WTg/edit ## 4. Changes Implemented ### 4.1 Live payroll and finalization - Removed the finalization bypass for stale live rows. - Finalization now blocks any row with `needs_recalc = 1`. - Net synchronization and arrears handling no longer silently clear the stale marker. - Finalization remains based on the recalculated live snapshot, preserving gross, OT, holiday, incentive, retro, tax, contribution, loan, and deduction results. - Tax application and tax override writes are restricted to the authenticated company and brand. Primary files: - `backend/payroll/finalize_payroll_from_live_cache.php` - `backend/payroll/payroll_recalculate_helper.php` - `backend/payroll/apply_income_tax_mvp.php` - `backend/payroll/save_income_tax_override_mvp.php` - `frontend/src/payrollPage/payrollComponents/FinalizePayrollButton.jsx` ### 4.2 Gross, earnings, deductions, and net pay - Kept regular earnings and OT earnings as separate components. - OT is paid as a peso amount and is not paid again through legacy OT day credit. - Total earnings now presents income adjustments consistently, including incentive and retro. - Negative retro values are shown instead of being hidden. - Attendance penalties remain deductions and are not subtracted twice from the displayed earnings total. - Payroll arrears created from a negative net result are shown as an offset in the breakdown. - Payroll summaries, print summaries, and payroll list calculations use the corrected component order. Primary files: - `backend/payroll/payroll_recalculate_helper.php` - `backend/payroll/payroll.php` - `frontend/src/payrollPage/payrollComponents/PayrollSummary.jsx` - `frontend/src/payrollPage/payrollComponents/PayrollSummaryPrint.jsx` - `frontend/src/payrollPage/payrollComponents/payrollList.jsx` ### 4.3 Holiday computation - Holiday credit is calculated for each employee instead of applying one blanket count to everyone. - The calculation checks the employee's schedule and attendance for the holiday date. - An unworked eligible regular holiday can receive the regular holiday credit. - Worked holidays add only the premium portion above attendance credit, avoiding a duplicate normal-day credit. - Special/custom holiday work uses its configured multiplier and does not automatically become a full paid day when unworked. - Recurring and extended holidays retain their date-range support. Primary file: - `backend/payroll/payroll_recalculate_helper.php` ### 4.4 Overtime computation A shared OT snapshot calculation now controls request storage and payroll aggregation. Implemented rate behavior: | OT condition | Minimum multiplier used when no higher explicit holiday OT rate exists | | --- | ---: | | Ordinary working day | 1.25 | | Rest day | 1.69 | | Special non-working holiday | 1.69 | | Special holiday on rest day | 1.95 | | Regular holiday | 2.60 | | Regular holiday on rest day | 3.38 | Additional rules: - An explicit configured holiday OT multiplier greater than `1.00` is respected. - If that holiday also falls on the employee's rest day, the rest-day OT factor is applied to the explicit holiday rate. - When holiday multiplier application is disabled, the calculation falls back to the applicable ordinary/rest-day rate. - Payable OT minutes are capped to the requested/approved hours. - Fixed mapped unpaid breaks overlapping the OT interval are deducted. - Flexible mapped breaks are deducted only to the extent the OT interval overlaps the scheduled shift. - Overnight OT and overnight mapped breaks are supported. - The calculation resolves raw interval minutes, break minutes, approved minutes, payable minutes, actual hours, multiplier, hourly rate, and amount. Persisted request snapshots include the payable values and an amount-formula audit string containing the raw and deducted-break minutes. - Payroll recalculation refreshes the OT request snapshot, correcting legacy rows that contain stale `1.00` multipliers or stale amounts. - Older records that stored day-credit fractions in `hours_requested` are detected and retained compatibly. - Admin-created OT now stores approved clock hours consistently instead of storing an OT day-credit fraction in the hours field. Primary files: - `backend/overtime/overtime_pay_helper.php` - `backend/overtime/admin_add_overtime.php` - `backend/payroll/payroll_recalculate_helper.php` - `backend/tests/overtime_pay_math_test.php` The rates follow the Philippine overtime computation structure described by DOLE and the Labor Code: - https://nwpc.dole.gov.ph/wp-content/uploads/2024/11/Workers-Statutory-Monetary-Benefits-Handbook-2024-Edition.pdf - https://lawphil.net/statutes/presdecs/pd1975/pd_850_1975.html ### 4.5 Incentive corrections and history - Incentive replacement updates legacy total salary using only the difference between the old and new incentive, preventing cumulative inflation. - Incentive amount, remarks, and taxability changes mark the live row for recalculation. - A change reason is required in the payroll UI. - Each material incentive change records old/new amount, old/new remarks, taxability metadata, cutoff, employee, actor, company, and brand. - The payroll UI displays the combined incentive and retro history. Primary files: - `backend/payroll/update_payroll.php` - `backend/payroll/payroll_adjustment_history_helper.php` - `backend/payroll/get_payroll_adjustment_history.php` - `frontend/src/payrollPage/payrollComponents/PayrollSummary.jsx` ### 4.6 Retro corrections and history - Retro records are linked to their applied payroll period using `applied_period_from` and `applied_period_until`. - Current-cutoff retro totals are rebuilt from matching applied records, reducing carry-forward and reapplication risk. - Retro can be positive or negative, and both are displayed in the earnings breakdown. - Retro cancellation is now a soft cancellation; the source row is retained. - Permanent/force deletion was removed from the payroll UI and cancellation endpoint. - Cancellation requires a reason and records the actor and timestamp. - Creating and cancelling retro entries creates immutable audit-history entries. - Retro taxability is stored through `tax_in_current_payroll` and used by withholding computation. Primary files: - `backend/payroll/save_retro_adjustment.php` - `backend/payroll/cancel_retro.php` - `backend/payroll/update_payroll.php` - `backend/payroll/payroll_adjustment_history_helper.php` - `backend/payroll/get_payroll_adjustment_history.php` - `frontend/src/payrollPage/payrollComponents/PayrollSummary.jsx` ### 4.7 Tax classification - Allowances now have an explicit `is_taxable` flag. - Incentives now have an explicit `incentive_is_taxable` flag across legacy, live, and finalized payroll tables. - Retro uses its existing `tax_in_current_payroll` classification. - Existing allowances and incentives default to taxable for backward compatibility. - Withholding-tax computation totals only taxable allowance, incentive, and retro components. - The payroll and allowance interfaces expose taxable/non-taxable controls and labels. - Allowance create, read, and update endpoints now enforce payroll authorization and employee/company/brand scope. Primary files: - `backend/allowance/create_allowance.php` - `backend/allowance/list_allowance.php` - `backend/allowance/update_allowance.php` - `backend/payroll/income_tax_helper.php` - `backend/payroll/update_payroll.php` - `backend/payroll/payroll.php` - `frontend/src/components/allowance/allowance_comp/AllowanceModal.jsx` - `frontend/src/payrollPage/payrollComponents/PayrollSummary.jsx` ### 4.8 Accounting posting and reposting - Posting now requires an authenticated administrator with payroll-finalization permission. - Posting requires one explicit `payroll_batch_no`; date-range-only posting is disabled to prevent mixed batches. - Finalized rows are checked against the administrator's brand scope. - Duplicate active posting for the same batch is blocked. - Replacement journal source keys receive a version suffix after prior journals exist. - Reposting requires a reason of at least five characters. - Reposting no longer deletes the original journal or its lines. - A new equal-and-opposite `PAYROLL_REVERSAL` journal is created by swapping debit and credit values from the original lines. - The original journal is marked `reversed` with actor, time, and reason. - Finalized accounting flags are reset only after a valid reversal, allowing the replacement posting. - The frontend explains reversal behavior and collects the repost reason. Primary files: - `backend/accounting/post_payroll_journal.php` - `backend/accounting/repost_payroll_journal.php` - `frontend/src/components/accounting/accountingComponents/PostPayrollJournalButton.jsx` ### 4.9 Attendance input consistency These changes improve the payroll inputs without changing leave logic: - The day-credit preview endpoint is read-only, authenticated, employee-scoped, and schedule-aware. - Server schedule and late-deduction rules remain authoritative. - The DTR page preloads schedule/late rules and provides an immediate local preview while waiting for the authoritative saved response. - The local attendance calculator now includes credit-based late deductions in displayed credited/deducted days. Primary files: - `backend/attendance/calculate_day_credit.php` - `backend/attendance/get_attendance_rules.php` - `frontend/src/components/DTRattenance/DTRComponent/DTR_record.jsx` - `frontend/src/utils/attendanceCalculator.js` ## 5. Database Migrations Run migrations in this order after taking a database backup. ### Migration 1: adjustment history Files: - `backend/migrations/20260914_001_payroll_adjustment_history.sql` - `backend/migrations/20260914_001_payroll_adjustment_history.php` It creates `payroll_adjustment_history`, adds cutoff and cancellation fields to `retro_adjustments`, creates useful indexes, and installs triggers that reject update/delete operations on history rows. ### Migration 2: tax classification Files: - `backend/migrations/20260914_002_payroll_tax_classification.sql` - `backend/migrations/20260914_002_payroll_tax_classification.php` It adds: - `employee_allowance.is_taxable` - `payroll.incentive_is_taxable` - `payroll_live_cache.incentive_is_taxable` - `payroll_finalized.incentive_is_taxable` The SQL uses existence checks so it can be rerun safely when only some columns are present. The PHP migration files provide the project migration-runner equivalents. No additional migration is required for the OT correction. ## 6. Security and Audit Improvements - Sensitive payroll, tax, allowance, posting, reposting, and cancellation endpoints use authenticated request bootstraps. - Company, brand, and employee scope checks were added to affected reads and writes. - Actor names come from the authenticated session instead of trusting client-supplied usernames. - Incentive and retro history is immutable at the database level. - Retro cancellation and accounting repost require human-readable reasons. - Original accounting journal history is retained through reversal rather than deletion. ## 7. Verification Performed Completed checks: - PHP syntax checks passed for all changed and newly added PHP files. - `git diff --check` passed; only Windows LF-to-CRLF notices were reported. - Direct OT math tests passed for: - ordinary OT; - rest-day OT; - regular-holiday OT; - regular-holiday/rest-day OT; - special-holiday OT; - special-holiday/rest-day OT; - approved-hour caps; - fixed meal breaks; - overnight meal breaks; and - historical day-credit compatibility. - Focused payroll integrity tests passed: 5 of 5. - The full frontend test run passed 47 of 50 tests. The three full-suite failures are outside the OT correction: 1. `full split-shift attendance earns one day` expects the older result shape and does not yet include the newly returned `deducted_days` and `late_deduction_value` fields. 2. `application modules do not bypass the shared Axios client` reports existing direct Axios imports across multiple modules. 3. `application modules do not call native fetch directly` reports existing direct fetch usage across multiple modules. A production frontend build passed earlier in the payroll correction process. It was not rerun after the final attendance-preview wiring, so a final production build remains a required pre-deployment check. ## 8. Required Manual Acceptance Test Use a staging or test payroll cutoff and verify at least one employee for each applicable scenario: 1. Ordinary-day OT at `1.25`. 2. Rest-day OT at `1.69`. 3. Regular-holiday OT at `2.60`. 4. Regular-holiday/rest-day OT at `3.38`. 5. Special-holiday OT at `1.69`. 6. Special-holiday/rest-day OT at `1.95`. 7. OT where the start/end interval exceeds approved hours. 8. OT spanning a mapped meal break. 9. Overnight OT spanning a mapped break. 10. Positive and negative retro adjustments. 11. Taxable and non-taxable allowance, incentive, and retro entries. 12. Incentive replacement to confirm only the difference affects legacy totals. 13. A stale live payroll row to confirm finalization is blocked. 14. Successful finalization after recalculation. 15. Initial accounting posting for one payroll batch. 16. Accounting repost with a reason, confirming original journal, reversal journal, and replacement journal all remain traceable. For every case, reconcile: `regular earnings + OT + allowances + incentive + retro + other income - attendance deductions - contributions - loans - tax - arrears offset = net pay` ## 9. Deployment Procedure 1. Back up the production database. 2. Confirm migrations `20260914_001` and `20260914_002` have been applied. 3. Confirm the accounting schema contains `reversal_of_journal_id`, `reversed_at`, `reversed_by`, and `reverse_reason` from the existing accounting schema alignment. 4. Deploy the backend and frontend changes to staging. 5. Run the final frontend production build. 6. Run PHP syntax checks and focused payroll tests in the deployment environment. 7. Recalculate an open payroll cutoff so existing live rows and OT snapshots receive the corrected computation. 8. Complete the manual acceptance test above. 9. Compare payroll totals before and after recalculation and obtain payroll-owner approval. 10. Deploy to production. 11. Recalculate only open/unfinalized cutoffs. Do not rewrite finalized historical payroll directly; use an approved retro correction when a finalized payroll requires adjustment. 12. Post accounting only by payroll batch number. 13. Monitor application and database logs during the first live payroll cycle. ## 10. Known Limitations and Remaining Work - Leave findings BUG-052 through BUG-055 remain open and were not fixed. - OT break deduction depends on mapped schedule breaks. An unconfigured break cannot be inferred safely. - Historical `hours_requested` values used two meanings. Compatibility detection protects legacy day-credit rows, but unusual manually edited historical values should be reviewed during staging reconciliation. - Higher explicit holiday OT rates can be configured through the holiday OT multiplier; the implementation will not intentionally reduce an explicit holiday rate above the minimum. - Existing finalized payroll is not automatically rewritten. This protects audit integrity. - The three unrelated frontend test failures listed above remain open. - The final frontend production build still needs to be rerun. ## 11. Suggested Commit Commit name: `fix(payroll): harden computation, history, overtime, and posting` Commit description: `Correct holiday and overtime computation, reconcile gross and net payroll components, preserve incentive and retro history, add taxable classifications, block stale finalization, enforce payroll and tenant authorization, and replace destructive accounting reposting with audited reversals. Leave computation remains unchanged.` ## 12. Changed File Inventory ### Accounting - `backend/accounting/post_payroll_journal.php` - `backend/accounting/repost_payroll_journal.php` - `frontend/src/components/accounting/accountingComponents/PostPayrollJournalButton.jsx` ### Allowances and tax - `backend/allowance/create_allowance.php` - `backend/allowance/list_allowance.php` - `backend/allowance/update_allowance.php` - `backend/payroll/apply_income_tax_mvp.php` - `backend/payroll/income_tax_helper.php` - `backend/payroll/save_income_tax_override_mvp.php` - `frontend/src/components/allowance/allowance_comp/AllowanceModal.jsx` ### Attendance inputs - `backend/attendance/calculate_day_credit.php` - `backend/attendance/get_attendance_rules.php` - `frontend/src/components/DTRattenance/DTRComponent/DTR_record.jsx` - `frontend/src/utils/attendanceCalculator.js` ### Overtime - `backend/overtime/admin_add_overtime.php` - `backend/overtime/overtime_pay_helper.php` - `backend/tests/overtime_pay_math_test.php` ### Payroll, incentive, retro, and presentation - `backend/payroll/cancel_retro.php` - `backend/payroll/finalize_payroll_from_live_cache.php` - `backend/payroll/get_payroll_adjustment_history.php` - `backend/payroll/payroll.php` - `backend/payroll/payroll_adjustment_history_helper.php` - `backend/payroll/payroll_recalculate_helper.php` - `backend/payroll/save_retro_adjustment.php` - `backend/payroll/update_payroll.php` - `frontend/src/payrollPage/payrollComponents/FinalizePayrollButton.jsx` - `frontend/src/payrollPage/payrollComponents/PayrollSummary.jsx` - `frontend/src/payrollPage/payrollComponents/PayrollSummaryPrint.jsx` - `frontend/src/payrollPage/payrollComponents/payrollList.jsx` ### Migrations and automated checks - `backend/migrations/20260914_001_payroll_adjustment_history.php` - `backend/migrations/20260914_001_payroll_adjustment_history.sql` - `backend/migrations/20260914_002_payroll_tax_classification.php` - `backend/migrations/20260914_002_payroll_tax_classification.sql` - `frontend/tests/payroll-adjustment-history.test.js` - `frontend/tests/payroll-computation-integrity.test.js`