"""Abstract repository for PlannedLeaveDay calendar events.""" from abc import abstractmethod from datetime import date from uuid import UUID from app.entities.planned_leave import PlannedLeaveDay from app.repositories.base import Repository class PlannedLeaveRepository(Repository[PlannedLeaveDay]): @abstractmethod async def list_by_workspace_child( self, workspace_id: UUID, child_id: UUID, from_date: date | None = None, to_date: date | None = None, ) -> list[PlannedLeaveDay]: """Return planned days for a workspace+child, optionally within a date range.""" @abstractmethod async def count_by_parent_type( self, personal_data_uuid: UUID, child_id: UUID, day_type: str, from_date: date | None = None, to_date: date | None = None, ) -> int: """Count planned days of a given type for a parent+child pair.""" @abstractmethod async def count_dubbeldagar(self, child_id: UUID, from_date: date | None = None) -> int: """Count dubbeldagar already planned for a child (after from_date).""" @abstractmethod async def list_by_wish(self, wish_id: UUID) -> list[PlannedLeaveDay]: """Every row currently attributed to this wish. `LeaveWishService.delete()` reads this BEFORE `delete_by_wish` to learn each row's (workspace, child, parent, date) key, so it can clear any `DayAssertion` sitting at that key too — once the rows are gone there is nothing left to look their keys up from. """ @abstractmethod async def delete_by_wish(self, wish_id: UUID) -> int: """Delete every row attributed to this wish (pinned or not) and return the count deleted. See `LeaveWishService.delete()`: called before the `leave_wish` row is removed so the FK's `ON DELETE SET NULL` never fires — by the time that DELETE runs, no `planned_leave_day` row still references this wish, so there is nothing left to orphan. """ @abstractmethod async def create_many(self, items: list[PlannedLeaveDay]) -> None: """Bulk-insert rows in one round trip (`executemany`). Empty list is a no-op. See backend-perf audit H1: a per-row `create()` + `commit()` loop measured 88.7ms/300 rows (default journal mode); this + one surrounding `unit_of_work()` is the batched replacement used by `PlanningService.apply_proposed_days` stage 4. """ @abstractmethod async def delete_many(self, ids: list[UUID]) -> int: """Delete rows by id in one or more `executemany`/`IN (...)` round trips. Idempotent: an id with no matching row is silently ignored rather than raising `NotFoundError` (unlike `delete()`) — callers that already know some keys may have vanished (e.g. a re-applied batch) should not have to pre-filter. Returns the number of rows actually deleted. """ @abstractmethod async def update_metadata_many(self, pairs: list[tuple[UUID, UUID | None]]) -> None: """Bulk-update `wish_id` for existing rows via `executemany`. Each pair is `(id, wish_id)`. Does not touch `day_type`/ `is_dubbeldag` — this is for the metadata-only path in `apply_proposed_days` (a row whose type is unchanged but whose `wish_id` differs from the incoming batch). `is_pinned` is not a column on this entity any more (restructure step 3b, migration 010) — "pinned" is a `DayAssertion` fact now. """