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:
@@ -10,14 +10,40 @@ from uuid import uuid4
|
||||
import pytest
|
||||
|
||||
from app.entities.child import Child
|
||||
from app.entities.leave_wish import LeaveWish
|
||||
from app.entities.personal_data import PersonalData
|
||||
from app.repositories_impl.sqlite.child_repository import SQLiteChildRepository
|
||||
from app.repositories_impl.sqlite.leave_wish_repository import SQLiteLeaveWishRepository
|
||||
from app.repositories_impl.sqlite.personal_data_repository import SQLitePersonalDataRepository
|
||||
from app.services.planning_service import PlanningService
|
||||
from app.utils.errors import NotFoundError, ValidationError
|
||||
from tests.conftest import SeededIds
|
||||
|
||||
|
||||
def _make_wish(workspace_id, child_id=None, **overrides) -> LeaveWish:
|
||||
base = dict(
|
||||
id=uuid4(),
|
||||
workspace_id=workspace_id,
|
||||
child_id=child_id,
|
||||
name="Test wish",
|
||||
window_start_month=6,
|
||||
window_start_day=1,
|
||||
window_end_month=6,
|
||||
window_end_day=30,
|
||||
target_weeks=1,
|
||||
recurrence="once",
|
||||
first_year=2026,
|
||||
last_year=None,
|
||||
priority=1,
|
||||
parent_mode="either",
|
||||
parent_personal_data_uuid=None,
|
||||
created_at=datetime.now(),
|
||||
updated_at=datetime.now(),
|
||||
)
|
||||
base.update(overrides)
|
||||
return LeaveWish(**base)
|
||||
|
||||
|
||||
class TestPlanningService:
|
||||
"""Test PlanningService business logic."""
|
||||
|
||||
@@ -39,6 +65,49 @@ class TestPlanningService:
|
||||
assert result.day_type == "vacation"
|
||||
assert result.is_dubbeldag is False
|
||||
|
||||
async def test_add_planned_day_with_wish_id_and_is_pinned(
|
||||
self,
|
||||
planning_service: PlanningService,
|
||||
leave_wish_repo: SQLiteLeaveWishRepository,
|
||||
seeded_ids: SeededIds,
|
||||
) -> None:
|
||||
"""Round-trips `wish_id`/`is_pinned` through the repository (create +
|
||||
re-fetch), not just the in-memory object the create call returns."""
|
||||
wish = await leave_wish_repo.create(
|
||||
_make_wish(seeded_ids.workspace_id, seeded_ids.child_id)
|
||||
)
|
||||
|
||||
result = await planning_service.add_planned_day(
|
||||
workspace_id=seeded_ids.workspace_id,
|
||||
child_id=seeded_ids.child_id,
|
||||
parent_personal_data_uuid=seeded_ids.personal_data_uuid,
|
||||
date=date(2026, 6, 15),
|
||||
day_type="vacation",
|
||||
wish_id=wish.id,
|
||||
is_pinned=True,
|
||||
)
|
||||
assert result.wish_id == wish.id
|
||||
assert result.is_pinned is True
|
||||
|
||||
fetched = await planning_service.get(result.id)
|
||||
assert fetched.wish_id == wish.id
|
||||
assert fetched.is_pinned is True
|
||||
|
||||
async def test_add_planned_day_without_wish_id_defaults_to_none(
|
||||
self,
|
||||
planning_service: PlanningService,
|
||||
seeded_ids: SeededIds,
|
||||
) -> None:
|
||||
result = await planning_service.add_planned_day(
|
||||
workspace_id=seeded_ids.workspace_id,
|
||||
child_id=seeded_ids.child_id,
|
||||
parent_personal_data_uuid=seeded_ids.personal_data_uuid,
|
||||
date=date(2026, 6, 15),
|
||||
day_type="vacation",
|
||||
)
|
||||
assert result.wish_id is None
|
||||
assert result.is_pinned is False
|
||||
|
||||
async def test_add_double_booking_raises(
|
||||
self,
|
||||
planning_service: PlanningService,
|
||||
@@ -339,6 +408,36 @@ class TestPlanningService:
|
||||
assert updated.day_type == "sjukpenning"
|
||||
assert updated.is_dubbeldag is True
|
||||
|
||||
async def test_update_planned_day_wish_id_and_is_pinned(
|
||||
self,
|
||||
planning_service: PlanningService,
|
||||
leave_wish_repo: SQLiteLeaveWishRepository,
|
||||
seeded_ids: SeededIds,
|
||||
) -> None:
|
||||
wish = await leave_wish_repo.create(
|
||||
_make_wish(seeded_ids.workspace_id, seeded_ids.child_id)
|
||||
)
|
||||
created = await planning_service.add_planned_day(
|
||||
workspace_id=seeded_ids.workspace_id,
|
||||
child_id=seeded_ids.child_id,
|
||||
parent_personal_data_uuid=seeded_ids.personal_data_uuid,
|
||||
date=date(2026, 6, 15),
|
||||
day_type="vacation",
|
||||
)
|
||||
assert created.wish_id is None
|
||||
|
||||
updated = await planning_service.update(
|
||||
created.id,
|
||||
wish_id=wish.id,
|
||||
is_pinned=True,
|
||||
)
|
||||
assert updated.wish_id == wish.id
|
||||
assert updated.is_pinned is True
|
||||
|
||||
refetched = await planning_service.get(created.id)
|
||||
assert refetched.wish_id == wish.id
|
||||
assert refetched.is_pinned is True
|
||||
|
||||
async def test_update_missing_raises(
|
||||
self,
|
||||
planning_service: PlanningService,
|
||||
|
||||
Reference in New Issue
Block a user