Files
JQC_multi_tenant/migrations/versions/phase46_schedule_recurrence.py
T

85 lines
2.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
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.
"""phase46 — inspection schedule day-of-week / day-of-month recurrence
Ports single-tenant phase43 onto MT's `inspection_schedules` table (MT's name
for the same thing — no rename, rule 7). Adds the recurrence-detail columns so a
weekly schedule can name the weekdays it runs on (Mon/Wed/Fri) and a monthly (or
quarterly / bi-annual / annual) schedule can name either a day of the month
("the 15th") or an nth weekday ("the 2nd Tuesday"):
weekdays VARCHAR(20) -- CSV of Python weekday ints, Mon=0, e.g. '0,2,4'
month_mode VARCHAR(20) -- 'day_of_month' | 'nth_weekday'
day_of_month SMALLINT -- 131, clamped to the month's last day
nth_week SMALLINT -- 15, or -1 for "last"
nth_weekday SMALLINT -- 06, Mon=0
All nullable with no backfill: existing phase34/phase43 rows keep NULLs and fall
back to the plain-interval behaviour in InspectionSchedule._add_interval(), so
no schedule changes cadence on deploy.
`next_run_at` is NOT touched — it keeps its name, its DATETIME type and its
index. It remains the due datetime (ST's `next_due_date` by another name); the
recurrence maths runs on its DATE part and preserves its TIME part.
`month_mode` is VARCHAR rather than ENUM so adding a recurrence style later
needs no 3-step MySQL ENUM migration.
Uses INFORMATION_SCHEMA column-existence checks — safe to re-run on every
tenant DB. Additive only: nothing is renamed, retyped or dropped.
"""
revision = 'phase46_schedule_recurrence'
down_revision = 'phase45_schedule_frequency_enum'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
_TABLE = 'inspection_schedules'
_COLUMNS = (
('weekdays', 'VARCHAR(20) NULL'),
('month_mode', 'VARCHAR(20) NULL'),
('day_of_month', 'SMALLINT NULL'),
('nth_week', 'SMALLINT NULL'),
('nth_weekday', 'SMALLINT NULL'),
)
def _table_exists(conn, table):
return conn.execute(sa.text(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES "
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t"
), {"t": table}).scalar() > 0
def _column_exists(conn, table, column):
return conn.execute(sa.text(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS "
"WHERE TABLE_SCHEMA = DATABASE() "
"AND TABLE_NAME = :t AND COLUMN_NAME = :c"
), {"t": table, "c": column}).scalar() > 0
def upgrade():
bind = op.get_bind()
if not _table_exists(bind, _TABLE):
return
for name, ddl in _COLUMNS:
if not _column_exists(bind, _TABLE, name):
op.execute(sa.text(
f"ALTER TABLE {_TABLE} ADD COLUMN {name} {ddl}"
))
def downgrade():
bind = op.get_bind()
if not _table_exists(bind, _TABLE):
return
for name, _ddl in reversed(_COLUMNS):
if _column_exists(bind, _TABLE, name):
op.execute(sa.text(
f"ALTER TABLE {_TABLE} DROP COLUMN {name}"
))