Check vacation days against the entitlement before persisting them

Applied batches were validated against the remaining föräldrapenning tiers
but never against semester. That held only while the allocator was the sole
writer, since its ledger spends vacation against a per-year budget. The
calendar is about to let a user force any day to semester and pin it, which
writes straight past that ledger, so nothing would have stopped forty pinned
vacation days against a twenty-five day entitlement.

The check mirrors the allocator's own derivation rather than restating it:
available days are total plus days saved in, and a year with no entered row
falls back to the statutory entitlement instead of reading as zero. The
semesterlagen floor stays out of it, being a bound on what may be carried
forward rather than a cap on what may be spent. Vacation is budgeted per
calendar year, so a period crossing New Year is charged to both.
This commit is contained in:
2026-09-17 18:22:55 +02:00
parent c1aa4ae0b0
commit d06c8c8449
6 changed files with 466 additions and 10 deletions
@@ -597,14 +597,17 @@ class TestApplyProposedDaysDeleteKeys:
planned_leave_repo: SQLitePlannedLeaveRepository,
seeded_ids: SeededIds,
) -> None:
"""`apply_proposed_days` places no balance cap on 'vacation' today (no
"""This batch nets to zero vacation days for 2026 (1 deleted, 1 added),
`_validate_remaining_balance` tier exists for it), so there is no cap
to "credit back" here the way there is for sjukpenning/lagsta. This
just confirms the general `delete_keys` write/validate machinery
handles a vacation-typed deletion correctly (deleted + new rows both
land right), since nothing else in this file exercises that day_type
through the delete path.
which fits comfortably under the statutory default entitlement (no
`VacationDays` row exists for this parent/year) -- so it exercises
the general `delete_keys` write/validate machinery for a vacation-
typed deletion, but does not by itself prove the vacation balance cap
(`_validate_remaining_vacation_balance`) is enforced or that its
`delete_keys` credit path matters. See
`test_planning_service_vacation.py::TestVacationBalanceDeleteKeysCredit`
for a tight-budget version that actually would reject without the
credit.
"""
ws, child, parent = (
seeded_ids.workspace_id,