feat(backend): consolidate queue logic for scheduling

This commit is contained in:
2025-08-21 17:17:42 -04:00
parent 8f0dc0d658
commit d0dbba21fb
19 changed files with 1256 additions and 146 deletions

View File

@@ -1,6 +1,22 @@
# database/models/__init__.py
# backend/database/models/__init__.py
from .classification import Classification
from .member import Member
from .service_type import ServiceType
from .servicetype import ServiceType
from .service import Service
from .service_availability import ServiceAvailability
from .serviceavailability import ServiceAvailability
from .schedule import Schedule
from .acceptedlog import AcceptedLog
from .declinelog import DeclineLog
from .scheduledlog import ScheduledLog
__all__ = [
"Classification",
"Member",
"ServiceType",
"Service",
"ServiceAvailability",
"Schedule",
"AcceptedLog",
"DeclineLog",
"ScheduledLog",
]

View File

@@ -0,0 +1,49 @@
from __future__ import annotations
from dataclasses import dataclass, asdict, fields
from datetime import date, datetime
from typing import Any, Dict, Tuple, Type, TypeVar, Union
Row = Tuple[Any, ...] | Dict[str, Any] # what sqlite3.Row returns
T = TypeVar("T", bound="BaseModel")
@dataclass()
class BaseModel:
"""A tiny helper that gives every model a common interface."""
@classmethod
def from_row(cls: Type[T], row: Row) -> T:
"""
Build a model instance from a sqlite3.Row (or a dictlike object).
Column names are matched to the dataclass field names.
"""
if isinstance(row, dict):
data = row
else: # sqlite3.Row behaves like a mapping, but we guard for safety
data = dict(row)
# Convert raw strings to proper Python types where we know the annotation
converted: Dict[str, Any] = {}
for f in fields(cls):
value = data.get(f.name)
if value is None:
converted[f.name] = None
continue
# datetime/date handling sqlite returns str in ISO format
if f.type is datetime:
converted[f.name] = datetime.fromisoformat(value)
elif f.type is date:
converted[f.name] = date.fromisoformat(value)
else:
converted[f.name] = value
return cls(**converted) # type: ignore[arg-type]
def to_dict(self) -> Dict[str, Any]:
"""Return a plain dict (useful for INSERT/UPDATE statements)."""
return asdict(self)
def __repr__(self) -> str: # a nicer representation when printing
field_vals = ", ".join(f"{f.name}={getattr(self, f.name)!r}" for f in fields(self))
return f"{self.__class__.__name__}({field_vals})"

View File

@@ -0,0 +1,11 @@
from dataclasses import dataclass
from datetime import datetime
from ._base import BaseModel
@dataclass()
class AcceptedLog(BaseModel):
LogId: int
MemberId: int
ServiceId: int
AcceptedAt: datetime

View File

@@ -1,14 +1,8 @@
from ..connection import DatabaseConnection
from dataclasses import dataclass
from ._base import BaseModel
class Classification:
def __init__(self, classification_name: str):
self.classification_name = classification_name
def save(self, db: DatabaseConnection):
query = "INSERT INTO Classifications (ClassificationName) VALUES (?)"
db.execute_query(query, (self.classification_name,))
@classmethod
def get_all(cls, db: DatabaseConnection):
query = "SELECT * FROM Classifications"
return db.execute_query_with_return(query)
@dataclass()
class Classification(BaseModel):
ClassificationId: int
ClassificationName: str

View File

@@ -0,0 +1,14 @@
from dataclasses import dataclass
from datetime import datetime, date
from typing import Optional
from ._base import BaseModel
@dataclass()
class DeclineLog(BaseModel):
DeclineId: int
MemberId: int
ServiceId: int
DeclinedAt: datetime
DeclineDate: date # the service day that was declined
Reason: Optional[str] = None

View File

