Files
nimbusflow/backend/repositories/service_availability.py

158 lines
6.1 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
servicetype slots (e.g. “9AM”, “11AM”, “6PM”). All SQL is
parameterised to stay safe from injection attacks.
"""
# ------------------------------------------------------------------
# Tablelevel 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 /
servicetype 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:
"""
Harddelete 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
idempotentcheck.
"""
return self.create(member_id, service_type_id)
def revoke(self, member_id: int, service_type_id: int) -> None:
"""
Remove a members 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 members 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
bulkexport scripts.
"""
return self._select_all(self._TABLE, ServiceAvailabilityModel)
# ------------------------------------------------------------------
# Helper for the roundrobin 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]