Aug 19 - Update code to catch up with ST

This commit is contained in:
2026-08-19 14:05:18 -04:00
parent c9984e7ae6
commit 12141c2f75
52 changed files with 3321 additions and 342 deletions
@@ -0,0 +1,63 @@
"""phase54 — per-account notification overrides
Creates `user_notification_matrix`, the per-account layer over the global
NotificationMatrix. One row = one account's explicit answer for one event
(enabled True/False); NO row means "inherit the global column".
See app/models/user_notification_matrix.py for the semantics.
**No backfill, deliberately.** An empty table means every account inherits,
which is exactly today's behaviour — so this migration cannot change who gets
notified. Overrides are created only when an admin sets one on the account's
page in Customer Management. Backfilling from the current global matrix would
freeze every account at today's routing and quietly break future changes to the
global columns.
The rest of phase51 (Customer Director / Customer Inspector) is a LABEL-only
rename over the existing 'customer' and 'external_inspector' ENUM values, so
there is no ENUM change and no user row is touched here.
Table-existence check — safe to re-run.
"""
revision = 'phase54_user_notif_matrix'
down_revision = 'phase53_knowledge_sort_order'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def _has_table(conn, name):
return conn.execute(sa.text(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES "
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t"
), {'t': name}).scalar() > 0
def upgrade():
conn = op.get_bind()
if _has_table(conn, 'user_notification_matrix'):
return
op.create_table(
'user_notification_matrix',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('user_id', sa.Integer,
sa.ForeignKey('users.id', ondelete='CASCADE'),
nullable=False, index=True),
sa.Column('event_type', sa.String(50), nullable=False),
sa.Column('enabled', sa.Boolean, nullable=False,
server_default=sa.text('1')),
sa.UniqueConstraint('user_id', 'event_type',
name='uq_user_notif_matrix_user_event'),
)
def downgrade():
conn = op.get_bind()
if _has_table(conn, 'user_notification_matrix'):
# Every row here is an explicit admin decision; dropping the table
# discards them and returns all accounts to global-matrix routing.
op.drop_table('user_notification_matrix')
@@ -0,0 +1,61 @@
"""phase55 — restrict forms to specific contracts
Creates `template_contracts`: one row = "this inspection template is available
on this contract". Backs per-customer forms — a customer's bespoke form must
not be visible to, or startable against, another customer's facilities.
**No rows for a template means SHARED (available on every contract)**, not
"available nowhere". That convention is why this migration needs no backfill
and cannot change behaviour on deploy: every template that exists today has no
rows and therefore stays available everywhere, exactly as before. A form only
becomes customer-specific once an admin attaches it to at least one contract.
Inverting that default later would silently hide every shared form from every
contract, so it is enforced in one place — InspectionTemplate.available_query()
— which the pickers, their POST validation, and the mobile API all use.
Table-existence check — safe to re-run.
"""
revision = 'phase55_template_contracts'
down_revision = 'phase54_user_notif_matrix'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
def _has_table(conn, name):
return conn.execute(sa.text(
"SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES "
"WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = :t"
), {'t': name}).scalar() > 0
def upgrade():
conn = op.get_bind()
if _has_table(conn, 'template_contracts'):
return
op.create_table(
'template_contracts',
sa.Column('id', sa.Integer, primary_key=True),
sa.Column('template_id', sa.Integer,
sa.ForeignKey('inspection_templates.id', ondelete='CASCADE'),
nullable=False, index=True),
sa.Column('project_id', sa.Integer,
sa.ForeignKey('projects.id', ondelete='CASCADE'),
nullable=False, index=True),
sa.Column('created_at', sa.DateTime, nullable=False,
server_default=sa.func.now()),
sa.UniqueConstraint('template_id', 'project_id', name='uq_template_contract'),
)
def downgrade():
conn = op.get_bind()
if _has_table(conn, 'template_contracts'):
# Dropping the table returns every form to shared — no form becomes
# unusable, they just stop being restricted.
op.drop_table('template_contracts')