fix(salary): recompute entitled_days on existing ledger rows and record pre-cutover taken days (#1403)

The vacation ledger sync carried entitled_days verbatim on existing open
rows while re-deriving accrued and taken, so a stale entitled value (for
example the flat 25 stored before Semesterlagen 7 § pro-rating existed)
survived every sync. The recompute loop now re-derives entitled the same
way the lazy-seed path does, with the opening-balance cutover still
outranking recomputation for the year containing cutover_date.

Opening balances could also not record paid vacation days already taken
in the cutover year under the previous payroll system. New additive
column employee_opening_balances.vacation_days_taken_this_year (NUMERIC
NOT NULL DEFAULT 0, CHECK 0..40) threaded through the shared service,
the Zod schema, the MCP staging tool (schema + mergeable fields), the
staged-operation executor, the v1 REST routes, and the employee editor
form. Ledger semantics for the cutover year, on both seed and recompute
paths: entitled = remaining + taken_this_year, taken = booked-run taken
+ taken_this_year, so remaining keeps meaning remaining and the seeded
value survives every subsequent sync.

Fixes #1347

Co-authored-by: Jakob Wennberg <311770904+jakobwennberg-oss@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Jakob Wennberg
2026-08-04 19:35:02 +02:00
committed by GitHub
co-authored by Jakob Wennberg Claude Fable 5
parent 2296c0cd59
commit 9f5a43310b
11 changed files with 219 additions and 17 deletions
+5
View File
@@ -2776,6 +2776,11 @@ const openingBalancesShape = {
ytd_tax: z.number().min(0).default(0),
ytd_net: z.number().min(0).default(0),
vacation_paid_days_remaining: z.number().min(0).max(40).default(0),
// Paid days already taken in the CURRENT vacation year under the previous
// system. The ledger's cutover-year row derives entitled = remaining +
// taken_this_year and folds this into taken_days; remaining keeps meaning
// "remaining at cutover".
vacation_days_taken_this_year: z.number().min(0).max(40).default(0),
vacation_saved_days_by_year: z
.record(fiscalYearSchema, z.number().min(0).max(40))
.default({}),