Let a day removed from a period stay removed

Taking one day out of a period the planner placed did nothing that
lasted: the period still asked for a full week, so the next sync put the
day straight back. The option was disabled rather than lying about it.

A removed day is now recorded as its own thing — no leave for this
parent, this child, this date — and the planner honours it on every
later sync. It is kept apart from the working-day overrides, which
answer a different question about the calendar rather than about the
plan.

The day belongs to the parent whose band was clicked, so removing one
parent's day leaves their partner's alone; where both parents were to be
off on one funding arrangement, the partner is simply funded on their
own instead. A removed day shortens what the period is asking for rather
than counting as a day the plan failed to find, so the advice about
closing a shortfall does not start arguing for a day nobody wants.

Nothing is removed without a way back: the toast undoes it, and the day
itself is marked in the calendar and offers to restore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ei7gCZzdd751uZtPzb1Pep
This commit is contained in:
2026-09-19 16:05:17 +02:00
parent 6d92b87bf3
commit 8cfb9c82c6
39 changed files with 2362 additions and 31 deletions
@@ -0,0 +1,40 @@
"""Domain entity: PlannedDayExclusion.
"The planner may not place leave for THIS parent, for THIS child, on THIS
date." One row is one user instruction, recorded when the user removes a
single day out of a planner-placed period ("this day" in the calendar's
three-way remove). Without it, the next `POST /api/plan/sync` would simply
re-derive the day from the wish — the wish still says "a full week", and
nothing anywhere said that one date was off the table.
Deliberately NOT modelled as a `DayOverride`. A `DayOverride` answers a
*calendar* question ("is this date a working day for this workspace?"), is
workspace-wide, and is applied inside `is_free_day`. This answers a
*planning* question ("may a leave day be placed here?"), is per parent and
per child, and is applied where a window is funded. Folding the second into
the first would mean a removed Tuesday silently became a non-working day for
everyone and everything, analytics included.
Keyed per parent (see `PlannedDayExclusionService`): a both-parents period
places one day per parent on the same date, and removing one parent's day
must not silently remove the other's. The allocator only drops the date from
a window entirely when no responsible parent may take it — see
`_Ledger._excluded_window_dates`.
"""
from dataclasses import dataclass
from datetime import date, datetime
from uuid import UUID
@dataclass
class PlannedDayExclusion:
"""One (workspace, child, parent, date) the planner must leave empty."""
id: UUID
workspace_id: UUID
child_id: UUID
parent_personal_data_uuid: UUID
date: date
created_at: datetime | None = None
updated_at: datetime | None = None