Rework balances around per-parent days left and deduce the partner from FK
Manual entry now takes one parent's sjukpenningnivå, lägstanivå and dubbeldagar left (plus optional reserved days) and derives the rest in build_snapshot, shared with the scraper. The scraper also reads the family panel and exposes a partner deduction; with two parents it is saved as a 'deduced' snapshot (migration 002). Parent count is the single source of entitlement: one member gets 480 days and 96 savable at age 4, two get 240 and 48 each. Workspaces are capped at two parents. Also lands the in-flight squash of migrations 002–011 into the initial schema and the removal of planning_mode. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -4,8 +4,8 @@ planning engine (see app.services.allocation_service module docstring).
|
||||
Uses the same sqlite-backed fixtures as the rest of the service test suite
|
||||
(tests/conftest.py) to seed a realistic workspace and verify the loader nets
|
||||
out already-committed `planned_leave_day` rows, falls back to default
|
||||
entitlement when no FK snapshot exists, and reads `planning_mode` off the
|
||||
workspace.
|
||||
entitlement when no FK snapshot exists, and resolves the age-4 savable-days
|
||||
cap from parent count.
|
||||
"""
|
||||
|
||||
from datetime import date, datetime
|
||||
@@ -91,7 +91,6 @@ class TestAllocationInputLoader:
|
||||
Workspace(
|
||||
id=uuid4(),
|
||||
name="Familj",
|
||||
planning_mode="couple",
|
||||
created_at=now,
|
||||
)
|
||||
)
|
||||
@@ -200,10 +199,8 @@ class TestAllocationInputLoader:
|
||||
)
|
||||
alloc_input = await loader.load(ws.id, today=date(2027, 1, 1))
|
||||
|
||||
assert alloc_input.planning_mode == "couple"
|
||||
assert alloc_input.today == date(2027, 1, 1)
|
||||
# Two parents in a 'couple' workspace -> per-parent age-4 cap (48),
|
||||
# not the per-child figure (96).
|
||||
# Two parents -> per-parent age-4 cap (48), not the per-child figure (96).
|
||||
assert alloc_input.age4_savable_cap == 48
|
||||
|
||||
pool_with_fetch = alloc_input.pools[(parent_with_fetch.uuid, child.id)]
|
||||
@@ -235,6 +232,123 @@ class TestAllocationInputLoader:
|
||||
assert parent_facts_by_uuid[parent_with_fetch.uuid].min_vacation_days_per_year == 20
|
||||
assert parent_facts_by_uuid[parent_without_fetch.uuid].min_vacation_days_per_year == 20
|
||||
|
||||
async def test_only_pinned_existing_hides_unpinned_wish_rows_but_all_existing_planned_keeps_them(
|
||||
self,
|
||||
workspace_repo: SQLiteWorkspaceRepository,
|
||||
child_repo: SQLiteChildRepository,
|
||||
personal_data_repo: SQLitePersonalDataRepository,
|
||||
parental_leave_repo: SQLiteParentalLeaveRepository,
|
||||
vacation_repo: SQLiteVacationDaysRepository,
|
||||
planned_leave_repo: SQLitePlannedLeaveRepository,
|
||||
leave_wish_repo: SQLiteLeaveWishRepository,
|
||||
) -> None:
|
||||
"""§fix (parent-reassignment moves dates, decision B) — `sync_plan`
|
||||
|
||||
loads with `only_pinned_existing=True`, which must keep hiding
|
||||
unpinned wish-attributed rows from `existing_planned` (unchanged
|
||||
behaviour), but `all_existing_planned` must carry EVERY persisted
|
||||
row regardless — that's what lets `_Ledger._persisted_by_wish` find
|
||||
a flexible wish's own prior placement even when it's currently
|
||||
hidden from `existing_planned`. See `AllocationInput
|
||||
.all_existing_planned`'s docstring in allocation_service.py.
|
||||
"""
|
||||
now = datetime.now()
|
||||
ws = await workspace_repo.create(Workspace(id=uuid4(), name="Familj", created_at=now))
|
||||
parent = await personal_data_repo.create(
|
||||
PersonalData(
|
||||
uuid=uuid4(),
|
||||
workspace_id=ws.id,
|
||||
name="Parent",
|
||||
salary_type="monthly",
|
||||
employment_status="employed",
|
||||
created_at=now,
|
||||
)
|
||||
)
|
||||
child = await child_repo.create(
|
||||
Child(
|
||||
id=uuid4(),
|
||||
workspace_id=ws.id,
|
||||
name="Barn",
|
||||
birth_date=date(2026, 1, 1),
|
||||
created_at=now,
|
||||
)
|
||||
)
|
||||
wish = await leave_wish_repo.create(
|
||||
LeaveWish(
|
||||
id=uuid4(),
|
||||
workspace_id=ws.id,
|
||||
child_id=child.id,
|
||||
name="Höstlov",
|
||||
window_start_month=10,
|
||||
window_start_day=1,
|
||||
window_end_month=10,
|
||||
window_end_day=7,
|
||||
target_weeks=1,
|
||||
recurrence="once",
|
||||
first_year=2027,
|
||||
last_year=None,
|
||||
priority=1,
|
||||
parent_mode="either",
|
||||
parent_personal_data_uuid=None,
|
||||
window_mode="flexible",
|
||||
)
|
||||
)
|
||||
|
||||
pinned = await planned_leave_repo.create(
|
||||
PlannedLeaveDay(
|
||||
id=uuid4(),
|
||||
workspace_id=ws.id,
|
||||
child_id=child.id,
|
||||
parent_personal_data_uuid=parent.uuid,
|
||||
date=date(2027, 10, 1),
|
||||
day_type="vacation",
|
||||
is_pinned=True,
|
||||
)
|
||||
)
|
||||
unpinned_wish_row = await planned_leave_repo.create(
|
||||
PlannedLeaveDay(
|
||||
id=uuid4(),
|
||||
workspace_id=ws.id,
|
||||
child_id=child.id,
|
||||
parent_personal_data_uuid=parent.uuid,
|
||||
date=date(2027, 10, 4),
|
||||
day_type="vacation",
|
||||
wish_id=wish.id,
|
||||
is_pinned=False,
|
||||
)
|
||||
)
|
||||
ad_hoc = await planned_leave_repo.create(
|
||||
PlannedLeaveDay(
|
||||
id=uuid4(),
|
||||
workspace_id=ws.id,
|
||||
child_id=child.id,
|
||||
parent_personal_data_uuid=parent.uuid,
|
||||
date=date(2027, 10, 5),
|
||||
day_type="vacation",
|
||||
wish_id=None,
|
||||
is_pinned=False,
|
||||
)
|
||||
)
|
||||
|
||||
loader = _make_loader(
|
||||
workspace_repo,
|
||||
child_repo,
|
||||
personal_data_repo,
|
||||
parental_leave_repo,
|
||||
vacation_repo,
|
||||
planned_leave_repo,
|
||||
leave_wish_repo,
|
||||
)
|
||||
alloc_input = await loader.load(ws.id, today=date(2027, 1, 1), only_pinned_existing=True)
|
||||
|
||||
visible_ids = {d.id for d in alloc_input.existing_planned}
|
||||
assert visible_ids == {pinned.id, ad_hoc.id}
|
||||
assert unpinned_wish_row.id not in visible_ids
|
||||
|
||||
assert alloc_input.all_existing_planned is not None
|
||||
all_ids = {d.id for d in alloc_input.all_existing_planned}
|
||||
assert all_ids == {pinned.id, unpinned_wish_row.id, ad_hoc.id}
|
||||
|
||||
async def test_load_reflects_a_custom_minimum_vacation_days(
|
||||
self,
|
||||
workspace_repo: SQLiteWorkspaceRepository,
|
||||
|
||||
Reference in New Issue
Block a user