Project

General

Profile

Actions

Feature #146

open
SV SV

[HR][P8] Leave year, accrual and LWP

Feature #146: [HR][P8] Leave year, accrual and LWP

Added by Sravan V S about 2 months ago. Updated 3 days ago.

Status:
Resolved
Priority:
High
Assignee:
Target version:
-
Start date:
09/28/2026
Due date:
% Done:

0%

Estimated time:
Resolved By:

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 - 13 days ago Actions #2

  • Assignee changed from Sravan V S to SK -

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.EnsureCurrentYearBalancesAsync now does var 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 use new Date().getFullYear() with the same comment.
  • LWP as a first-class leave type: LeaveRequestService resolves a dedicated LWP-coded leave type (LwpLeaveTypeCode) and reserves/deducts/releases through the same ledger methods #140 built - confirmed BypassBalanceCheck requests go through ResolveBalanceLeaveTypeIdAsync, not a side-channel.
  • Accrual/carry-forward job: LeaveBalances/LeaveAccrualRunner.cs + Services/LeaveAccrualHostedService.cs exist and are wired up.
  • Data-repair migration: 20261031120437_RepairLeaveYearBoundaryToCalendarYear is 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
Actions

Also available in: PDF Atom