# Payroll Loan Deduction Draft and Correction Plan Status: Implemented in the application source on 2026-09-24; pending database migration deployment and acceptance testing. ## Problem Saving a deduction in Payroll Management can create a posted loan journal entry and reduce the loan balance before payroll is finalized. The journal then blocks deletion because its origin is `payroll`, while the existing payroll void process expects a finalized batch. This leaves an incorrectly entered deduction difficult to correct through the application. The relevant write path is `backend/loan_api/update_loan_summary.php`. It currently writes a posted credit and updates the loan balance during deduction saving. Journal origin identifies the source of a transaction; it should not, by itself, determine whether an unfinalized payroll deduction can be corrected. ## Intended workflow 1. Open Payroll Management and select the employee and payroll period. 2. Enter a loan deduction and save it as a draft. 3. Show the proposed deduction and projected remaining balance in the payroll preview. 4. Allow the user to edit or remove the draft and recalculate preview totals and net pay. 5. Before finalization, show a deduction review with employee, loan ID/description, amount, and balance before and after. Highlight deductions that pay off a loan. 6. Finalize payroll, post the journal, update the loan balance, and record payoff dates in one database transaction. 7. Correct finalized deductions through the existing payroll reversal workflow, retaining an audit trail. Saving or removing a draft must not alter the actual loan balance or payoff date. Draft removal must persist as an explicit skip/removal decision so recalculation cannot silently reintroduce a scheduled deduction. ## Scope and affected frontend files Paths below are relative to `frontend/src/payrollPage/`, unless stated otherwise. | File | Planned responsibility | | --- | --- | | `payrollpage/PayrollPage.jsx` | Starting page: refresh draft deductions and payroll totals after Save, Edit, or Remove; respect finalized mode. | | `payrollLoanSummary/PayrollLoanEditor.jsx` | Save deductions as drafts; add Remove; display actual and projected balances and validation messages. | | `payrollApi/PayrollLaonEditorAPIs.js` | Send draft save/remove requests and handle failures consistently. | | `payrollComponents/deductionTemplate.jsx` | Display draft and posted deductions in employee payroll details. | | `payrollComponents/LoanDetailsMultiApply.jsx` | Display draft deduction amounts and status accurately. | | `payrollComponents/payrollJournalEntry.jsx` | Prevent payroll preview actions from immediately posting repayments; expose the appropriate correction action. | | `payrollComponents/FinalizePayrollButton.jsx` | Present deduction review and payoff warnings before finalization. | | `payrollApi/payrollapi.jsx` | Handle finalization validation and posting results. | Additional integration review: `frontend/src/components/payroll/payrollModal.jsx` also uses the loan editor, and `frontend/src/payrollPage/payrollApi/payroll_loan_summaryAPI.js` calls the loan-summary update endpoint. Both must remain compatible with the revised contract. ## Affected backend files | File | Planned responsibility | | --- | --- | | `backend/loan_api/update_loan_summary.php` | Save draft deductions without reducing actual balances or closing loans. | | `backend/loan_api/get_loan_summary.php` | Return draft amounts, actual balances, projected balances, and editability. | | `backend/payroll/loan_live_cache_sync.php` | Calculate payroll preview totals from the draft source without treating drafts as posted repayments. | | `backend/payroll/finalize_payroll_from_live_cache.php` | Validate and post deductions exactly once; update balances and payoff dates within finalization. | | `backend/loan_journal_entry_api/create_journal_entry.php` | Guard the alternate payroll-entry path against bypassing the draft workflow. | | `backend/payroll/void_finalized_payroll_batch.php` | Verify reversal compatibility and restoration of balances and payoff-date state. | Review `backend/payroll/payroll.php`, `backend/payroll/payroll_recalculate_helper.php`, and other deduction-saving endpoints for calculations or writes that might overwrite drafts or post deductions early. Exact edits require tracing their active callers during implementation; copied or unused files should not be changed merely because they contain similar code. ## Proposed database architecture and relationships Unlike the earlier journal UI/payoff-date change, this proposal introduces durable draft storage. Table and column names below are proposed, not existing schema guarantees. | Entity | Keys and relevant fields | Purpose | | --- | --- | --- | | Existing `loans` | `loan_id`; company, brand, employee; balance, status, date_end, closed_at | Authoritative loan balance and lifecycle. | | Proposed `payroll_loan_deduction_drafts` | Primary key `draft_id`; loan_id; company_id; brand_id; employee_id; period dates; amount; state; version; created_by/updated_by; timestamps; finalized batch reference | Durable editable payroll deduction or explicit removal/skip decision. | | Existing `loan_journal_entry` | `journal_id`; loan_id; payroll_batch_no; entry_status; idempotency_key | Posted repayment and reversal history. | | Existing `payroll_live_cache` | Existing employee/tenant/period identifiers and preview totals | Derived preview, not the authoritative draft store. | | Existing finalized payroll/batch records | Existing batch identity and tenant scope | Determines finalization and reversal eligibility. | Relationships: - One loan can have many deduction drafts across payroll periods. - Each draft belongs to one employee, tenant, loan, and payroll period. - A finalized draft links to its posted journal entry and finalized payroll batch. - Payroll preview rows derive their deduction totals from drafts and explicit skip decisions. Required constraints: - Unique draft identity across company, brand, employee, loan, and payroll period. - Unique posting identity to prevent duplicate credits on retries. - Validate that the employee and loan belong to the active company and brand. - Match foreign-key types and indexes to the actual schema before writing the migration. - Use version checks or equivalent concurrency protection for competing draft edits. ## Proposed RESTful PHP API plan Routes below follow the application's current endpoint naming conventions. New routes are proposals. | Method | Route | Purpose | Payload / response | | --- | --- | --- | --- | | GET | `/api/loan_api/get_loan_summary` | Read loans with draft state and preview balances. | Employee/period filters; returns loan ID, actual balance, draft amount, projected balance, draft version, state, and editability. | | POST | `/api/loan_api/update_loan_summary` | Save or edit a draft. | Loan ID, employee ID, period, amount, expected version; returns saved draft and refreshed preview totals. | | POST | `/api/loan_api/remove_loan_deduction_draft` (new) | Remove a proposed deduction and retain an explicit skip decision. | Draft ID, expected version, optional reason; returns updated state and preview totals. | | POST | `/api/payroll/finalize_payroll_from_live_cache` | Finalize payroll and post accepted deductions atomically. | Existing finalization payload plus any required review/version token; returns batch identity and posting results. | | POST | `/api/loan_journal_entry_api/undo_unfinalized_payroll_deduction` (new) | Correct an existing prematurely posted deduction. | Journal ID and mandatory reason; returns correction identity, restored loan state, and refreshed payroll totals. | | POST | `/api/payroll/void_finalized_payroll_batch` | Correct finalized payroll through its audit-preserving reversal process. | Existing batch/reason contract; returns reversal results. | Tenant identifiers must come from authenticated server context. The server must independently validate permissions, finalization state, amounts, and record ownership. ## React data flow and state - Payroll Management owns the selected employee/period context and refreshes affected totals after successful mutations. - The loan editor loads the persisted draft rather than treating an unsaved local amount as a posted repayment. - API responses provide actual balance and projected balance separately; the UI labels both clearly. - Keep saving and loading states local where practical, preserving table filters and scroll position. - Cancel or ignore stale requests when the selected employee or period changes. - Disable duplicate submissions while saving, with server idempotency protecting retries. - After a successful save/remove, refresh the relevant employee preview instead of relying on stale props or subtracting amounts locally. - Switching into finalized mode removes draft-edit actions; the backend enforces the same restriction. ## Existing unfinalized posted deductions Provide a controlled undo action for records created by the earlier workflow. A missing payroll batch number alone is not sufficient evidence that an entry is unfinalized. The server must verify linked and matching finalized payroll records, lock the affected records, record the correction reason and actor, restore the loan balance where applicable, reconcile payoff dates, and rebuild payroll preview totals and cached loan details atomically. Preserve the original transaction and correction history where possible. Review legacy records before migration. Do not automatically convert or delete all payroll-origin journal entries with a null batch number. ## Validation and safeguards - Reject negative amounts and deductions above the available fixed-term loan balance. - Handle open-ended deductions separately because they have no fixed payoff balance. - Reject new deductions against closed or cancelled loans unless an explicit supported correction workflow applies. - Revalidate balances at finalization to account for other payments made after draft creation. - Prevent duplicate posting for the same loan and payroll period. - Serialize conflicting finalization/correction operations and reject stale draft versions. - Keep finalization atomic: any posting failure must roll back the related payroll and loan changes. - Recalculate deduction totals, net pay, and cached loan details together after corrections. ## Implementation order 1. Confirm active callers, existing database constraints, and the draft-storage migration. 2. Implement draft persistence, removal/skip state, and draft-aware reads. 3. Update payroll preview calculations and all active deduction-entry paths. 4. Update finalization to validate and post drafts atomically and idempotently. 5. Add controlled undo for legacy unfinalized posted entries. 6. Verify finalized-payroll reversals and loan lifecycle reconciliation. 7. Update UI labels/review actions, documentation, and regression coverage. ## Acceptance checklist - Saving a draft changes payroll preview totals but leaves the actual loan balance and payoff dates unchanged. - Editing/removing a draft updates net pay and remains correct after refresh/recalculation. - Removing a draft does not silently restore a scheduled deduction. - Reopening the editor displays the saved draft for the correct employee and period. - Finalization posts each deduction once and updates the corresponding loan correctly. - Retrying finalization does not duplicate repayments. - Concurrent balance changes are detected before posting. - A failed posting leaves neither partial finalized payroll nor partial loan updates. - Finalized entries cannot be edited as drafts. - Legacy unfinalized undo restores balances and preview totals with an audit reason. - Finalized reversal restores the appropriate balance and payoff-date state. - Tenant isolation holds for reads, saves, removals, finalization, and corrections. - Opening and closing the journal preserves the Loan Management table state. ## Related documentation See [Loan Journal and Payoff Dates](LOAN-JOURNAL-AND-PAYOFF-DATES.md) for the earlier journal UI and lifecycle changes. This document describes the proposed next phase; its acceptance checklist is not a claim of completed testing.