# Payroll Loan Deduction: Pre-implementation Discovery Completed: 2026-09-25 Scope: read-only review of `solidmarkmaster_db` and active application callers. ## 1. Confirmed data architecture | Table | Confirmed identity / use | Current local state | | --- | --- | --- | | `loans` | `loan_id`, company, brand, employee; stores actual balance and status | 42 rows | | `loan_journal_entry` | Immutable posted/reversed loan transactions | 39 rows; repaired to use auto-increment primary key `journal_id` | | `payroll_live_cache` | Payroll preview by employee, tenant, and period | 90 rows; unique key on `(employee_id, company_id, brand_id, date_from, date_until)` | | `payroll_finalized` | Immutable finalized payroll snapshots | 92 rows | | `payroll_finalize_batches` | Finalized cutoff/batch header | 11 rows | The authoritative editable payroll-preview identity is: `company_id + brand_id + employee_id + date_from + date_until -> payroll_live_cache.live_id` `payroll_loan_deduction_drafts.source_live_id` references that resolved preview identity. The draft table also has a unique key on `(company_id, brand_id, source_live_id, loan_id)`, preventing duplicate editable drafts for the same loan and preview row. ## 2. Active callers and legacy callers | Endpoint / route | Current caller | Finding | | --- | --- | --- | | `loan_api/update_loan_summary` | `PayrollLaonEditorAPIs.js`, `payroll_loan_summaryAPI.js` | Active draft save/update route. | | `loan_api/create_loan_summary` | No caller found under `frontend/src` | Loan creation is currently routed through `loan_api/create_loan` instead. | | `payroll/save_loan_deduction` | `payrollapi.jsx` | Legacy caller remains, but backend returns `410 Gone`. Remove or replace the frontend function. | | `payroll/update_loan_deduction_applied` | `payrollapi.jsx` | Legacy caller remains; backend returns `410 Gone`. Remove or replace the frontend function. | | `payroll/sync_loan_deductions` | `payrollapi.jsx` | Legacy caller remains; it must not bypass the draft contract. | | `payroll/finalize_payroll_from_live_cache` | `payrollapi.jsx` | Active finalization route; now rejects partial employee selection. | ## 3. Legacy payroll-origin journal report The read-only report found 30 posted payroll-origin journal entries without a payroll batch number: | Classification | Count | Required treatment | | --- | ---: | --- | | Matches a finalized payroll snapshot | 1 | Reconcile/link to its historical batch only after manual review. | | No matching finalized payroll snapshot | 29 | Treat as legacy unfinalized; preserve and use the guarded undo workflow where appropriate. | No legacy records were deleted, changed, or auto-converted. ## 4. Authorization findings | Operation | Current enforcement | | --- | --- | | Payroll finalization and other payroll-folder mutations | Authenticated `admin_portal` user, `can_edit_payroll_date` permission (or system admin), brand access, and target-scope validation. | | Payroll loan-specific routes under `backend/payroll` | Same payroll guard, using the `loan` permission. | | `loan_api/update_loan_summary`, `create_loan_summary`, and legacy undo | Require an authenticated `admin_portal` user through `require_admin_state()`. | ### Security remediation completed The draft save route is under `backend/loan_api`, so it did not initially receive the centralized payroll request guard automatically. On 2026-09-25, equivalent `loan` permission, brand-access, and target-scope enforcement was added to draft create/edit/skip and legacy-undo mutations. ## 5. Discovery conclusion The draft data model and preview identity are stable enough to continue implementation. The remaining immediate work is to retire the frontend legacy API callers and align loan-draft authorization with the existing payroll mutation guard before conducting a committed full-cutoff finalization test.