# HR & Payroll v0.39.3 — Pre-Final Correction Register

Project: HRPAY-BMD-20260911-01
Review date: 2026-09-16

Purpose: source/logic hardening before Local Django + PostgreSQL UAT. These corrections do **not** constitute Runtime PASS.

## Correction list

### PF-001 — Navigation (High)
- **Finding:** Configurable Django Admin path was linked with hard-coded /admin/, which could break when DJANGO_ADMIN_PATH changes.
- **Correction:** Replaced hard-coded admin links with Django named URL admin:index.
- **Files:** `backend/templates/base.html; backend/templates/core/administration_hub.html`
- **Verification:** Static URL audit / local browser smoke
- **Local UAT required:** Yes

### PF-002 — Attendance (Critical)
- **Finding:** Attendance CSV/correction path could mutate a finalized attendance month or finalized record.
- **Correction:** Validation and confirmation now block finalized months/records; centralized reconciliation rejects finalized-period mutation until controlled reopen.
- **Files:** `backend/apps/imports/services.py; backend/apps/attendance/services.py`
- **Verification:** Static service review / Local UAT reopen-finalize test
- **Local UAT required:** Yes

### PF-003 — Attendance Import (High)
- **Finding:** Attendance CSV time/status validation was too permissive and could accept invalid date/time/status combinations.
- **Correction:** Added HH:MM parsing, service-period checks, overnight-shift rules, work-date checks, status/punch consistency and confirm-time revalidation.
- **Files:** `backend/apps/imports/services.py; backend/apps/attendance/services.py`
- **Verification:** Compile + source review / import UAT
- **Local UAT required:** Yes

### PF-004 — Attendance / OT (High)
- **Finding:** Attendance CSV exposed OT Hours, creating a potential path around OT approval.
- **Correction:** Non-zero OT Hours through attendance CSV is rejected; template/sample no longer expose OT Hours. OT remains approval-workflow controlled.
- **Files:** `backend/apps/imports/services.py; ATTENDANCE-IMPORT-TEMPLATE.csv; SAMPLE-ATTENDANCE.csv`
- **Verification:** Static import contract / OT UAT
- **Local UAT required:** Yes

### PF-005 — Attendance / Leave (High)
- **Finding:** Manual Leave/Holiday/Weekly Off statuses could diverge from approved leave/calendar evidence; approved leave cancellation could leave stale attendance overlay.
- **Correction:** Leave now requires approved leave evidence; Holiday/Weekly Off must match calendar; cancellation rebuilds attendance from raw punches/policy; full-day approved leave clears roll-up punches while preserving PunchLog audit.
- **Files:** `backend/apps/attendance/services.py; backend/apps/leave/services.py`
- **Verification:** Static service review / leave-cancel UAT
- **Local UAT required:** Yes

### PF-006 — Attendance (High)
- **Finding:** Correction requests could be created for finalized months and timestamp/status rules were not uniformly enforced at service level.
- **Correction:** Blocked correction creation on finalized periods and applied authoritative attendance validation during create/resubmit/approve.
- **Files:** `backend/apps/attendance/services.py`
- **Verification:** Static service review / correction UAT
- **Local UAT required:** Yes

### PF-007 — CSV Migration (High)
- **Finding:** Duplicate keys, ambiguous/deactivated masters, invalid employee/salary/loan values and empty files could pass too far into staged import.
- **Correction:** Added duplicate-key detection, empty-file block, active/unique master validation, email/status/value checks, loan status/outstanding checks and confirm-time company/master revalidation.
- **Files:** `backend/apps/imports/services.py`
- **Verification:** Static import review / migration UAT
- **Local UAT required:** Yes

### PF-008 — Salary Opening (High)
- **Finding:** Salary opening import did not fully enforce effective-dated salary overlap rules used by normal salary assignment.
- **Correction:** Added salary effective-window overlap validation at validate and confirm stages, including same-effective-date controlled update behavior.
- **Files:** `backend/apps/imports/services.py`
- **Verification:** Static rule comparison / salary import UAT
- **Local UAT required:** Yes

### PF-009 — Payroll (Critical)
- **Finding:** In-service employee without salary assignment could be silently skipped from payroll, allowing incomplete payroll coverage.
- **Correction:** Payroll build now fails closed on missing salary assignment and removes stale draft records only for no-longer-eligible employees.
- **Files:** `backend/apps/payroll/services.py; backend/apps/payroll/views.py`
- **Verification:** Static service review / payroll UAT
- **Local UAT required:** Yes

### PF-010 — Payroll (Critical)
- **Finding:** Payroll could continue after non-attendance inputs changed following build (salary, OT, bonus, deduction, loan recovery, adjustment or policy).
- **Correction:** Added deterministic payroll input signature and current-input gate before Review/Approve/Lock; rebuild is required on mismatch.
- **Files:** `backend/apps/payroll/services.py`
- **Verification:** Static calculation review / rebuild-staleness UAT
- **Local UAT required:** Yes

### PF-011 — Payroll (High)
- **Finding:** Negative Net Payable and OT outside employee service period could reach downstream workflow.
- **Correction:** Negative net blocks Review/Approve/Lock; OT is constrained to service period; approved full-day leave + approved OT conflict blocks calculation.
- **Files:** `backend/apps/payroll/services.py; backend/apps/payroll/forms.py`
- **Verification:** Static rule review / payroll UAT
- **Local UAT required:** Yes

