526 lines
21 KiB
Python
526 lines
21 KiB
Python
"""baseline (squashed) — pre-phase1 core schema
|
|
|
|
Revision ID: 0003_add_user_active
|
|
Revises:
|
|
Create Date: 2026-06-26
|
|
|
|
Squashed baseline that restores the missing root of the tenant migration chain.
|
|
The original 0001/0002/0003_add_user_active migrations were lost; phase1's
|
|
down_revision pointed at a non-existent '0003_add_user_active', leaving the
|
|
chain with no base and unbuildable by Alembic.
|
|
|
|
This revision recreates the FULL current application schema (generated from the
|
|
SQLAlchemy models) with INFORMATION_SCHEMA existence guards (Rule 14), so it is:
|
|
* a complete no-op on existing databases (e.g. tenant-zero / LT, already at
|
|
a later head) — Alembic treats it as applied ancestry and never runs it;
|
|
* the schema builder for FRESH tenant databases.
|
|
|
|
IMPORTANT — fresh tenant provisioning uses "upgrade to THIS revision, then
|
|
stamp head" (see control/tenant_migrate.bootstrap_tenant). The historical
|
|
phase1..phaseN migrations are NOT replayed on fresh DBs (several are not
|
|
idempotent), so this baseline must represent the complete schema on its own.
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
|
|
revision = '0003_add_user_active'
|
|
down_revision = None
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def _table_exists(bind, table):
|
|
return bind.execute(sa.text(
|
|
"SELECT COUNT(*) FROM information_schema.tables "
|
|
"WHERE table_schema = DATABASE() AND table_name = :t"
|
|
), {'t': table}).scalar() > 0
|
|
|
|
|
|
def _index_exists(bind, table, index):
|
|
return bind.execute(sa.text(
|
|
"SELECT COUNT(*) FROM information_schema.statistics "
|
|
"WHERE table_schema = DATABASE() AND table_name = :t AND index_name = :i"
|
|
), {'t': table, 'i': index}).scalar() > 0
|
|
|
|
|
|
def upgrade():
|
|
bind = op.get_bind()
|
|
if not _table_exists(bind, 'notification_matrix'):
|
|
op.execute(sa.text("""CREATE TABLE notification_matrix (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
event_type VARCHAR(50) NOT NULL,
|
|
role_key VARCHAR(30) NOT NULL,
|
|
enabled BOOL NOT NULL,
|
|
custom_emails TEXT,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT uq_notif_matrix_event_role UNIQUE (event_type, role_key)
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'users'):
|
|
op.execute(sa.text("""CREATE TABLE users (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
username VARCHAR(100) NOT NULL,
|
|
full_name VARCHAR(150),
|
|
email VARCHAR(255) NOT NULL,
|
|
password_hash VARCHAR(255) NOT NULL,
|
|
`role` ENUM('admin','director','inspector','project_manager','customer') NOT NULL,
|
|
created_at DATETIME,
|
|
active BOOL NOT NULL,
|
|
password_set BOOL NOT NULL,
|
|
set_password_token VARCHAR(64),
|
|
set_password_token_expires DATETIME,
|
|
PRIMARY KEY (id)
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'users', 'ix_users_email'):
|
|
op.execute(sa.text("CREATE UNIQUE INDEX ix_users_email ON users (email)"))
|
|
if not _index_exists(bind, 'users', 'ix_users_set_password_token'):
|
|
op.execute(sa.text("CREATE INDEX ix_users_set_password_token ON users (set_password_token)"))
|
|
if not _index_exists(bind, 'users', 'ix_users_username'):
|
|
op.execute(sa.text("CREATE UNIQUE INDEX ix_users_username ON users (username)"))
|
|
|
|
if not _table_exists(bind, 'api_device_tokens'):
|
|
op.execute(sa.text("""CREATE TABLE api_device_tokens (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
device_id VARCHAR(64) NOT NULL,
|
|
apns_token VARCHAR(200) NOT NULL,
|
|
device_name VARCHAR(100),
|
|
app_version VARCHAR(20),
|
|
ios_version VARCHAR(20),
|
|
registered_at DATETIME NOT NULL,
|
|
last_seen_at DATETIME,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT uq_device_token_user_device UNIQUE (user_id, device_id),
|
|
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE CASCADE
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'api_device_tokens', 'ix_api_device_tokens_user_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_api_device_tokens_user_id ON api_device_tokens (user_id)"))
|
|
|
|
if not _table_exists(bind, 'api_refresh_tokens'):
|
|
op.execute(sa.text("""CREATE TABLE api_refresh_tokens (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
token_hash VARCHAR(64) NOT NULL,
|
|
device_id VARCHAR(64),
|
|
device_name VARCHAR(100),
|
|
created_at DATETIME NOT NULL,
|
|
expires_at DATETIME NOT NULL,
|
|
revoked BOOL NOT NULL,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE CASCADE
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'api_refresh_tokens', 'ix_api_refresh_tokens_token_hash'):
|
|
op.execute(sa.text("CREATE UNIQUE INDEX ix_api_refresh_tokens_token_hash ON api_refresh_tokens (token_hash)"))
|
|
if not _index_exists(bind, 'api_refresh_tokens', 'ix_api_refresh_tokens_user_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_api_refresh_tokens_user_id ON api_refresh_tokens (user_id)"))
|
|
|
|
if not _table_exists(bind, 'audit_logs'):
|
|
op.execute(sa.text("""CREATE TABLE audit_logs (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
user_id INTEGER,
|
|
username VARCHAR(100) NOT NULL,
|
|
user_role VARCHAR(20) NOT NULL,
|
|
action VARCHAR(50) NOT NULL,
|
|
entity_type VARCHAR(50) NOT NULL,
|
|
entity_id INTEGER,
|
|
entity_label VARCHAR(255),
|
|
details TEXT,
|
|
created_at DATETIME NOT NULL,
|
|
ip_address VARCHAR(45),
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE SET NULL
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'audit_logs', 'ix_audit_logs_action'):
|
|
op.execute(sa.text("CREATE INDEX ix_audit_logs_action ON audit_logs (action)"))
|
|
if not _index_exists(bind, 'audit_logs', 'ix_audit_logs_created_at'):
|
|
op.execute(sa.text("CREATE INDEX ix_audit_logs_created_at ON audit_logs (created_at)"))
|
|
if not _index_exists(bind, 'audit_logs', 'ix_audit_logs_entity_type'):
|
|
op.execute(sa.text("CREATE INDEX ix_audit_logs_entity_type ON audit_logs (entity_type)"))
|
|
|
|
if not _table_exists(bind, 'broadcasts'):
|
|
op.execute(sa.text("""CREATE TABLE broadcasts (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
title VARCHAR(255) NOT NULL,
|
|
body TEXT NOT NULL,
|
|
target_roles JSON NOT NULL,
|
|
sent_by_id INTEGER,
|
|
sent_at DATETIME NOT NULL,
|
|
recipient_count INTEGER NOT NULL,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(sent_by_id) REFERENCES users (id) ON DELETE SET NULL
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'device_registrations'):
|
|
op.execute(sa.text("""CREATE TABLE device_registrations (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
device_id VARCHAR(64) NOT NULL,
|
|
user_id INTEGER NOT NULL,
|
|
device_name VARCHAR(255) NOT NULL,
|
|
app_version VARCHAR(32) NOT NULL,
|
|
ios_version VARCHAR(32) NOT NULL,
|
|
registered_at DATETIME NOT NULL,
|
|
last_seen_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(user_id) REFERENCES users (id)
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'device_registrations', 'ix_device_registrations_device_id'):
|
|
op.execute(sa.text("CREATE UNIQUE INDEX ix_device_registrations_device_id ON device_registrations (device_id)"))
|
|
if not _index_exists(bind, 'device_registrations', 'ix_device_registrations_user_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_device_registrations_user_id ON device_registrations (user_id)"))
|
|
|
|
if not _table_exists(bind, 'inspection_templates'):
|
|
op.execute(sa.text("""CREATE TABLE inspection_templates (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
frequency ENUM('daily','weekly','monthly','quarterly'),
|
|
created_by INTEGER,
|
|
created_at DATETIME,
|
|
form_schema JSON,
|
|
active BOOL NOT NULL,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(created_by) REFERENCES users (id)
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'notification_preferences'):
|
|
op.execute(sa.text("""CREATE TABLE notification_preferences (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
event_type VARCHAR(50) NOT NULL,
|
|
email_enabled BOOL NOT NULL,
|
|
digest_mode BOOL NOT NULL,
|
|
digest_frequency VARCHAR(10) NOT NULL,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT uq_notif_pref_user_event UNIQUE (user_id, event_type),
|
|
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE CASCADE
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'notification_preferences', 'ix_notification_preferences_user_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_notification_preferences_user_id ON notification_preferences (user_id)"))
|
|
|
|
if not _table_exists(bind, 'projects'):
|
|
op.execute(sa.text("""CREATE TABLE projects (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
name VARCHAR(255) NOT NULL,
|
|
description TEXT,
|
|
project_manager_id INTEGER,
|
|
active BOOL NOT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(project_manager_id) REFERENCES users (id)
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'checklist_items'):
|
|
op.execute(sa.text("""CREATE TABLE checklist_items (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
template_id INTEGER NOT NULL,
|
|
category VARCHAR(100),
|
|
item_description TEXT NOT NULL,
|
|
scoring_type ENUM('pass_fail','rating_5','rating_10'),
|
|
weight NUMERIC(3, 2),
|
|
requires_photo BOOL,
|
|
display_order INTEGER,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(template_id) REFERENCES inspection_templates (id)
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'facilities'):
|
|
op.execute(sa.text("""CREATE TABLE facilities (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
name VARCHAR(255) NOT NULL,
|
|
address TEXT,
|
|
contact_person VARCHAR(100),
|
|
contact_phone VARCHAR(20),
|
|
active BOOL,
|
|
created_at DATETIME,
|
|
project_id INTEGER,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(project_id) REFERENCES projects (id) ON DELETE SET NULL
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'facilities', 'ix_facilities_project_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_facilities_project_id ON facilities (project_id)"))
|
|
|
|
if not _table_exists(bind, 'inspector_assignments'):
|
|
op.execute(sa.text("""CREATE TABLE inspector_assignments (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
project_id INTEGER NOT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT uq_inspector_project UNIQUE (user_id, project_id),
|
|
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE CASCADE,
|
|
FOREIGN KEY(project_id) REFERENCES projects (id) ON DELETE CASCADE
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'inspector_assignments', 'ix_inspector_assignments_user_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_inspector_assignments_user_id ON inspector_assignments (user_id)"))
|
|
|
|
if not _table_exists(bind, 'areas'):
|
|
op.execute(sa.text("""CREATE TABLE areas (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
facility_id INTEGER NOT NULL,
|
|
name VARCHAR(255) NOT NULL,
|
|
area_type VARCHAR(50),
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(facility_id) REFERENCES facilities (id)
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'customer_assignments'):
|
|
op.execute(sa.text("""CREATE TABLE customer_assignments (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
project_id INTEGER NOT NULL,
|
|
facility_id INTEGER,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT uq_customer_assignment UNIQUE (user_id, project_id, facility_id),
|
|
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE CASCADE,
|
|
FOREIGN KEY(project_id) REFERENCES projects (id) ON DELETE CASCADE,
|
|
FOREIGN KEY(facility_id) REFERENCES facilities (id) ON DELETE CASCADE
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'customer_assignments', 'ix_customer_assignments_facility_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_customer_assignments_facility_id ON customer_assignments (facility_id)"))
|
|
if not _index_exists(bind, 'customer_assignments', 'ix_customer_assignments_project_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_customer_assignments_project_id ON customer_assignments (project_id)"))
|
|
if not _index_exists(bind, 'customer_assignments', 'ix_customer_assignments_user_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_customer_assignments_user_id ON customer_assignments (user_id)"))
|
|
|
|
if not _table_exists(bind, 'facility_score_alerts'):
|
|
op.execute(sa.text("""CREATE TABLE facility_score_alerts (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
facility_id INTEGER NOT NULL,
|
|
sent_at DATETIME NOT NULL,
|
|
current_avg NUMERIC(5, 2) NOT NULL,
|
|
prior_avg NUMERIC(5, 2) NOT NULL,
|
|
delta NUMERIC(5, 2) NOT NULL,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(facility_id) REFERENCES facilities (id) ON DELETE CASCADE
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'scheduled_reports'):
|
|
op.execute(sa.text("""CREATE TABLE scheduled_reports (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
name VARCHAR(255) NOT NULL,
|
|
report_type ENUM('summary','facility','issues') NOT NULL,
|
|
frequency ENUM('daily','weekly','monthly') NOT NULL,
|
|
facility_id INTEGER,
|
|
recipients JSON NOT NULL,
|
|
include_pdf BOOL NOT NULL,
|
|
include_csv BOOL NOT NULL,
|
|
active BOOL NOT NULL,
|
|
created_by INTEGER,
|
|
created_at DATETIME NOT NULL,
|
|
last_sent_at DATETIME,
|
|
next_send_at DATETIME,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(facility_id) REFERENCES facilities (id) ON DELETE SET NULL,
|
|
FOREIGN KEY(created_by) REFERENCES users (id) ON DELETE SET NULL
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'support_tickets'):
|
|
op.execute(sa.text("""CREATE TABLE support_tickets (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
customer_id INTEGER,
|
|
facility_id INTEGER,
|
|
subject VARCHAR(200) NOT NULL,
|
|
body TEXT NOT NULL,
|
|
status VARCHAR(20) NOT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(customer_id) REFERENCES users (id) ON DELETE SET NULL,
|
|
FOREIGN KEY(facility_id) REFERENCES facilities (id) ON DELETE SET NULL
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'inspections'):
|
|
op.execute(sa.text("""CREATE TABLE inspections (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
template_id INTEGER NOT NULL,
|
|
facility_id INTEGER NOT NULL,
|
|
area_id INTEGER,
|
|
inspector_id INTEGER NOT NULL,
|
|
inspection_date DATETIME NOT NULL,
|
|
overall_score NUMERIC(5, 2),
|
|
status ENUM('in_progress','completed','flagged'),
|
|
notes TEXT,
|
|
form_data JSON,
|
|
completed_at DATETIME,
|
|
mobile_local_id VARCHAR(64),
|
|
submit_latitude NUMERIC(10, 7),
|
|
submit_longitude NUMERIC(10, 7),
|
|
parent_inspection_id INTEGER,
|
|
follow_up_required BOOL NOT NULL,
|
|
follow_up_note TEXT,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(template_id) REFERENCES inspection_templates (id),
|
|
FOREIGN KEY(facility_id) REFERENCES facilities (id),
|
|
FOREIGN KEY(area_id) REFERENCES areas (id),
|
|
FOREIGN KEY(inspector_id) REFERENCES users (id),
|
|
FOREIGN KEY(parent_inspection_id) REFERENCES inspections (id) ON DELETE SET NULL
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'inspections', 'ix_inspections_mobile_local_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_inspections_mobile_local_id ON inspections (mobile_local_id)"))
|
|
|
|
if not _table_exists(bind, 'support_ticket_replies'):
|
|
op.execute(sa.text("""CREATE TABLE support_ticket_replies (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
ticket_id INTEGER NOT NULL,
|
|
user_id INTEGER,
|
|
body TEXT NOT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(ticket_id) REFERENCES support_tickets (id) ON DELETE CASCADE,
|
|
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE SET NULL
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'inspection_results'):
|
|
op.execute(sa.text("""CREATE TABLE inspection_results (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
inspection_id INTEGER NOT NULL,
|
|
checklist_item_id INTEGER NOT NULL,
|
|
score NUMERIC(5, 2),
|
|
passed BOOL,
|
|
comments TEXT,
|
|
photo_path VARCHAR(255),
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(inspection_id) REFERENCES inspections (id),
|
|
FOREIGN KEY(checklist_item_id) REFERENCES checklist_items (id)
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'issues'):
|
|
op.execute(sa.text("""CREATE TABLE issues (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
inspection_id INTEGER,
|
|
area_id INTEGER,
|
|
facility_id INTEGER,
|
|
severity ENUM('low','medium','high','critical') NOT NULL,
|
|
description TEXT NOT NULL,
|
|
photo_path VARCHAR(255),
|
|
status ENUM('open','in_progress','resolved','pending_verification'),
|
|
assigned_to INTEGER,
|
|
reported_by INTEGER,
|
|
reported_at DATETIME,
|
|
resolved_at DATETIME,
|
|
result_notes TEXT,
|
|
result_photos JSON,
|
|
mobile_photo_paths JSON,
|
|
verified_by INTEGER,
|
|
verified_at DATETIME,
|
|
verification_note TEXT,
|
|
sla_notified VARCHAR(10),
|
|
mobile_local_id VARCHAR(64),
|
|
vendor_name VARCHAR(100),
|
|
vendor_contact VARCHAR(200),
|
|
vendor_notes TEXT,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(inspection_id) REFERENCES inspections (id),
|
|
FOREIGN KEY(area_id) REFERENCES areas (id),
|
|
FOREIGN KEY(facility_id) REFERENCES facilities (id),
|
|
FOREIGN KEY(assigned_to) REFERENCES users (id),
|
|
FOREIGN KEY(reported_by) REFERENCES users (id) ON DELETE SET NULL,
|
|
FOREIGN KEY(verified_by) REFERENCES users (id) ON DELETE SET NULL
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'issues', 'ix_issues_mobile_local_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_issues_mobile_local_id ON issues (mobile_local_id)"))
|
|
|
|
if not _table_exists(bind, 'issue_comments'):
|
|
op.execute(sa.text("""CREATE TABLE issue_comments (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
issue_id INTEGER NOT NULL,
|
|
user_id INTEGER NOT NULL,
|
|
status_at_time VARCHAR(20),
|
|
body TEXT NOT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
is_customer_visible BOOL NOT NULL,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(issue_id) REFERENCES issues (id),
|
|
FOREIGN KEY(user_id) REFERENCES users (id)
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'issue_followers'):
|
|
op.execute(sa.text("""CREATE TABLE issue_followers (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
issue_id INTEGER NOT NULL,
|
|
user_id INTEGER NOT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
PRIMARY KEY (id),
|
|
CONSTRAINT uq_issue_follower UNIQUE (issue_id, user_id),
|
|
FOREIGN KEY(issue_id) REFERENCES issues (id) ON DELETE CASCADE,
|
|
FOREIGN KEY(user_id) REFERENCES users (id) ON DELETE CASCADE
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
|
|
if not _table_exists(bind, 'notifications'):
|
|
op.execute(sa.text("""CREATE TABLE notifications (
|
|
id INTEGER NOT NULL AUTO_INCREMENT,
|
|
user_id INTEGER NOT NULL,
|
|
title VARCHAR(255) NOT NULL,
|
|
body TEXT NOT NULL,
|
|
link VARCHAR(512),
|
|
is_read BOOL NOT NULL,
|
|
created_at DATETIME NOT NULL,
|
|
issue_id INTEGER,
|
|
inspection_id INTEGER,
|
|
event_type VARCHAR(50),
|
|
digest_pending BOOL NOT NULL,
|
|
PRIMARY KEY (id),
|
|
FOREIGN KEY(user_id) REFERENCES users (id),
|
|
FOREIGN KEY(issue_id) REFERENCES issues (id) ON DELETE CASCADE,
|
|
FOREIGN KEY(inspection_id) REFERENCES inspections (id) ON DELETE CASCADE
|
|
)DEFAULT CHARSET=utf8mb4 ENGINE=InnoDB"""))
|
|
if not _index_exists(bind, 'notifications', 'ix_notifications_digest_pending'):
|
|
op.execute(sa.text("CREATE INDEX ix_notifications_digest_pending ON notifications (digest_pending)"))
|
|
if not _index_exists(bind, 'notifications', 'ix_notifications_user_id'):
|
|
op.execute(sa.text("CREATE INDEX ix_notifications_user_id ON notifications (user_id)"))
|
|
|
|
|
|
|
|
def downgrade():
|
|
bind = op.get_bind()
|
|
if _table_exists(bind, 'notifications'):
|
|
op.execute(sa.text('DROP TABLE notifications'))
|
|
if _table_exists(bind, 'issue_followers'):
|
|
op.execute(sa.text('DROP TABLE issue_followers'))
|
|
if _table_exists(bind, 'issue_comments'):
|
|
op.execute(sa.text('DROP TABLE issue_comments'))
|
|
if _table_exists(bind, 'issues'):
|
|
op.execute(sa.text('DROP TABLE issues'))
|
|
if _table_exists(bind, 'inspection_results'):
|
|
op.execute(sa.text('DROP TABLE inspection_results'))
|
|
if _table_exists(bind, 'support_ticket_replies'):
|
|
op.execute(sa.text('DROP TABLE support_ticket_replies'))
|
|
if _table_exists(bind, 'inspections'):
|
|
op.execute(sa.text('DROP TABLE inspections'))
|
|
if _table_exists(bind, 'support_tickets'):
|
|
op.execute(sa.text('DROP TABLE support_tickets'))
|
|
if _table_exists(bind, 'scheduled_reports'):
|
|
op.execute(sa.text('DROP TABLE scheduled_reports'))
|
|
if _table_exists(bind, 'facility_score_alerts'):
|
|
op.execute(sa.text('DROP TABLE facility_score_alerts'))
|
|
if _table_exists(bind, 'customer_assignments'):
|
|
op.execute(sa.text('DROP TABLE customer_assignments'))
|
|
if _table_exists(bind, 'areas'):
|
|
op.execute(sa.text('DROP TABLE areas'))
|
|
if _table_exists(bind, 'inspector_assignments'):
|
|
op.execute(sa.text('DROP TABLE inspector_assignments'))
|
|
if _table_exists(bind, 'facilities'):
|
|
op.execute(sa.text('DROP TABLE facilities'))
|
|
if _table_exists(bind, 'checklist_items'):
|
|
op.execute(sa.text('DROP TABLE checklist_items'))
|
|
if _table_exists(bind, 'projects'):
|
|
op.execute(sa.text('DROP TABLE projects'))
|
|
if _table_exists(bind, 'notification_preferences'):
|
|
op.execute(sa.text('DROP TABLE notification_preferences'))
|
|
if _table_exists(bind, 'inspection_templates'):
|
|
op.execute(sa.text('DROP TABLE inspection_templates'))
|
|
if _table_exists(bind, 'device_registrations'):
|
|
op.execute(sa.text('DROP TABLE device_registrations'))
|
|
if _table_exists(bind, 'broadcasts'):
|
|
op.execute(sa.text('DROP TABLE broadcasts'))
|
|
if _table_exists(bind, 'audit_logs'):
|
|
op.execute(sa.text('DROP TABLE audit_logs'))
|
|
if _table_exists(bind, 'api_refresh_tokens'):
|
|
op.execute(sa.text('DROP TABLE api_refresh_tokens'))
|
|
if _table_exists(bind, 'api_device_tokens'):
|
|
op.execute(sa.text('DROP TABLE api_device_tokens'))
|
|
if _table_exists(bind, 'users'):
|
|
op.execute(sa.text('DROP TABLE users'))
|
|
if _table_exists(bind, 'notification_matrix'):
|
|
op.execute(sa.text('DROP TABLE notification_matrix'))
|