Link planned days to their wish, fix the age-4 cap, drop the planning goal
Three changes that together unblock treating a calendar period as one editable object. Migration 008 adds `planned_leave_day.wish_id` (nullable, ON DELETE SET NULL) so a persisted day knows which `leave_wish` produced it, `is_pinned` to mark a day the user hand-edited, and `leave_wish.target_days` so a period drawn on a calendar need not be a whole number of weeks. Apply-time identity still keys on day_type/is_dubbeldag only, so a wish_id-only difference is a metadata update rather than a collision. The age-4 savable ceiling was applying the per-child figure of 96 to each parent's pool, modelling a two-parent family as able to save 192 days past the child's fourth birthday. `resolve_age4_savable_cap` is now the single place that decides 48 (couple with two parents) versus 96 (everyone else), and both AnalyticsService and the allocator read it. Migration 009 drops `workspace.planning_goal` and `leave_wish.goal_override`. The allocator's tier-3 policy collapses to one rule; `salary_type` still branches, since that reflects what a vacation day actually costs a parent rather than a user preference.
This commit is contained in:
@@ -30,19 +30,42 @@ def weekly_intensity(planned_days: list[PlannedLeaveDay], period_weeks: float) -
|
||||
return len(fp_days) / period_weeks
|
||||
|
||||
|
||||
def resolve_age4_savable_cap(planning_mode: str, parent_count: int) -> int:
|
||||
"""Single source of truth for which age-4 savable-days ceiling applies.
|
||||
|
||||
`rules.max_savable_days_at_age_4` (96) is the per-CHILD ceiling. When a
|
||||
child actually has two parents in a workspace explicitly flagged
|
||||
`planning_mode == 'couple'`, each parent's own balance is capped at
|
||||
`rules.max_savable_days_per_parent_at_age_4` (48) instead — 96 each would
|
||||
let the family save 192 combined, double the legal figure.
|
||||
|
||||
Requiring both conditions (couple mode AND >=2 parents) is a safety belt:
|
||||
a workspace flagged 'couple' that only has one member so far still gets
|
||||
the per-child (96) figure, since there is no second parent to divide it
|
||||
with. `AnalyticsService` and `allocation_service`'s `_Ledger` both call
|
||||
this so they can never disagree on which cap applies.
|
||||
"""
|
||||
rules = settings.rules
|
||||
if planning_mode == "couple" and parent_count >= 2:
|
||||
return rules.max_savable_days_per_parent_at_age_4
|
||||
return rules.max_savable_days_at_age_4
|
||||
|
||||
|
||||
def days_left_at_age_4_projection(
|
||||
days_used: int,
|
||||
planned_fp_before_b4: int,
|
||||
total_days: int,
|
||||
max_savable: int,
|
||||
) -> tuple[int, int]:
|
||||
"""Return (projected_left_at_b4, expiring_at_b4) without hitting the DB.
|
||||
|
||||
Delegates the full DB-backed version to AnalyticsService. Use this in
|
||||
tests or when the numbers are already loaded.
|
||||
`max_savable` is the resolved cap (see `resolve_age4_savable_cap`) — the
|
||||
caller decides which figure applies; this function only does the shared
|
||||
arithmetic so `AnalyticsService` and `allocation_service._Ledger` can
|
||||
never derive it differently.
|
||||
"""
|
||||
rules = settings.rules
|
||||
projected_left = total_days - (days_used + planned_fp_before_b4)
|
||||
expiring = max(projected_left - rules.max_savable_days_at_age_4, 0)
|
||||
expiring = max(projected_left - max_savable, 0)
|
||||
return projected_left, expiring
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user