### PF-012 — Payroll Workflow (High)
- **Finding:** Rejecting payroll Approve/Lock could leave the period in a state with no clean correction/rebuild path.
- **Correction:** Approve/Lock rejection returns the period to Draft with audit reason; Review rejection remains Draft; Pay rejection remains Locked/unpaid.
- **Files:** `backend/apps/payroll/services.py; backend/apps/payroll/views.py; backend/apps/workflow/services.py`
- **Verification:** Static state-transition review / maker-checker UAT
- **Local UAT required:** Yes

### PF-013 — Workflow (Medium)
- **Finding:** Return for Correction was available for task types with no supported resubmit lifecycle, causing possible dead-end tasks.
- **Correction:** Return is restricted to source types with an implemented correction/resubmit lifecycle; unsupported types must Approve/Reject.
- **Files:** `backend/apps/workflow/forms.py; backend/apps/workflow/services.py`
- **Verification:** Static workflow review / task UAT
- **Local UAT required:** Yes

### PF-014 — Final Settlement (Critical)
- **Finding:** Settlement preparation/approval/payment could use stale payroll due or loan outstanding, and finalized attendance handling could attempt hidden reconciliation.
- **Correction:** Finalized attendance is verified rather than mutated; settlement approval/payment re-calculate payroll due and loan outstanding and block stale settlement values.
- **Files:** `backend/apps/exits/services.py`
- **Verification:** Static settlement review / exit UAT
- **Local UAT required:** Yes

### PF-015 — Final Settlement (High)
- **Finding:** Negative manual settlement inputs could reach service calls outside form validation.
- **Correction:** Service layer rejects negative leave encashment, other earnings and other deductions; negative net payable remains blocked.
- **Files:** `backend/apps/exits/services.py`
- **Verification:** Static service review / exit UAT
- **Local UAT required:** Yes

### PF-016 — Multi-company Integrity (Critical)
- **Finding:** Existing company-owned records could be reassigned to another company from business forms, risking historical cross-company inconsistency.
- **Correction:** Company ownership is disabled after creation for employee/master/policy/device/custom-field records; dependent choices remain scoped to original company.
- **Files:** `backend/apps/employees/forms.py; backend/apps/organization/forms.py; backend/apps/attendance/forms.py`
- **Verification:** Static form review / multi-company UAT
- **Local UAT required:** Yes

### PF-017 — Django Admin / Multi-company (Critical)
- **Finding:** Django Admin could still change the direct Company field after normal UI ownership was made immutable.
- **Correction:** CompanyScopedAdminMixin now makes direct company ownership read-only after object creation, including for superusers; maintenance edits and audit remain available.
- **Files:** `backend/apps/accounts/admin_utils.py`
- **Verification:** Static admin review / admin UAT
- **Local UAT required:** Yes

### PF-018 — Implementation UX (Medium)
- **Finding:** Migration screen/sample data did not explain or consistently demonstrate the controlled Leave/OT/finalized-month import boundaries.
- **Correction:** Updated migration help and sample attendance to match authoritative workflow rules.
- **Files:** `backend/templates/core/migration_center.html; SAMPLE-ATTENDANCE.csv`
- **Verification:** Static content check
- **Local UAT required:** No

## Pre-final verification performed

- Full Python compile: PASS.
- P0 static evidence verifier: 18/18 PASS (source evidence only).
- P1 static evidence verifier: 33/33 PASS (source evidence only).
- P2/P3 static evidence verifier: 18/18 PASS (source evidence only).
- Named URL references: 221 checked, 0 missing.
- Template targets: 119 checked, 0 missing.
- Raw hard-coded internal href/action in runtime templates: 0 found.
- `OPEN-ME.html` local links: 10 checked, 0 missing.
- Pure payroll calculation tests: 4/4 PASS.
- Pure loan calculation tests: 3/3 PASS.
- Django-dependent tests: not executable in this review sandbox because Django/psycopg are unavailable; must run on Local UAT.

## Gates that remain OPEN

- QA-011: reviewed/frozen Django migration files are still required from the exact source in a real Django environment.
- Local Django + PostgreSQL runtime/browser testing remains required.
- QA-094 PostgreSQL Backup → Verify → Restore → Control Totals requires real runtime evidence.
- Physical ZKTeco FO-M1 punch test remains required.
- Parallel manual-vs-software payroll reconciliation remains required.

## Local UAT focus

1. Finalize attendance, then attempt Leave approval/import/correction; verify all mutation paths block until controlled reopen.
2. Build payroll, change salary/OT/bonus/deduction/loan/adjustment/policy, then verify Review/Approve/Lock requires rebuild.
3. Verify payroll employee coverage and missing-salary fail-closed behavior.
4. Exercise maker/checker Reject/Return paths and verify no workflow dead-end.
5. Prepare final settlement, change salary/attendance/loan inputs, and verify stale approval/payment is blocked.
6. Verify company ownership cannot be moved through business UI or Django Admin.
7. Reconcile 28/29/30/31-day and mid-month joining/revision payroll against approved manual payroll policy, especially any fixed-divisor policy.

## Migration impact

No Django model field/constraint change was introduced by this pre-final correction set, so these corrections do not themselves require a new schema migration. However, the project-level QA-011 initial/frozen migration gate remains open and must still be completed before Local UAT acceptance.
