# Employee entitlements and ledger — September 6, 2026 NEW: Leave Balance now shows employee/type/year entitlement accounts, a Grant credits modal, and paginated balance history. HR can reverse an unused grant through a new recorded entry. The original grant and its reason remain visible. ## Accounting model `employee_leave_entitlements` stores immutable account identity/ownership and the opening review status. `employee_leave_ledger` journals opening credits and usage plus all subsequent changes. `leave_credit_grants` preserves explicit HR grants and full grant reversals with unique request keys. The existing balance row remains the operational total. There is one usage writer: the SQL `leave_post_usage` routine, called by the existing approved-leave triggers. PHP changes credits only for grants/reversals. Balance triggers append the ledger in the same transaction; PHP does not post a second deduction. The generated balance column is never written directly. Ledger sums must equal recorded credits and usage; the postflight checks both separately. Opening entries preserve the supplied balance amounts without reconstructing old events from unreliable deduction logs. Clean arithmetic is labeled **Opening unverified**, not certified history. Missing references, invalid values, and unexplained usage are **Needs reconciliation**. Such accounts block new grants and pending/approved leave until separately reconciled; cancellation/rejection remains available through the existing service and reversal safeguards. Missing balance groups remain in the read-only reports; no missing employee is invented. ## Grant and leave rules - A grant adds credits to an employee/type/year account. It requires a reason, authorized `leave_balances` access, `can_add`, and the selected company/brand. A new account starts at zero before the grant. Existing credits remain intact. - Grant requests have a stable unique key. Retrying the same content returns the original grant. Reusing that key for different content is rejected. - Reversing a grant also requires `can_edit`. Each grant can be reversed once, in full. Used credits and pending reservations cannot be removed. Reversal entries link to the original ledger entry and retain the actor and reason. - Submissions/approvals must have sufficient credits after existing usage and pending reservations. The request's own old contribution is excluded on edit. Existing company policy limits and service eligibility are additional checks. - Approvals debit once, identical reapproval posts nothing, and material edits reverse old usage before applying the replacement. Cancellation restores the original year. Post-cutover reversals link to their matching deduction; old usage reversals refer to preserved opening usage without inventing old events. - Leave must be split across calendar years. Credits in another year cannot be spent on this year's leave. Grants may target a future year but do not bypass that year's policy eligibility or annual ceiling. - Account ownership does not follow a later employee transfer. Transfers and employee-ID/account-identity rewrites need explicit reconciliation. Deleting balances, ledger entries or grant history is blocked; archive history instead. - Direct external balance adjustments are journaled using the database actor and flag the account for review. Global type allowance updates through the app are blocked after ledger setup; use explicit employee grants. Legacy creation triggers that seed defaults still create opening-unverified accounts, with the seeded amounts visible in the ledger. Credit grants are manual and take effect when posted to their selected year. Automatic monthly accrual, prorating, carryover, expiry jobs, bulk grants and statutory event entitlements are not activated by this migration. ## Access and display Admin account/history queries filter both current employee scope and stored account ownership. Client-portal API callers may read their own history only; they cannot grant credits or list other employees' accounts. New UI actions use the existing authenticated transport and company/brand selection. History is paginated 100 entries at a time and shows actor, reason, signed change, balance after, source leave and reversal references. Dates shown are database timestamps. ## Rollout 1. Preserve a full backup, including routines/triggers, and test a fresh clone. Confirm `2026_09_06` integrity setup is complete. Keep the original preflight reports and the deferred reconciliation review. 2. Pause leave writes, employee identity changes, credit integrations and payroll workers for migration. Select the intended database in phpMyAdmin. 3. Run `backend/migrations/2026_09_06_leave_entitlements_ledger.sql`. The dated comments explain each added/replaced/preserved object. DDL auto-commits and completion is marked last. PHP blocks partial setup. Do not insert the marker manually or rerun the old integrity migration over the new routine. 4. Run `backend/migrations/2026_09_06_leave_ledger_postflight.sql` against the verified target. Ledger credit/usage differences must be empty; historical review findings remain visible and are not automatically cleared. 5. Deploy matching backend and rebuilt frontend together. Open Leave Balance, choose the year, grant test credits to a verified employee and inspect History. Test approval, edit, cancellation and an unused grant reversal on the clone. Reopen writes only after reconciliation totals and workflow checks pass. Before new writes, rollback means restoring the saved database/routines and matching application. After new entries exist, preserve/export them and reconcile before restoring any old state. Removing journal triggers alone is not a rollback. The agent has not applied this migration to the live database. Deferred historical employee-reference/balance issues remain open. No existing employee is remapped. ## Validation The disposable integration suite passed 76 checks: the 48 previous integrity/policy checks and 28 ledger checks for opening preservation, idempotent grants, deductions, linked reversals, pending reservations, flagged accounts, immutable history, ownership, and exact ledger-to-balance reconciliation. Five permission cases passed for admin grants, denied/client/foreign-brand grants and own-history access. PHP syntax, targeted frontend lint and a production build are checked. Browser/login testing on the working server is not claimed. The ledger migration was also executed against a disposable import of `solidmarkmaster_db (11).sql`. Hash comparisons confirmed that every existing balance, request, approved-leave and deduction-log row stayed unchanged. It created 146 opening-unverified accounts and flagged the two known usage discrepancies. Postflight showed no ledger-to-balance differences and retained the missing balance group of two approved records totaling 1.88 days.