"""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 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, bool]]) -> None: """Bulk-update `(wish_id, is_pinned)` for existing rows via `executemany`. Each pair is `(id, wish_id, is_pinned)`. 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`/`is_pinned` differ from the incoming batch). """