# myapp/repositories/service_availability.py # ------------------------------------------------------------ # Persistence layer for the ServiceAvailability table. # ------------------------------------------------------------ from __future__ import annotations from typing import List, Optional, Sequence, Any from ..db import BaseRepository from ..models import ServiceAvailability as ServiceAvailabilityModel class ServiceAvailabilityRepository(BaseRepository[ServiceAvailabilityModel]): """ CRUD + query helpers for the ``ServiceAvailability`` table. The table records which members are allowed to receive which service‑type slots (e.g. “9 AM”, “11 AM”, “6 PM”). All SQL is parameterised to stay safe from injection attacks. """ # ------------------------------------------------------------------ # Table‑level constants – change them in one place if the schema evolves. # ------------------------------------------------------------------ _TABLE = "ServiceAvailability" _PK = "ServiceAvailabilityId" # ------------------------------------------------------------------ # Basic CRUD helpers # ------------------------------------------------------------------ def create( self, member_id: int, service_type_id: int, ) -> ServiceAvailabilityModel: """ Insert a new availability row. The ``UNIQUE (MemberId, ServiceTypeId)`` constraint guarantees idempotency – if the pair already exists SQLite will raise an ``IntegrityError``. To make the operation truly idempotent we first check for an existing row and return it unchanged. """ existing = self.get(member_id, service_type_id) if existing: return existing avail = ServiceAvailabilityModel( ServiceAvailabilityId=-1, # placeholder – will be overwritten MemberId=member_id, ServiceTypeId=service_type_id, ) return self._insert(self._TABLE, avail, self._PK) def get( self, member_id: int, service_type_id: int, ) -> Optional[ServiceAvailabilityModel]: """ Retrieve a single availability record for the given member / service‑type pair, or ``None`` if it does not exist. """ sql = f""" SELECT * FROM {self._TABLE} WHERE MemberId = ? AND ServiceTypeId = ? """ row = self.db.fetchone(sql, (member_id, service_type_id)) return ServiceAvailabilityModel.from_row(row) if row else None def delete(self, availability_id: int) -> None: """ Hard‑delete an availability row by its primary key. Use with care – most callers will prefer ``revoke`` (by member & service type) which is a bit more expressive. """ sql = f"DELETE FROM {self._TABLE} WHERE {self._PK} = ?" self.db.execute(sql, (availability_id,)) # ------------------------------------------------------------------ # Convenience “grant / revoke” helpers (the most common ops) # ------------------------------------------------------------------ def grant(self, member_id: int, service_type_id: int) -> ServiceAvailabilityModel: """ Public API to give a member permission for a particular service slot. Internally delegates to ``create`` which already handles the idempotent‑check. """ return self.create(member_id, service_type_id) def revoke(self, member_id: int, service_type_id: int) -> None: """ Remove a member’s permission for a particular service slot. """ sql = f""" DELETE FROM {self._TABLE} WHERE MemberId = ? AND ServiceTypeId = ? """ self.db.execute(sql, (member_id, service_type_id)) # ------------------------------------------------------------------ # Query helpers used by the scheduling service # ------------------------------------------------------------------ def list_by_member(self, member_id: int) -> List[ServiceAvailabilityModel]: """ Return every ``ServiceAvailability`` row that belongs to the given member. Handy for building a member’s personal “available slots” view. """ sql = f""" SELECT * FROM {self._TABLE} WHERE MemberId = ? """ rows = self.db.fetchall(sql, (member_id,)) return [ServiceAvailabilityModel.from_row(r) for r in rows] def list_by_service_type(self, service_type_id: int) -> List[ServiceAvailabilityModel]: """ Return all members that are allowed to receive the given service type. """ sql = f""" SELECT * FROM {self._TABLE} WHERE ServiceTypeId = ? """ rows = self.db.fetchall(sql, (service_type_id,)) return [ServiceAvailabilityModel.from_row(r) for r in rows] def list_all(self) -> List[ServiceAvailabilityModel]: """ Return every row in the table – useful for admin dashboards or bulk‑export scripts. """ return self._select_all(self._TABLE, ServiceAvailabilityModel) # ------------------------------------------------------------------ # Helper for the round‑robin scheduler # ------------------------------------------------------------------ def members_for_type(self, service_type_id: int) -> List[int]: """ Return a flat list of ``MemberId`` values that are eligible for the supplied ``service_type_id``. The scheduling service can then intersect this list with the pool of members that have the correct classification, activity flag, etc. """ sql = f""" SELECT MemberId FROM {self._TABLE} WHERE ServiceTypeId = ? """ rows = self.db.fetchall(sql, (service_type_id,)) # ``rows`` is a sequence of sqlite3.Row objects; each row acts like a dict. return [row["MemberId"] for row in rows]