Improve apply_subscriptions

Events now have a subscriptions_applied flag. Using apply_subscriptions without passing an event now processes all events in the next week, which have not yet been processed
This commit is contained in:
2025-10-27 15:32:00 +01:00
parent a2722d5f9f
commit b687a260c5
8 changed files with 95 additions and 22 deletions

1
.gitignore vendored
View File

@@ -4,3 +4,4 @@ test.php
**/__pycache__ **/__pycache__
database.db* database.db*
.idea .idea
db_fixtures

9
justfile Normal file
View File

@@ -0,0 +1,9 @@
lint:
uv run isort src
uv run black src
reset_db:
rm -f database.db
touch database.db
alembic upgrade heads
bash -c 'shopt -s nullglob; for file in db_fixtures/*.sql; do sqlite3 database.db < "$file"; done'

View File

@@ -24,7 +24,7 @@ dev = [
] ]
[project.scripts] [project.scripts]
apply-subscriptions = "meal_manager.scripts:apply_subscriptions" apply-subscriptions = "meal_manager.scripts:apply_subscriptions_cli"
[tool.isort] [tool.isort]
@@ -36,7 +36,7 @@ profile = "black"
# this is typically a path given in POSIX (e.g. forward slashes) # this is typically a path given in POSIX (e.g. forward slashes)
# format, relative to the token %(here)s which refers to the location of this # format, relative to the token %(here)s which refers to the location of this
# ini file # ini file
script_location = "%(here)s/alembic" script_location = "%(here)s/src/meal_manager/alembic"
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s # template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
# Uncomment the line below if you want the files to be prepended with date and time # Uncomment the line below if you want the files to be prepended with date and time

View File

@@ -84,7 +84,7 @@ def upgrade() -> None:
sa.PrimaryKeyConstraint("household_id"), sa.PrimaryKeyConstraint("household_id"),
) )
op.create_table( op.create_table(
"team_registration", "teamregistration",
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False), sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
sa.Column("event_id", sa.Integer(), nullable=False), sa.Column("event_id", sa.Integer(), nullable=False),
sa.Column("person_name", sa.String(), nullable=False), sa.Column("person_name", sa.String(), nullable=False),

View File

@@ -0,0 +1,34 @@
"""Add subscriptions_applied column to Event
Revision ID: 13084c5c1f68
Revises: 299a83240036
Create Date: 2025-10-27 12:25:14.633641
"""
from typing import Sequence, Union
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "13084c5c1f68"
down_revision: Union[str, Sequence[str], None] = "299a83240036"
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None
def upgrade() -> None:
"""Upgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.add_column(
"event", sa.Column("subscriptions_applied", sa.Boolean(), nullable=False)
)
# ### end Alembic commands ###
def downgrade() -> None:
"""Downgrade schema."""
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("event", "subscriptions_applied")
# ### end Alembic commands ###

View File

@@ -33,6 +33,8 @@ class Event(Base):
team_prep_min: Mapped[int] = mapped_column(default=1, nullable=False) team_prep_min: Mapped[int] = mapped_column(default=1, nullable=False)
team_prep_max: 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)
registrations: Mapped[list["Registration"]] = relationship( registrations: Mapped[list["Registration"]] = relationship(
"Registration", cascade="all, delete" "Registration", cascade="all, delete"
) )

View File

@@ -1,33 +1,38 @@
import argparse import argparse
import datetime
from sqlalchemy import select from sqlalchemy import Date, cast, func, select
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
from meal_manager.main import engine from meal_manager.main import engine
from meal_manager.models import Event, Registration, Subscription from meal_manager.models import Event, Registration, Subscription
def apply_subscriptions(): def apply_subscriptions(session: Session, event: Event = None, dry_run: bool = False):
parser = argparse.ArgumentParser(description="Apply subscriptions for an event")
parser.add_argument("event_id", type=int, help="Event ID (required)")
parser.add_argument(
"--dry-run", action="store_true", help="Run without making changes"
)
args = parser.parse_args() subscriptions = session.scalars(select(Subscription)).all()
# Access the arguments if event is not None:
event_id = args.event_id events = [event]
dry_run = args.dry_run else:
today = datetime.date.today()
query = select(Event).where(
~Event.subscriptions_applied,
func.strftime("%Y-%m-%d %H:%M:%S", Event.event_time) >= today.isoformat(),
func.strftime("%Y-%m-%d %H:%M:%S", Event.event_time)
<= (today + datetime.timedelta(days=7)).isoformat(),
)
events = session.scalars(query).all()
with Session(engine) as session: if len(events) == 0:
subscriptions = session.scalars(select(Subscription)).all() print("No events to process")
event = session.scalars(select(Event).where(Event.id == event_id)).one() return
for event in events:
if dry_run: if dry_run:
print(f"DRY RUN: Would process event {event_id}") print(f"DRY RUN: Would process event {event.title} ({event.id})")
else: else:
print(f"Processing event {event_id}") print(f"Processing event {event.title} ({event.id})")
print(f"There are {len(subscriptions)} subscriptions to process") print(f"There are {len(subscriptions)} subscriptions to process")
relevant_subscriptions = [ relevant_subscriptions = [
@@ -60,6 +65,28 @@ def apply_subscriptions():
else: else:
session.add(reg) session.add(reg)
print(f"Registered {subscription.household.name}") print(f"Registered {subscription.household.name}")
event.subscriptions_applied = True
if not dry_run: if not dry_run:
session.commit() session.commit()
def apply_subscriptions_cli():
parser = argparse.ArgumentParser(description="Apply subscriptions for an event")
parser.add_argument("--event_id", type=int, help="Event ID (required)")
parser.add_argument(
"--dry-run", action="store_true", help="Run without making changes"
)
args = parser.parse_args()
# Access the arguments
event_id = args.event_id
dry_run = args.dry_run
with Session(engine) as session:
if event_id is not None:
event = session.scalars(select(Event).where(Event.id == event_id)).one()
apply_subscriptions(session, event, dry_run)
else:
apply_subscriptions(session, dry_run=dry_run)