Feature #146
open[HR][P8] Leave year, accrual and LWP
Added by Sravan V S about 2 months ago. Updated 3 days ago.
0%
Description
Leave-year model, accrual and carry-forward jobs, LWP as real loss-of-pay days instead of a negative paid balance, data repair for already-posted rows.
Size: L
Needs owner decision #13, 14, 15 (see #137).
Source: clinq-docs/modules/hr/reviews/2026-09-28-ui-audit/ (branch docs/hr-ui-audit-2026-09-28)
Findings covered:
- C-02
- C-11
- C-08
- C-20
- C-38
SV Updated by Sravan V S about 2 months ago Actions #1
- Assignee set to Sravan V S
S- Updated by SK - 12 days ago Actions #3
- Status changed from New to Resolved
- Assignee changed from SK - to Sravan V S
- Resolved By set to SK -
Fixed and merged to dev on nuvita-hr. Owner decisions #13 (calendar year, Jan-Dec), #14 (exclude holidays/weekly-offs, sandwich rule off), and #15 (LWP as a dedicated leave type, not a bypass-converts-shortfall mechanism) are confirmed - this ticket is where #13 actually gets implemented as a real switch, not just a guideline, since #140 (built before #13 was confirmed) deliberately left the existing fiscal-year (April-March) convention untouched.
Found every site computing the leave-year boundary - more than #140 had flagged, including two portal pages independently reimplementing the fiscal formula in TypeScript - and switched them all to calendar year. Along the way, found a real latent bug worth knowing about: balance deduction was already bucketing by plain calendar year while balance credit used the fiscal formula, so a January-March onboarding's credited balance and its first leave deduction could land in different year buckets, silently granting a second full entitlement. This fix closes that gap going forward; the migration below repairs rows already affected by it.
Decision #14 was already substantially implemented by #140's existing day-counting logic (holidays and weekly-offs already excluded, sandwich rule structurally off) - only change was flipping an unused, dormant "sandwich rule" flag's default to match, not building new logic nobody asked for.
Built real LWP: a dedicated "Leave Without Pay" leave type (the UI pieces for this already existed but were never wired to anything) now actually records real loss-of-pay days through the same balance ledger and transactional safeguards #140 already built (reservation, concurrency token, single-transaction commit) - no parallel write path, no risk of reopening the race conditions #140 fixed. Previously, bypassing the balance check just forced a deduction with no availability check at all - a real negative-balance side effect, not a real ledger entry.
Built the missing accrual/carry-forward job from scratch - confirmed it genuinely didn't exist as a running job anywhere (the underlying calculation functions had zero real callers). A minimal scheduled job now runs monthly accrual and the Jan 1 annual credit + carry-forward, following this codebase's one existing precedent for this kind of background job.
Deploy gate, same caution as #140's migration: this ships with a one-off data-repair migration re-bucketing already-posted leave-balance rows from the old fiscal-year boundary to the new calendar-year one. It's deliberately conservative (errs toward skipping a row for manual review rather than guessing), but this is real employee leave-entitlement data - the migration's own code comments spell out exactly what to validate against a copy of real tenant data (row-count sanity check, a separate list of rows it deliberately skips for manual reconciliation, and confirming with payroll whether any affected employee has already had a payroll run reference the old year bucket) before this runs against any real tenant. Assigning to Sravan for that sign-off, same as #140 - the comments above stay as the record of what was built and why.
Tests: HR backend unit 1327/1327 passed; HR backend integration 254/254 passed; HR frontend 104/104 files, 810/810 tests passed, type-check clean.
S- Updated by SK - 12 days ago Actions #4
- Assignee changed from Sravan V S to Roshni Harish.
Double-checked directly against current dev code (commit 3228600, build + targeted unit/integration tests all green) before reassigning:
- Calendar-year switch:
LeaveBalanceService.EnsureCurrentYearBalancesAsyncnow doesvar leaveYear = (short)DateTime.UtcNow.Year;with a comment citing owner decision #13 (previously April-March). Both portal pages that had reimplemented the fiscal formula (leave-requests/[id]/page.tsx,employees/[id]/page.tsx) now usenew Date().getFullYear()with the same comment. - LWP as a first-class leave type:
LeaveRequestServiceresolves a dedicatedLWP-coded leave type (LwpLeaveTypeCode) and reserves/deducts/releases through the same ledger methods #140 built - confirmedBypassBalanceCheckrequests go throughResolveBalanceLeaveTypeIdAsync, not a side-channel. - Accrual/carry-forward job:
LeaveBalances/LeaveAccrualRunner.cs+Services/LeaveAccrualHostedService.csexist and are wired up. - Data-repair migration:
20261031120437_RepairLeaveYearBoundaryToCalendarYearis present in the migrations folder.
This is genuinely done, not just claimed. Reassigning to Roshni for QA sign-off - the migration itself (one-off data repair against real tenant data) still needs your/ops go-ahead before it's actually run against a live tenant, separate from this code-correctness check.
RH Updated by Roshni Harish. 3 days ago Actions #5
- Assignee changed from Roshni Harish. to Sravan V S