Complete NextSteps roadmap: planning engine UI, calendar cluster, flexible wishes
Works through every numbered item in NextSteps260913.md section 3, plus the three known verification issues in section 4.1. Surface what the backend already computed: - 3.1 render surplus_advice in PlanResults, mirroring the shortfall remedies - 3.2 surface VacationBalance.is_assumed so assumed statutory years are visible - 3.3 age-4 expiry given the proposed plan, via a new Age4Projection reusing the existing _age4_projections arithmetic (Dashboard stat verified, not rebuilt) Calendar cluster (one pass over the holiday data): - new GET /api/calendar/days exposing roda dagar, klamdagar and day overrides, paginated four years at a time - 3.4 shade roda dagar/klamdagar, with R/K badges so colour is not the only signal - 3.6 click-to-override behind an explicit mode toggle, leaving drag multi-select intact - 3.7 click month/year to advance - 3.8 per-year red-days list in Settings, statutory days plus overrides i18n: - 3.5 five backend-generated Swedish surfaces converted to message_key + data, following the tips_service pattern; old string fields replaced, not kept Flexible wishes (3.9): - PLANNING_ENGINE_SPEC section 10: window_mode, candidate search, cost function, deterministic tie-break, two-pass ordering, test matrix rows 15-24 - backend: entity, migration 006, _FULL_SCHEMA sync, repository, allocator - frontend: season-preset toggle in WishEditor, unplaced outcome rendering Fixes (4.1): - pin typecheck to the local tsc rather than npx resolving a newer TypeScript - ignore generated coverage/ and dist/ in the ESLint flat config - fix the flaky usePlanApply test properly, with waitFor rather than a retry Tests: backend 361 -> 404, frontend 540 -> 704. Coverage 89.47% -> 96.52% lines. Not yet verified in a browser. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RXMhc9nT6sNz1SL4j8dznL
This commit is contained in:
@@ -4,11 +4,15 @@ Demonstrates service-layer testing with external service dependencies.
|
||||
Aim for >=90% coverage on new code (ARCHITECTURE.md §10).
|
||||
"""
|
||||
|
||||
from datetime import date
|
||||
from datetime import date, datetime
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
|
||||
from app.entities.child import Child
|
||||
from app.entities.personal_data import PersonalData
|
||||
from app.repositories_impl.sqlite.child_repository import SQLiteChildRepository
|
||||
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
|
||||
@@ -40,7 +44,13 @@ class TestPlanningService:
|
||||
planning_service: PlanningService,
|
||||
seeded_ids: SeededIds,
|
||||
) -> None:
|
||||
"""Adding a second planned day on same date raises ValidationError."""
|
||||
"""Adding a second planned day on same date raises ValidationError.
|
||||
|
||||
Note: This test verifies that the same parent cannot plan the same date twice.
|
||||
The error message was updated to be more accurate: "Parent already has a planned
|
||||
day" instead of the previous generic "A planned day already exists" (which
|
||||
incorrectly rejected different parents planning the same date as a dubbeldag).
|
||||
"""
|
||||
child_id = seeded_ids.child_id
|
||||
|
||||
await planning_service.add_planned_day(
|
||||
@@ -53,7 +63,7 @@ class TestPlanningService:
|
||||
)
|
||||
|
||||
# Try to add another on same date
|
||||
with pytest.raises(ValidationError, match="already exists"):
|
||||
with pytest.raises(ValidationError, match="Parent already has a planned day"):
|
||||
await planning_service.add_planned_day(
|
||||
workspace_id=seeded_ids.workspace_id,
|
||||
child_id=child_id,
|
||||
@@ -339,3 +349,141 @@ class TestPlanningService:
|
||||
uuid4(),
|
||||
day_type="lagsta",
|
||||
)
|
||||
|
||||
async def test_add_planned_day_different_parents_same_date_allowed(
|
||||
self,
|
||||
planning_service: PlanningService,
|
||||
personal_data_repo: SQLitePersonalDataRepository,
|
||||
seeded_ids: SeededIds,
|
||||
) -> None:
|
||||
"""Two different parents can plan the same date for the same child (dubbeldagar).
|
||||
|
||||
This is the legitimate dubbeldagar case: parent A has the date planned,
|
||||
parent B can also plan the same date (subject to dubbeldagar validation).
|
||||
Note: The test uses a non-dubbeldag date for parent A and a dubbeldag date
|
||||
for parent B within the 15-month window.
|
||||
"""
|
||||
child_id = seeded_ids.child_id
|
||||
ws = seeded_ids.workspace_id
|
||||
parent_a_uuid = seeded_ids.personal_data_uuid
|
||||
|
||||
# Create a second parent
|
||||
parent_b = await personal_data_repo.create(
|
||||
PersonalData(
|
||||
uuid=uuid4(),
|
||||
workspace_id=ws,
|
||||
name="Test Parent B",
|
||||
created_at=datetime.now(),
|
||||
)
|
||||
)
|
||||
parent_b_uuid = parent_b.uuid
|
||||
|
||||
# Parent A plans 2024-10-01 (within dubbeldagar window, but not as a dubbeldag)
|
||||
await planning_service.add_planned_day(
|
||||
workspace_id=ws,
|
||||
child_id=child_id,
|
||||
parent_personal_data_uuid=parent_a_uuid,
|
||||
date=date(2024, 10, 1),
|
||||
day_type="sjukpenning",
|
||||
is_dubbeldag=False,
|
||||
)
|
||||
|
||||
# Parent B can also plan the same date (this should succeed)
|
||||
# Parent B marks this as a dubbeldag
|
||||
result = await planning_service.add_planned_day(
|
||||
workspace_id=ws,
|
||||
child_id=child_id,
|
||||
parent_personal_data_uuid=parent_b_uuid,
|
||||
date=date(2024, 10, 1),
|
||||
day_type="lagsta",
|
||||
is_dubbeldag=True,
|
||||
)
|
||||
assert result.parent_personal_data_uuid == parent_b_uuid
|
||||
assert result.date == date(2024, 10, 1)
|
||||
assert result.is_dubbeldag is True
|
||||
|
||||
async def test_add_planned_day_same_parent_same_date_rejected(
|
||||
self,
|
||||
planning_service: PlanningService,
|
||||
seeded_ids: SeededIds,
|
||||
) -> None:
|
||||
"""Same parent cannot plan the same date twice (genuine double-booking).
|
||||
|
||||
This verifies that the fix for parent-aware checking still rejects
|
||||
the genuine double-booking case where the same parent tries to plan
|
||||
the same date twice.
|
||||
"""
|
||||
child_id = seeded_ids.child_id
|
||||
ws = seeded_ids.workspace_id
|
||||
parent_uuid = seeded_ids.personal_data_uuid
|
||||
|
||||
# Parent plans 2024-09-01
|
||||
await planning_service.add_planned_day(
|
||||
workspace_id=ws,
|
||||
child_id=child_id,
|
||||
parent_personal_data_uuid=parent_uuid,
|
||||
date=date(2024, 9, 1),
|
||||
day_type="sjukpenning",
|
||||
is_dubbeldag=False,
|
||||
)
|
||||
|
||||
# Same parent tries to plan the same date again - should be rejected
|
||||
with pytest.raises(ValidationError, match="already has a planned day"):
|
||||
await planning_service.add_planned_day(
|
||||
workspace_id=ws,
|
||||
child_id=child_id,
|
||||
parent_personal_data_uuid=parent_uuid,
|
||||
date=date(2024, 9, 1),
|
||||
day_type="lagsta",
|
||||
is_dubbeldag=False,
|
||||
)
|
||||
|
||||
async def test_add_planned_day_same_date_different_child_allowed(
|
||||
self,
|
||||
planning_service: PlanningService,
|
||||
child_repo: SQLiteChildRepository,
|
||||
seeded_ids: SeededIds,
|
||||
) -> None:
|
||||
"""Same parent can plan the same date for different children.
|
||||
|
||||
The double-booking check is per (parent, child), so the same parent
|
||||
planning the same date for different children is always allowed.
|
||||
"""
|
||||
ws = seeded_ids.workspace_id
|
||||
parent_uuid = seeded_ids.personal_data_uuid
|
||||
child_1 = seeded_ids.child_id
|
||||
|
||||
# Create a second child
|
||||
child_2 = await child_repo.create(
|
||||
Child(
|
||||
id=uuid4(),
|
||||
workspace_id=ws,
|
||||
name="Test Child 2",
|
||||
birth_date=date(2024, 6, 1),
|
||||
created_at=datetime.now(),
|
||||
)
|
||||
)
|
||||
child_2_id = child_2.id
|
||||
|
||||
# Parent plans 2024-09-01 for child 1
|
||||
await planning_service.add_planned_day(
|
||||
workspace_id=ws,
|
||||
child_id=child_1,
|
||||
parent_personal_data_uuid=parent_uuid,
|
||||
date=date(2024, 9, 1),
|
||||
day_type="sjukpenning",
|
||||
is_dubbeldag=False,
|
||||
)
|
||||
|
||||
# Same parent can plan the same date for child 2
|
||||
result = await planning_service.add_planned_day(
|
||||
workspace_id=ws,
|
||||
child_id=child_2_id,
|
||||
parent_personal_data_uuid=parent_uuid,
|
||||
date=date(2024, 9, 1),
|
||||
day_type="lagsta",
|
||||
is_dubbeldag=False,
|
||||
)
|
||||
assert result.child_id == child_2_id
|
||||
assert result.parent_personal_data_uuid == parent_uuid
|
||||
assert result.date == date(2024, 9, 1)
|
||||
|
||||
Reference in New Issue
Block a user