Let a household choose to be paid for the red days
Asking for a full week off never spent anything on the Saturday, the Sunday or a holiday, and a test now says so outright rather than leaving it to be rediscovered. Those days are free because vacation cannot be taken on a day that is not a working day. Parental benefit is a payment rather than a schedule, though, so it can be drawn on a red day to turn an unpaid day off into a paid one — at the cost of a day the family would otherwise keep. That is now a workspace setting, off by default, and it never reaches for a vacation day. With it off the planner behaves exactly as before. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ei7gCZzdd751uZtPzb1Pep
This commit is contained in:
@@ -62,6 +62,15 @@ REASON_FP_LAGSTA_STRETCH = "fp_lagsta_stretch"
|
||||
REASON_SWAP_FIRST_HALF = "swap_first_half"
|
||||
REASON_SWAP_SECOND_HALF = "swap_second_half"
|
||||
REASON_DUBBELDAG = "dubbeldag"
|
||||
# §todo item 2 — a free day (Saturday, Sunday or public holiday) funded with a
|
||||
# parental-benefit day instead of being left free, opted into per-workspace via
|
||||
# `AllocationInput.take_pl_on_red_days` (default False — see `_fund_solo`/
|
||||
# `_fund_both`). Vacation is deliberately never used here: Swedish law does not
|
||||
# let you "take vacation" on a day that already isn't a working day, but FP is
|
||||
# a benefit payment, not a scheduling construct, so it can still be drawn on a
|
||||
# red day to replace the (unpaid) day off with a paid one — at the cost of
|
||||
# spending a day the household would otherwise have kept.
|
||||
REASON_FP_RED_DAY = "fp_red_day"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Surplus advice kinds (§9.2) — the mirror of a shortfall remedy: emitted
|
||||
@@ -219,6 +228,15 @@ class AllocationInput:
|
||||
# despite the calendar. `is_free_day` is the single place these are
|
||||
# merged on top of `holidays`.
|
||||
day_overrides: dict[date, bool] = field(default_factory=dict)
|
||||
# §todo item 2 — per-workspace opt-in (default False, byte-identical to
|
||||
# pre-existing behaviour when unset): allow a free day (Saturday, Sunday,
|
||||
# public holiday) inside a funded window to be paid with an FP day
|
||||
# (never vacation — see `REASON_FP_RED_DAY`). Read only by `_fund_solo`/
|
||||
# `_fund_both`; deliberately NOT consulted by `is_free_day` itself, which
|
||||
# stays the single, opt-in-agnostic definition of "is this day free" (it
|
||||
# is also used for costing/klämdag-cluster detection, where the day must
|
||||
# stay classified as free regardless of whether it ends up FP-funded).
|
||||
take_pl_on_red_days: bool = False
|
||||
# §fix (parent-reassignment moves dates, decision B) — the FULL,
|
||||
# never-filtered snapshot of every persisted `planned_leave_day` row for
|
||||
# this workspace, independent of `existing_planned` above (which
|
||||
@@ -1057,6 +1075,25 @@ class _Ledger:
|
||||
continue
|
||||
|
||||
if is_free_day(d, self._input.holidays, self._input.day_overrides):
|
||||
fp_on_red_day = (
|
||||
self._spend_fp(pool_key, prefer="sjukpenning")
|
||||
if self._input.take_pl_on_red_days and pool_key is not None
|
||||
else None
|
||||
)
|
||||
if fp_on_red_day is not None:
|
||||
cost[fp_on_red_day] += 1
|
||||
days.append(
|
||||
ProposedDay(
|
||||
d,
|
||||
parent.uuid,
|
||||
child_id,
|
||||
fp_on_red_day,
|
||||
False,
|
||||
wish.id,
|
||||
REASON_FP_RED_DAY,
|
||||
)
|
||||
)
|
||||
continue
|
||||
cost["free"] += 1
|
||||
reason = free_reason_override or (
|
||||
REASON_FREE_RED_DAY
|
||||
@@ -1248,14 +1285,42 @@ class _Ledger:
|
||||
continue
|
||||
|
||||
if is_free_day(d, self._input.holidays, self._input.day_overrides):
|
||||
cost["free"] += 1
|
||||
reason = (
|
||||
# §todo item 2 — each parent's own FP pool is tried
|
||||
# independently (never vacation, never a peeked-then-
|
||||
# committed-together pair like dubbeldagar/co-leave above):
|
||||
# one parent running out of FP must never stop the other
|
||||
# from still getting paid for this same red day.
|
||||
free_reason = (
|
||||
REASON_FREE_RED_DAY
|
||||
if _is_real_red_day(d, self._input.holidays)
|
||||
else REASON_FREE_WEEKEND
|
||||
)
|
||||
days.append(ProposedDay(d, parent_a.uuid, child.id, "free", False, wish.id, reason))
|
||||
days.append(ProposedDay(d, parent_b.uuid, child.id, "free", False, wish.id, reason))
|
||||
for red_parent, red_pool in ((parent_a, pool_a), (parent_b, pool_b)):
|
||||
fp_on_red_day = (
|
||||
self._spend_fp((red_parent.uuid, child.id), prefer="sjukpenning")
|
||||
if self._input.take_pl_on_red_days and red_pool is not None
|
||||
else None
|
||||
)
|
||||
if fp_on_red_day is not None:
|
||||
cost[fp_on_red_day] += 1
|
||||
days.append(
|
||||
ProposedDay(
|
||||
d,
|
||||
red_parent.uuid,
|
||||
child.id,
|
||||
fp_on_red_day,
|
||||
False,
|
||||
wish.id,
|
||||
REASON_FP_RED_DAY,
|
||||
)
|
||||
)
|
||||
else:
|
||||
cost["free"] += 1
|
||||
days.append(
|
||||
ProposedDay(
|
||||
d, red_parent.uuid, child.id, "free", False, wish.id, free_reason
|
||||
)
|
||||
)
|
||||
continue
|
||||
|
||||
funded = False
|
||||
@@ -2550,6 +2615,7 @@ class AllocationInputLoader:
|
||||
existing_planned=existing_planned,
|
||||
holidays=holidays_map,
|
||||
day_overrides=day_overrides,
|
||||
take_pl_on_red_days=ws.take_pl_on_red_days,
|
||||
all_existing_planned=all_existing_planned,
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user