e4719cb9ae
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>
548 lines
22 KiB
Python
548 lines
22 KiB
Python
"""Tests for the core calculation engine (days-left-at-age-4).
|
|
|
|
These tests cover the headline feature and must cover edge cases:
|
|
leap years, unborn child (due date), no FK data yet, full planned calendar.
|
|
"""
|
|
|
|
from datetime import date, datetime
|
|
from uuid import uuid4
|
|
|
|
import pytest
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from app.entities.child import Child
|
|
from app.entities.parental_leave import ParentalLeaveFetch, TierData
|
|
from app.repositories_impl.sqlite.child_repository import SQLiteChildRepository
|
|
from app.repositories_impl.sqlite.parental_leave_repository import SQLiteParentalLeaveRepository
|
|
from app.repositories_impl.sqlite.personal_data_repository import SQLitePersonalDataRepository
|
|
from app.repositories_impl.sqlite.leave_wish_repository import SQLiteLeaveWishRepository
|
|
from app.repositories_impl.sqlite.planned_leave_repository import SQLitePlannedLeaveRepository
|
|
from app.repositories_impl.sqlite.vacation_days_repository import SQLiteVacationDaysRepository
|
|
from app.repositories_impl.sqlite.workspace_repository import SQLiteWorkspaceRepository
|
|
from app.services.analytics_service import AnalyticsService
|
|
from tests.conftest import SeededIds
|
|
|
|
|
|
def _make_child(workspace_id, birth_date: date) -> Child:
|
|
return Child(
|
|
id=uuid4(),
|
|
workspace_id=workspace_id,
|
|
name="TestChild",
|
|
birth_date=birth_date,
|
|
created_at=datetime.now(),
|
|
)
|
|
|
|
|
|
def _make_fetch(
|
|
personal_data_uuid, workspace_id, child_id, total=240, used=100
|
|
) -> ParentalLeaveFetch:
|
|
now = datetime.now()
|
|
tier = TierData(
|
|
sjukpenning_total=390,
|
|
sjukpenning_used=used,
|
|
sjukpenning_left=390 - used,
|
|
lagsta_niva_total=90,
|
|
lagsta_niva_used=0,
|
|
lagsta_niva_left=90,
|
|
)
|
|
return ParentalLeaveFetch(
|
|
id=uuid4(),
|
|
personal_data_uuid=personal_data_uuid,
|
|
workspace_id=workspace_id,
|
|
child_id=child_id,
|
|
source="manual",
|
|
fetched_at=now,
|
|
total_days=total,
|
|
days_used=used,
|
|
days_left=total - used,
|
|
reserved_days_total=90,
|
|
reserved_days_left=90,
|
|
transferable_days_left=50,
|
|
dubbeldagar_left=60,
|
|
tier_data=tier,
|
|
created_at=now,
|
|
updated_at=now,
|
|
)
|
|
|
|
|
|
async def _make_service(
|
|
child_repo: SQLiteChildRepository,
|
|
leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_repo: SQLitePlannedLeaveRepository,
|
|
pd_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
) -> AnalyticsService:
|
|
return AnalyticsService(leave_repo, planned_repo, child_repo, pd_repo, workspace_repo)
|
|
|
|
|
|
class TestAge4Projection:
|
|
async def test_no_planned_days_projection(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
ws = seeded_ids.workspace_id
|
|
pd_uuid = seeded_ids.personal_data_uuid
|
|
|
|
# Use a birth date far in the future so today < B4
|
|
birth = date.today() + relativedelta(years=2)
|
|
child = _make_child(ws, birth)
|
|
await child_repo.create(child)
|
|
|
|
fetch = _make_fetch(pd_uuid, ws, child.id, total=240, used=100)
|
|
await parental_leave_repo.create(fetch)
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
proj = await service.get_days_left_at_age_4(pd_uuid, child.id)
|
|
|
|
assert proj.days_used == 100
|
|
assert proj.planned_days_before_b4 == 0
|
|
assert proj.projected_left_at_b4 == 140 # 240 - 100
|
|
assert proj.savable_after_b4 == 96
|
|
assert proj.expiring_at_b4 == 44 # 140 - 96
|
|
assert proj.savable_cap_at_b4 == 96 # solo workspace -> per-child ceiling
|
|
|
|
async def test_no_fetch_data_assumes_full_entitlement(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
ws = seeded_ids.workspace_id
|
|
pd_uuid = seeded_ids.personal_data_uuid
|
|
birth = date.today() + relativedelta(years=2)
|
|
child = _make_child(ws, birth)
|
|
await child_repo.create(child)
|
|
|
|
# No FK data entered
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
proj = await service.get_days_left_at_age_4(pd_uuid, child.id)
|
|
|
|
# `seeded_ids`'s workspace has exactly one member -> a sole parent's
|
|
# default entitlement is the FULL pool (480), not a partnered
|
|
# parent's half-share (240) -- see `default_tier_pool_share`.
|
|
assert proj.total_days == 480 # default entitlement
|
|
assert proj.days_used == 0
|
|
assert proj.projected_left_at_b4 == 480
|
|
assert proj.savable_after_b4 == 96
|
|
assert proj.expiring_at_b4 == 384 # 480 - 96
|
|
|
|
async def test_leap_year_birthday(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
ws = seeded_ids.workspace_id
|
|
pd_uuid = seeded_ids.personal_data_uuid
|
|
# Child born 2024-02-29 (leap day); 4th birthday must be 2028-02-29
|
|
birth = date(2024, 2, 29)
|
|
child = _make_child(ws, birth)
|
|
await child_repo.create(child)
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
proj = await service.get_days_left_at_age_4(pd_uuid, child.id)
|
|
|
|
# dateutil.relativedelta gives 2028-02-29 (2028 is a leap year)
|
|
assert proj.fourth_birthday == date(2028, 2, 29)
|
|
|
|
async def test_all_days_used_nothing_left(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
ws = seeded_ids.workspace_id
|
|
pd_uuid = seeded_ids.personal_data_uuid
|
|
birth = date.today() + relativedelta(years=2)
|
|
child = _make_child(ws, birth)
|
|
await child_repo.create(child)
|
|
|
|
fetch = _make_fetch(pd_uuid, ws, child.id, total=240, used=240)
|
|
await parental_leave_repo.create(fetch)
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
proj = await service.get_days_left_at_age_4(pd_uuid, child.id)
|
|
|
|
assert proj.projected_left_at_b4 == 0
|
|
assert proj.savable_after_b4 == 0
|
|
assert proj.expiring_at_b4 == 0
|
|
|
|
async def test_child_not_found_raises(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
) -> None:
|
|
from app.utils.errors import NotFoundError
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
with pytest.raises(NotFoundError):
|
|
await service.get_days_left_at_age_4(uuid4(), uuid4())
|
|
|
|
|
|
class TestValidateDubbeldagar:
|
|
async def test_child_not_found_raises(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
) -> None:
|
|
from app.utils.errors import NotFoundError
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
with pytest.raises(NotFoundError):
|
|
await service.validate_dubbeldagar(uuid4(), date.today())
|
|
|
|
async def test_outside_window_raises(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
from app.utils.errors import DubbeldagError
|
|
|
|
ws = seeded_ids.workspace_id
|
|
birth = date(2024, 1, 1)
|
|
child = _make_child(ws, birth)
|
|
await child_repo.create(child)
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
# Date beyond the 15-month window
|
|
beyond_window = birth + relativedelta(months=16)
|
|
with pytest.raises(DubbeldagError):
|
|
await service.validate_dubbeldagar(child.id, beyond_window)
|
|
|
|
async def test_quota_exceeded_raises(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
from app.config import settings
|
|
from app.entities.planned_leave import PlannedLeaveDay
|
|
from app.utils.errors import DubbeldagError
|
|
|
|
ws = seeded_ids.workspace_id
|
|
pd_uuid = seeded_ids.personal_data_uuid
|
|
birth = date(2024, 1, 1)
|
|
child = _make_child(ws, birth)
|
|
await child_repo.create(child)
|
|
|
|
# Fill up the dubbeldagar quota
|
|
for i in range(settings.rules.max_dubbeldagar):
|
|
day = birth + relativedelta(days=i)
|
|
await planned_leave_repo.create(
|
|
PlannedLeaveDay(
|
|
id=uuid4(),
|
|
workspace_id=ws,
|
|
child_id=child.id,
|
|
parent_personal_data_uuid=pd_uuid,
|
|
date=day,
|
|
day_type="sjukpenning",
|
|
is_dubbeldag=True,
|
|
)
|
|
)
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
within_window = birth + relativedelta(months=1)
|
|
with pytest.raises(DubbeldagError):
|
|
await service.validate_dubbeldagar(child.id, within_window)
|
|
|
|
|
|
class TestGetOverview:
|
|
async def test_overview_single_parent(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
ws = seeded_ids.workspace_id
|
|
pd_uuid = seeded_ids.personal_data_uuid
|
|
|
|
birth = date.today() + relativedelta(years=2)
|
|
child = _make_child(ws, birth)
|
|
await child_repo.create(child)
|
|
|
|
fetch = _make_fetch(pd_uuid, ws, child.id, total=240, used=80)
|
|
await parental_leave_repo.create(fetch)
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
overview = await service.get_overview(ws, child.id)
|
|
|
|
assert overview.workspace_id == ws
|
|
assert len(overview.parent_projections) == 1
|
|
assert overview.combined_days_used == 80
|
|
assert overview.combined_days_left == 160 # 240 - 80
|
|
assert overview.combined_expiring_at_b4 == 64 # 160 - 96
|
|
|
|
async def test_overview_no_parents_raises(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
) -> None:
|
|
from app.utils.errors import NotFoundError
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
with pytest.raises(NotFoundError):
|
|
await service.get_overview(uuid4(), uuid4())
|
|
|
|
|
|
class TestCoupleWorkspacePerParentCap:
|
|
"""Headline regression: a two-parent `couple` workspace must cap each
|
|
|
|
parent's own savable balance at 48 (not 96), so the combined savable
|
|
figure across both parents tops out at 96 total — never 192. This is
|
|
the bug this task exists to fix: the age-4 ceiling is per CHILD, not
|
|
per PARENT pool.
|
|
"""
|
|
|
|
async def test_two_parent_couple_caps_48_each_96_combined(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
) -> None:
|
|
from app.entities.personal_data import PersonalData
|
|
from app.entities.workspace import Workspace
|
|
|
|
now = datetime.now()
|
|
ws = await workspace_repo.create(Workspace(id=uuid4(), name="Familj", created_at=now))
|
|
parent_a = await personal_data_repo.create(
|
|
PersonalData(uuid=uuid4(), workspace_id=ws.id, name="A", created_at=now)
|
|
)
|
|
parent_b = await personal_data_repo.create(
|
|
PersonalData(uuid=uuid4(), workspace_id=ws.id, name="B", created_at=now)
|
|
)
|
|
|
|
birth = date.today() + relativedelta(years=2)
|
|
child = _make_child(ws.id, birth)
|
|
await child_repo.create(child)
|
|
|
|
# Both parents sit on a huge untouched pool -> both should be
|
|
# clamped to the per-parent cap, never the per-child one.
|
|
fetch_a = _make_fetch(parent_a.uuid, ws.id, child.id, total=240, used=0)
|
|
fetch_b = _make_fetch(parent_b.uuid, ws.id, child.id, total=240, used=0)
|
|
await parental_leave_repo.create(fetch_a)
|
|
await parental_leave_repo.create(fetch_b)
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
|
|
proj_a = await service.get_days_left_at_age_4(parent_a.uuid, child.id)
|
|
proj_b = await service.get_days_left_at_age_4(parent_b.uuid, child.id)
|
|
assert proj_a.savable_cap_at_b4 == 48
|
|
assert proj_b.savable_cap_at_b4 == 48
|
|
assert proj_a.savable_after_b4 == 48
|
|
assert proj_b.savable_after_b4 == 48
|
|
assert proj_a.expiring_at_b4 == 192 # 240 - 48
|
|
assert proj_b.expiring_at_b4 == 192
|
|
|
|
overview = await service.get_overview(ws.id, child.id)
|
|
combined_savable = sum(p.savable_after_b4 for p in overview.parent_projections)
|
|
assert combined_savable == 96 # never 192 — this is the headline regression
|
|
assert overview.combined_expiring_at_b4 == 384 # (240-48) * 2
|
|
|
|
async def test_single_member_workspace_falls_back_to_96(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
) -> None:
|
|
"""Safety belt: a workspace with only one member so far (partner not
|
|
|
|
added yet) must still use the per-child (96) figure — there is no
|
|
second parent to divide it with.
|
|
"""
|
|
from app.entities.personal_data import PersonalData
|
|
from app.entities.workspace import Workspace
|
|
|
|
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="A", created_at=now)
|
|
)
|
|
birth = date.today() + relativedelta(years=2)
|
|
child = _make_child(ws.id, birth)
|
|
await child_repo.create(child)
|
|
fetch = _make_fetch(parent.uuid, ws.id, child.id, total=240, used=0)
|
|
await parental_leave_repo.create(fetch)
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
proj = await service.get_days_left_at_age_4(parent.uuid, child.id)
|
|
assert proj.savable_cap_at_b4 == 96
|
|
assert proj.savable_after_b4 == 96
|
|
assert proj.expiring_at_b4 == 144 # 240 - 96
|
|
|
|
|
|
class TestSingleParentWorkspaceCap:
|
|
"""The age-4 cap is purely a function of parent count (no separate
|
|
|
|
"planning mode" flag exists): a workspace with a single member gets the
|
|
full per-child figure (96); `TestCoupleWorkspacePerParentCap` above
|
|
covers the two-parent 48/48 split.
|
|
"""
|
|
|
|
async def test_genuine_single_parent_workspace_gets_96(
|
|
self,
|
|
child_repo: SQLiteChildRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
seeded_ids: SeededIds,
|
|
) -> None:
|
|
ws = seeded_ids.workspace_id
|
|
pd_uuid = seeded_ids.personal_data_uuid
|
|
birth = date.today() + relativedelta(years=2)
|
|
child = _make_child(ws, birth)
|
|
await child_repo.create(child)
|
|
fetch = _make_fetch(pd_uuid, ws, child.id, total=240, used=0)
|
|
await parental_leave_repo.create(fetch)
|
|
|
|
service = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
proj = await service.get_days_left_at_age_4(pd_uuid, child.id)
|
|
assert proj.savable_cap_at_b4 == 96
|
|
assert proj.savable_after_b4 == 96
|
|
|
|
|
|
class TestAgreesWithPlanProposalForTheSameCommittedState:
|
|
"""Bug A regression: the Dashboard (`AnalyticsService`) and the Plan tab's
|
|
propose-time projection (`allocation_service._Ledger.build_age4_projections`,
|
|
via `Allocator`/`AllocationInputLoader`) must compute the same
|
|
`expiring_at_b4` for a parent with no FK snapshot yet and no leave wishes
|
|
(so the proposal adds nothing new) — i.e. the *committed* state.
|
|
|
|
Both surfaces derive their no-snapshot default through the same shared
|
|
`app.utils.tip_predicates.default_tier_pool_share(rules, parent_count)`
|
|
helper, so they can never disagree again: a genuinely SOLE parent (this
|
|
workspace has exactly one member, "Sam") is entitled to the WHOLE
|
|
child's combined tier totals (`rules.sjukpenning_days` +
|
|
`rules.lagsta_niva_days` == 390 + 90 == 480) since there's no partner to
|
|
split the pool with; a partnered parent would instead get their fair
|
|
half-share (195 + 45 == 240). This test pins the two surfaces together.
|
|
"""
|
|
|
|
async def test_no_snapshot_no_wishes_matches_dashboard(
|
|
self,
|
|
workspace_repo: SQLiteWorkspaceRepository,
|
|
child_repo: SQLiteChildRepository,
|
|
personal_data_repo: SQLitePersonalDataRepository,
|
|
parental_leave_repo: SQLiteParentalLeaveRepository,
|
|
planned_leave_repo: SQLitePlannedLeaveRepository,
|
|
vacation_repo: SQLiteVacationDaysRepository,
|
|
leave_wish_repo: SQLiteLeaveWishRepository,
|
|
) -> None:
|
|
from app.entities.personal_data import PersonalData
|
|
from app.entities.workspace import Workspace
|
|
from app.services.allocation_service import AllocationInputLoader, Allocator
|
|
|
|
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="Sam",
|
|
salary_type="monthly",
|
|
employment_status="employed",
|
|
created_at=now,
|
|
)
|
|
)
|
|
birth = date.today() + relativedelta(years=2)
|
|
child = _make_child(ws.id, birth)
|
|
await child_repo.create(child)
|
|
# Deliberately no ParentalLeaveFetch and no leave wishes for `parent`
|
|
# -> both surfaces must fall back to default entitlement.
|
|
|
|
# -- Dashboard side --
|
|
analytics = await _make_service(
|
|
child_repo, parental_leave_repo, planned_leave_repo, personal_data_repo, workspace_repo
|
|
)
|
|
dashboard_proj = await analytics.get_days_left_at_age_4(parent.uuid, child.id)
|
|
|
|
# -- Plan-tab side --
|
|
loader = AllocationInputLoader(
|
|
workspace_repo=workspace_repo,
|
|
child_repo=child_repo,
|
|
personal_data_repo=personal_data_repo,
|
|
parental_leave_repo=parental_leave_repo,
|
|
vacation_repo=vacation_repo,
|
|
planned_leave_repo=planned_leave_repo,
|
|
leave_wish_repo=leave_wish_repo,
|
|
)
|
|
alloc_input = await loader.load(ws.id, today=date.today())
|
|
proposal = Allocator().propose(alloc_input)
|
|
assert proposal.days == [] # no wishes -> nothing new proposed
|
|
plan_proj = next(
|
|
p
|
|
for p in proposal.age4_projections
|
|
if p.parent_personal_data_uuid == parent.uuid and p.child_id == child.id
|
|
)
|
|
|
|
assert dashboard_proj.projected_left_at_b4 == plan_proj.projected_left_at_b4
|
|
assert dashboard_proj.savable_after_b4 == plan_proj.savable_after_b4
|
|
assert dashboard_proj.expiring_at_b4 == plan_proj.expiring_at_b4
|
|
# Pin the actual figures too, so a future regression of either
|
|
# fallback (240 vs. 480) fails loudly rather than merely disagreeing.
|
|
assert dashboard_proj.projected_left_at_b4 == 480
|
|
assert dashboard_proj.expiring_at_b4 == 384
|