"""create report schedules table Revision ID: efe19595299c Revises: 6512af98fb22 Create Date: 2026-04-07 14:02:00.000000 """ from typing import Sequence, Union from alembic import op import sqlalchemy as sa from sqlalchemy.dialects import postgresql # revision identifiers, used by Alembic. revision: str = "efe19595299c" down_revision: Union[str, Sequence[str], None] = "6512af98fb22" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: """Upgrade schema.""" # Create enums frequency_enum = sa.Enum( "daily", "weekly", "monthly", name="report_schedule_frequency" ) frequency_enum.create(op.get_bind(), checkfirst=True) format_enum = sa.Enum("pdf", "csv", name="report_schedule_format") format_enum.create(op.get_bind(), checkfirst=True) # Create report_schedules table op.create_table( "report_schedules", sa.Column( "id", postgresql.UUID(as_uuid=True), primary_key=True, server_default=sa.text("uuid_generate_v4()"), ), sa.Column( "user_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ), sa.Column( "scenario_id", postgresql.UUID(as_uuid=True), sa.ForeignKey("scenarios.id", ondelete="CASCADE"), nullable=False, ), sa.Column("name", sa.String(255), nullable=True), sa.Column( "frequency", postgresql.ENUM( "daily", "weekly", "monthly", name="report_schedule_frequency", create_type=False, ), nullable=False, ), sa.Column("day_of_week", sa.Integer(), nullable=True), # 0-6 for weekly sa.Column("day_of_month", sa.Integer(), nullable=True), # 1-31 for monthly sa.Column("hour", sa.Integer(), nullable=False), # 0-23 sa.Column("minute", sa.Integer(), nullable=False), # 0-59 sa.Column( "format", postgresql.ENUM( "pdf", "csv", name="report_schedule_format", create_type=False ), nullable=False, ), sa.Column( "include_logs", sa.Boolean(), nullable=False, server_default=sa.text("false"), ), sa.Column("sections", postgresql.JSONB(), server_default="[]"), sa.Column("email_to", postgresql.ARRAY(sa.String(255)), server_default="{}"), sa.Column( "is_active", sa.Boolean(), nullable=False, server_default=sa.text("true") ), sa.Column("last_run_at", sa.TIMESTAMP(timezone=True), nullable=True), sa.Column("next_run_at", sa.TIMESTAMP(timezone=True), nullable=True), sa.Column( "created_at", sa.TIMESTAMP(timezone=True), server_default=sa.text("NOW()"), nullable=False, ), ) # Add indexes op.create_index("idx_report_schedules_user_id", "report_schedules", ["user_id"]) op.create_index( "idx_report_schedules_scenario_id", "report_schedules", ["scenario_id"] ) op.create_index( "idx_report_schedules_next_run_at", "report_schedules", ["next_run_at"] ) # Add check constraints using raw SQL for complex expressions op.execute(""" ALTER TABLE report_schedules ADD CONSTRAINT chk_report_schedules_hour CHECK (hour >= 0 AND hour <= 23) """) op.execute(""" ALTER TABLE report_schedules ADD CONSTRAINT chk_report_schedules_minute CHECK (minute >= 0 AND minute <= 59) """) op.execute(""" ALTER TABLE report_schedules ADD CONSTRAINT chk_report_schedules_day_of_week CHECK (day_of_week IS NULL OR (day_of_week >= 0 AND day_of_week <= 6)) """) op.execute(""" ALTER TABLE report_schedules ADD CONSTRAINT chk_report_schedules_day_of_month CHECK (day_of_month IS NULL OR (day_of_month >= 1 AND day_of_month <= 31)) """) def downgrade() -> None: """Downgrade schema.""" # Drop constraints op.execute( "ALTER TABLE report_schedules DROP CONSTRAINT IF EXISTS chk_report_schedules_hour" ) op.execute( "ALTER TABLE report_schedules DROP CONSTRAINT IF EXISTS chk_report_schedules_minute" ) op.execute( "ALTER TABLE report_schedules DROP CONSTRAINT IF EXISTS chk_report_schedules_day_of_week" ) op.execute( "ALTER TABLE report_schedules DROP CONSTRAINT IF EXISTS chk_report_schedules_day_of_month" ) # Drop indexes op.drop_index("idx_report_schedules_next_run_at", table_name="report_schedules") op.drop_index("idx_report_schedules_scenario_id", table_name="report_schedules") op.drop_index("idx_report_schedules_user_id", table_name="report_schedules") # Drop table op.drop_table("report_schedules") # Drop enum types op.execute("DROP TYPE IF EXISTS report_schedule_frequency;") op.execute("DROP TYPE IF EXISTS report_schedule_format;")