@@ -1,19 +1,20 @@
from ..connection import DatabaseConnection
from dataclasses import dataclass
from datetime import datetime, date
from typing import Optional
from ._base import BaseModel
class Member:
def __init__(self, first_name: str, last_name: str, email: str, phone_number: str, classification_id: int, notes: str = None):
self.first_name = first_name
self.last_name = last_name
self.email = email
self.phone_number = phone_number
self.classification_id = classification_id
self.notes = notes
def save(self, db: DatabaseConnection):
query = "INSERT INTO Members (FirstName, LastName, Email, PhoneNumber, ClassificationId, Notes) VALUES (?, ?, ?, ?, ?, ?)"
db.execute_query(query, (self.first_name, self.last_name, self.email, self.phone_number, self.classification_id, self.notes))
@classmethod
def get_all(cls, db: DatabaseConnection):
query = "SELECT * FROM Members"
return db.execute_query_with_return(query)
@dataclass
class Member(BaseModel):
MemberId: int
FirstName: str
LastName: str
Email: Optional[str] = None
PhoneNumber: Optional[str] = None
ClassificationId: Optional[int] = None
Notes: Optional[str] = None
IsActive: int = 1
LastScheduledAt: Optional[datetime] = None
LastAcceptedAt: Optional[datetime] = None
LastDeclinedAt: Optional[datetime] = None
DeclineStreak: int = 0

View File

@@ -0,0 +1,17 @@
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
from ._base import BaseModel
@dataclass
class Schedule(BaseModel):
ScheduleId: int
ServiceId: int
MemberId: int
Status: str # 'pending' | 'accepted' | 'declined'
ScheduledAt: datetime # renamed from OfferedAt
AcceptedAt: Optional[datetime] = None
DeclinedAt: Optional[datetime] = None
ExpiresAt: Optional[datetime] = None
DeclineReason: Optional[str] = None

View File

@@ -0,0 +1,12 @@
from dataclasses import dataclass
from datetime import datetime
from ._base import BaseModel
@dataclass()
class ScheduledLog(BaseModel):
LogId: int
MemberId: int
ServiceId: int
ScheduledAt: datetime
ExpiresAt: datetime

View File

@@ -1,15 +1,10 @@
from ..connection import DatabaseConnection
from dataclasses import dataclass
from datetime import date
from ._base import BaseModel
class Service:
def __init__(self, service_type_id: int, service_date: str):
self.service_type_id = service_type_id
self.service_date = service_date
def save(self, db: DatabaseConnection):
query = "INSERT INTO Services (ServiceTypeId, ServiceDate) VALUES (?, ?)"
db.execute_query(query, (self.service_type_id, self.service_date))
@classmethod
def get_all(cls, db: DatabaseConnection):
query = "SELECT * FROM Services"
return db.execute_query_with_return(query)
@dataclass()
class Service(BaseModel):
ServiceId: int
ServiceTypeId: int
ServiceDate: date

View File

@@ -1,15 +0,0 @@
from ..connection import DatabaseConnection
class ServiceAvailability:
def __init__(self, member_id: int, service_type_id: int):
self.member_id = member_id
self.service_type_id = service_type_id
def save(self, db: DatabaseConnection):
query = "INSERT INTO ServiceAvailability (MemberId, ServiceTypeId) VALUES (?, ?)"
db.execute_query(query, (self.member_id, self.service_type_id))
@classmethod
def get_all(cls, db: DatabaseConnection):
query = "SELECT * FROM ServiceAvailability"
return db.execute_query_with_return(query)

View File

@@ -1,14 +0,0 @@
from ..connection import DatabaseConnection
class ServiceType:
def __init__(self, type_name: str):
self.type_name = type_name
def save(self, db: DatabaseConnection):
query = "INSERT INTO ServiceTypes (TypeName) VALUES (?)"
db.execute_query(query, (self.type_name,))
@classmethod
def get_all(cls, db: DatabaseConnection):
query = "SELECT * FROM ServiceTypes"
return db.execute_query_with_return(query)

View File

@@ -0,0 +1,9 @@
from dataclasses import dataclass
from ._base import BaseModel
@dataclass()
class ServiceAvailability(BaseModel):
ServiceAvailabilityId: int
MemberId: int
ServiceTypeId: int

View File

@@ -0,0 +1,8 @@
from dataclasses import dataclass
from ._base import BaseModel
@dataclass()
class ServiceType(BaseModel):
ServiceTypeId: int
TypeName: str