import typing from datetime import date, datetime from sqlalchemy import ForeignKey from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship from sqlalchemy.types import Text class Base(DeclarativeBase): pass WorkTypes = typing.Literal["cooking", "dishes", "tables"] class Event(Base): __tablename__ = "event" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) title: Mapped[str] = mapped_column(nullable=False) event_time: Mapped[datetime] = mapped_column(nullable=False) registration_deadline: Mapped[datetime] = mapped_column(nullable=False) description: Mapped[str] = mapped_column(nullable=True) recipe_link: Mapped[str] = mapped_column(nullable=True) # Min and max number of people needed for cooking, doing the dishes and preparing the tables team_cooking_min: Mapped[int] = mapped_column(default=3, nullable=False) team_cooking_max: Mapped[int] = mapped_column(default=5, nullable=False) team_dishes_min: Mapped[int] = mapped_column(default=3, nullable=False) team_dishes_max: Mapped[int] = mapped_column(default=5, nullable=False) # Todo: Rename to "table" team_prep_min: Mapped[int] = mapped_column(default=1, nullable=False) team_prep_max: Mapped[int] = mapped_column(default=1, nullable=False) subscriptions_applied: Mapped[bool] = mapped_column(default=False, nullable=False) ignore_subscriptions: Mapped[bool] = mapped_column(default=False, nullable=False) registrations: Mapped[list["Registration"]] = relationship( "Registration", cascade="all, delete" ) team: Mapped[list["TeamRegistration"]] = relationship( "TeamRegistration", cascade="all, delete" ) def team_min_reached(self, work_type: WorkTypes): threshold = { "cooking": self.team_cooking_min, "dishes": self.team_dishes_min, "tables": self.team_prep_min, }[work_type] return sum(1 for t in self.team if t.work_type == work_type) >= threshold def team_max_reached(self, work_type: WorkTypes): threshold = { "cooking": self.team_cooking_max, "dishes": self.team_dishes_max, "tables": self.team_prep_max, }[work_type] return sum(1 for t in self.team if t.work_type == work_type) >= threshold def all_teams_min(self): return all( self.team_min_reached(work_type) for work_type in typing.get_args(WorkTypes) ) def all_teams_max(self): return all( self.team_max_reached(work_type) for work_type in typing.get_args(WorkTypes) ) @property def registration_open(self): return datetime.now() < self.registration_deadline @property def in_the_past(self): return datetime.now() > self.event_time class TeamRegistration(Base): __tablename__ = "teamregistration" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) event_id: Mapped[int] = mapped_column(ForeignKey("event.id")) person_name: Mapped[str] = mapped_column(nullable=False) work_type: Mapped[WorkTypes] = mapped_column(Text, nullable=False) comment: Mapped[str | None] = mapped_column() class Household(Base): __tablename__ = "household" id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(nullable=False) class Registration(Base): __tablename__ = "registration" event_id: Mapped[int] = mapped_column(ForeignKey("event.id"), primary_key=True) household_id: Mapped[int] = mapped_column( ForeignKey("household.id"), primary_key=True ) num_adult_meals: Mapped[int] = mapped_column(nullable=False) num_children_meals: Mapped[int] = mapped_column(nullable=False) num_small_children_meals: Mapped[int] = mapped_column(nullable=False) comment: Mapped[str | None] = mapped_column() household: Mapped["Household"] = relationship() class Subscription(Base): __tablename__ = "subscription" household_id: Mapped[int] = mapped_column( ForeignKey("household.id"), primary_key=True ) num_adult_meals: Mapped[int] = mapped_column(nullable=False) num_children_meals: Mapped[int] = mapped_column(nullable=False) num_small_children_meals: Mapped[int] = mapped_column(nullable=False) comment: Mapped[str | None] = mapped_column() last_modified: Mapped[datetime] = mapped_column( default=datetime.now, nullable=False ) monday: Mapped[bool] = mapped_column(default=True, nullable=False) tuesday: Mapped[bool] = mapped_column(default=True, nullable=False) wednesday: Mapped[bool] = mapped_column(default=True, nullable=False) thursday: Mapped[bool] = mapped_column(default=True, nullable=False) friday: Mapped[bool] = mapped_column(default=True, nullable=False) saturday: Mapped[bool] = mapped_column(default=True, nullable=False) sunday: Mapped[bool] = mapped_column(default=True, nullable=False) household: Mapped["Household"] = relationship() def day_string_de(self) -> str: """ Generates a string representation of selected days in German short form. """ result = [] if self.monday: result.append("Mo") if self.tuesday: result.append("Di") if self.wednesday: result.append("Mi") if self.thursday: result.append("Do") if self.friday: result.append("Fr") if self.saturday: result.append("Sa") if self.sunday: result.append("So") if len(result) < 7: return ", ".join(result) else: return "Alle" def valid_on(self, date: date) -> bool: weekday = date.weekday() return { 0: self.monday, 1: self.tuesday, 2: self.wednesday, 3: self.thursday, 4: self.friday, 5: self.saturday, 6: self.sunday, }[weekday]