diff --git a/migrations/README b/migrations/README new file mode 100644 index 0000000..0e04844 --- /dev/null +++ b/migrations/README @@ -0,0 +1 @@ +Single-database configuration for Flask. diff --git a/migrations/alembic.ini b/migrations/alembic.ini new file mode 100644 index 0000000..ec9d45c --- /dev/null +++ b/migrations/alembic.ini @@ -0,0 +1,50 @@ +# A generic, single database configuration. + +[alembic] +# template used to generate migration files +# file_template = %%(rev)s_%%(slug)s + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + + +# Logging configuration +[loggers] +keys = root,sqlalchemy,alembic,flask_migrate + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARN +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARN +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[logger_flask_migrate] +level = INFO +handlers = +qualname = flask_migrate + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/migrations/env.py b/migrations/env.py new file mode 100644 index 0000000..4c97092 --- /dev/null +++ b/migrations/env.py @@ -0,0 +1,113 @@ +import logging +from logging.config import fileConfig + +from flask import current_app + +from alembic import context + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +fileConfig(config.config_file_name) +logger = logging.getLogger('alembic.env') + + +def get_engine(): + try: + # this works with Flask-SQLAlchemy<3 and Alchemical + return current_app.extensions['migrate'].db.get_engine() + except (TypeError, AttributeError): + # this works with Flask-SQLAlchemy>=3 + return current_app.extensions['migrate'].db.engine + + +def get_engine_url(): + try: + return get_engine().url.render_as_string(hide_password=False).replace( + '%', '%%') + except AttributeError: + return str(get_engine().url).replace('%', '%%') + + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +config.set_main_option('sqlalchemy.url', get_engine_url()) +target_db = current_app.extensions['migrate'].db + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def get_metadata(): + if hasattr(target_db, 'metadatas'): + return target_db.metadatas[None] + return target_db.metadata + + +def run_migrations_offline(): + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, target_metadata=get_metadata(), literal_binds=True + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online(): + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + + # this callback is used to prevent an auto-migration from being generated + # when there are no changes to the schema + # reference: http://alembic.zzzcomputing.com/en/latest/cookbook.html + def process_revision_directives(context, revision, directives): + if getattr(config.cmd_opts, 'autogenerate', False): + script = directives[0] + if script.upgrade_ops.is_empty(): + directives[:] = [] + logger.info('No changes in schema detected.') + + conf_args = current_app.extensions['migrate'].configure_args + if conf_args.get("process_revision_directives") is None: + conf_args["process_revision_directives"] = process_revision_directives + + connectable = get_engine() + + with connectable.connect() as connection: + context.configure( + connection=connection, + target_metadata=get_metadata(), + **conf_args + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/migrations/script.py.mako b/migrations/script.py.mako new file mode 100644 index 0000000..2c01563 --- /dev/null +++ b/migrations/script.py.mako @@ -0,0 +1,24 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision = ${repr(up_revision)} +down_revision = ${repr(down_revision)} +branch_labels = ${repr(branch_labels)} +depends_on = ${repr(depends_on)} + + +def upgrade(): + ${upgrades if upgrades else "pass"} + + +def downgrade(): + ${downgrades if downgrades else "pass"} diff --git a/migrations/versions/10ec0a5894e8_phase_1_initial_schema.py b/migrations/versions/10ec0a5894e8_phase_1_initial_schema.py new file mode 100644 index 0000000..b78e13c --- /dev/null +++ b/migrations/versions/10ec0a5894e8_phase_1_initial_schema.py @@ -0,0 +1,810 @@ +"""Phase 1 initial schema + +Revision ID: 10ec0a5894e8 +Revises: +Create Date: 2026-05-06 15:30:37.356335 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '10ec0a5894e8' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('audit_log', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('actor_id', sa.Integer(), nullable=False), + sa.Column('actor_type', sa.String(length=50), nullable=False), + sa.Column('action', sa.String(length=100), nullable=False), + sa.Column('target_type', sa.String(length=100), nullable=True), + sa.Column('target_id', sa.Integer(), nullable=True), + sa.Column('before_json', sa.JSON(), nullable=True), + sa.Column('after_json', sa.JSON(), nullable=True), + sa.Column('ip_address', sa.String(length=45), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('audit_log', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_audit_log_created_at'), ['created_at'], unique=False) + + op.create_table('jwt_blocklist', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('jti', sa.String(length=36), nullable=False), + sa.Column('token_type', sa.String(length=20), nullable=False), + sa.Column('revoked_at', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('jwt_blocklist', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_jwt_blocklist_jti'), ['jti'], unique=True) + + op.create_table('plans', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=50), nullable=False), + sa.Column('price_monthly', sa.Numeric(precision=8, scale=2), nullable=False), + sa.Column('max_staff', sa.Integer(), nullable=True), + sa.Column('max_locations', sa.Integer(), nullable=True), + sa.Column('features_json', sa.JSON(), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('system_users', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(length=255), nullable=False), + sa.Column('password_hash', sa.String(length=255), nullable=False), + sa.Column('name', sa.String(length=100), nullable=False), + sa.Column('role', sa.String(length=50), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('failed_login_attempts', sa.Integer(), nullable=False), + sa.Column('locked_until', sa.DateTime(), nullable=True), + sa.Column('password_reset_token', sa.String(length=255), nullable=True), + sa.Column('password_reset_expires_at', sa.DateTime(), nullable=True), + sa.Column('last_login_at', sa.DateTime(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('system_users', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_system_users_email'), ['email'], unique=True) + + op.create_table('tenants', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('slug', sa.String(length=80), nullable=False), + sa.Column('name', sa.String(length=150), nullable=False), + sa.Column('owner_email', sa.String(length=255), nullable=False), + sa.Column('plan_id', sa.Integer(), nullable=False), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('trial_ends_at', sa.DateTime(), nullable=True), + sa.Column('subscription_expires_at', sa.DateTime(), nullable=True), + sa.Column('is_demo', sa.Boolean(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('updated_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['plan_id'], ['plans.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('tenants', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_tenants_slug'), ['slug'], unique=True) + + op.create_table('locations', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=150), nullable=False), + sa.Column('address', sa.String(length=255), nullable=True), + sa.Column('phone', sa.String(length=30), nullable=True), + sa.Column('email', sa.String(length=255), nullable=True), + sa.Column('timezone', sa.String(length=60), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('is_primary', sa.Boolean(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('deleted_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('locations', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_locations_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('marketing_campaigns', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=150), nullable=False), + sa.Column('channel', sa.String(length=20), nullable=False), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('audience_filter_json', sa.JSON(), nullable=True), + sa.Column('subject', sa.String(length=255), nullable=True), + sa.Column('message_body', sa.Text(), nullable=True), + sa.Column('scheduled_at', sa.DateTime(), nullable=True), + sa.Column('sent_at', sa.DateTime(), nullable=True), + sa.Column('sent_count', sa.Integer(), nullable=False), + sa.Column('open_count', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('marketing_campaigns', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_marketing_campaigns_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('products', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=150), nullable=False), + sa.Column('sku', sa.String(length=100), nullable=True), + sa.Column('category', sa.String(length=100), nullable=True), + sa.Column('sale_price', sa.Numeric(precision=8, scale=2), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('deleted_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('products', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_products_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('services', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=150), nullable=False), + sa.Column('category', sa.String(length=100), nullable=True), + sa.Column('duration_min', sa.Integer(), nullable=False), + sa.Column('price', sa.Numeric(precision=8, scale=2), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('deleted_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('services', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_services_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('tenant_billing_history', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('amount', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('description', sa.String(length=255), nullable=False), + sa.Column('paid_at', sa.DateTime(), nullable=True), + sa.Column('invoice_ref', sa.String(length=100), nullable=True), + sa.Column('recorded_by', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['recorded_by'], ['system_users.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('tenant_billing_history', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_tenant_billing_history_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('tenant_setting_overrides', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('setting_key', sa.String(length=100), nullable=False), + sa.Column('setting_value', sa.Text(), nullable=True), + sa.Column('overridden_by', sa.Integer(), nullable=False), + sa.Column('overridden_at', sa.DateTime(), nullable=False), + sa.Column('lifted_at', sa.DateTime(), nullable=True), + sa.Column('note', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['overridden_by'], ['system_users.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('tenant_setting_overrides', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_tenant_setting_overrides_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('tenant_settings', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('setting_key', sa.String(length=100), nullable=False), + sa.Column('setting_value', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('tenant_id', 'setting_key', name='uq_tenant_settings_key') + ) + with op.batch_alter_table('tenant_settings', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_tenant_settings_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('users', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('email', sa.String(length=255), nullable=False), + sa.Column('password_hash', sa.String(length=255), nullable=False), + sa.Column('role', sa.String(length=30), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('failed_login_attempts', sa.Integer(), nullable=False), + sa.Column('locked_until', sa.DateTime(), nullable=True), + sa.Column('password_reset_token', sa.String(length=255), nullable=True), + sa.Column('password_reset_expires_at', sa.DateTime(), nullable=True), + sa.Column('last_login_at', sa.DateTime(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('deleted_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('tenant_id', 'email', name='uq_users_tenant_email') + ) + with op.batch_alter_table('users', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_users_email'), ['email'], unique=False) + batch_op.create_index(batch_op.f('ix_users_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('daily_reconciliations', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('date', sa.Date(), nullable=False), + sa.Column('total_cash', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('total_app_payments', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('total_tips', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('total_gift_card_redemptions', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('expected_cash_in_drawer', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('actual_cash_counted', sa.Numeric(precision=10, scale=2), nullable=True), + sa.Column('variance', sa.Numeric(precision=10, scale=2), nullable=True), + sa.Column('closed_by', sa.Integer(), nullable=True), + sa.Column('closed_at', sa.DateTime(), nullable=True), + sa.Column('notes', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['closed_by'], ['users.id'], ), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('tenant_id', 'location_id', 'date', name='uq_reconciliation_location_date') + ) + with op.batch_alter_table('daily_reconciliations', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_daily_reconciliations_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_daily_reconciliations_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('inventory', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=150), nullable=False), + sa.Column('sku', sa.String(length=100), nullable=True), + sa.Column('category', sa.String(length=100), nullable=True), + sa.Column('qty_on_hand', sa.Integer(), nullable=False), + sa.Column('reorder_level', sa.Integer(), nullable=False), + sa.Column('cost_price', sa.Numeric(precision=8, scale=2), nullable=True), + sa.Column('sale_price', sa.Numeric(precision=8, scale=2), nullable=True), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('inventory', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_inventory_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_inventory_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('location_settings', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('setting_key', sa.String(length=100), nullable=False), + sa.Column('setting_value', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('location_id', 'setting_key', name='uq_location_settings_key') + ) + with op.batch_alter_table('location_settings', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_location_settings_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_location_settings_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('promotions', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=150), nullable=False), + sa.Column('discount_percent', sa.Integer(), nullable=False), + sa.Column('applies_to', sa.String(length=30), nullable=False), + sa.Column('target_ids_json', sa.JSON(), nullable=True), + sa.Column('starts_at', sa.DateTime(), nullable=False), + sa.Column('ends_at', sa.DateTime(), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('created_by', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['created_by'], ['users.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('promotions', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_promotions_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('staff', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('name', sa.String(length=150), nullable=False), + sa.Column('phone', sa.String(length=30), nullable=False), + sa.Column('passcode_hash', sa.String(length=255), nullable=False), + sa.Column('staff_type', sa.String(length=30), nullable=False), + sa.Column('pay_type', sa.String(length=20), nullable=False), + sa.Column('hourly_rate', sa.Numeric(precision=8, scale=2), nullable=True), + sa.Column('salary_amount', sa.Numeric(precision=10, scale=2), nullable=True), + sa.Column('guarantee_amount', sa.Numeric(precision=10, scale=2), nullable=True), + sa.Column('pay_period', sa.String(length=20), nullable=False), + sa.Column('commission_rate', sa.Numeric(precision=5, scale=2), nullable=True), + sa.Column('commission_enabled', sa.Boolean(), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('passcode_failed_attempts', sa.Integer(), nullable=False), + sa.Column('passcode_locked_until', sa.DateTime(), nullable=True), + sa.Column('deleted_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['users.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('tenant_id', 'phone', name='uq_staff_tenant_phone') + ) + with op.batch_alter_table('staff', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_staff_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('customers', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=150), nullable=False), + sa.Column('phone', sa.String(length=30), nullable=True), + sa.Column('email', sa.String(length=255), nullable=True), + sa.Column('date_of_birth', sa.Date(), nullable=True), + sa.Column('preferred_staff_id', sa.Integer(), nullable=True), + sa.Column('notes', sa.Text(), nullable=True), + sa.Column('loyalty_points', sa.Integer(), nullable=False), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('no_show_count', sa.Integer(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.Column('deleted_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['preferred_staff_id'], ['staff.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('customers', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_customers_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('inventory_log', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('inventory_id', sa.Integer(), nullable=False), + sa.Column('delta', sa.Integer(), nullable=False), + sa.Column('reason', sa.String(length=255), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['inventory_id'], ['inventory.id'], ), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('inventory_log', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_inventory_log_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_inventory_log_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('staff_clockings', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('staff_id', sa.Integer(), nullable=False), + sa.Column('clocked_in_at', sa.DateTime(), nullable=False), + sa.Column('clocked_out_at', sa.DateTime(), nullable=True), + sa.Column('total_minutes', sa.Integer(), nullable=True), + sa.Column('notes', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['staff_id'], ['staff.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('staff_clockings', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_staff_clockings_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_staff_clockings_staff_id'), ['staff_id'], unique=False) + batch_op.create_index(batch_op.f('ix_staff_clockings_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('staff_locations', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('staff_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['staff_id'], ['staff.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('staff_id', 'location_id', name='uq_staff_location') + ) + with op.batch_alter_table('staff_locations', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_staff_locations_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_staff_locations_staff_id'), ['staff_id'], unique=False) + batch_op.create_index(batch_op.f('ix_staff_locations_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('staff_pay_periods', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('staff_id', sa.Integer(), nullable=False), + sa.Column('period_start', sa.Date(), nullable=False), + sa.Column('period_end', sa.Date(), nullable=False), + sa.Column('pay_type', sa.String(length=20), nullable=False), + sa.Column('base_amount', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('commission_amount', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('guarantee_topup', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('total_amount', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('notes', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['staff_id'], ['staff.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('staff_pay_periods', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_staff_pay_periods_staff_id'), ['staff_id'], unique=False) + batch_op.create_index(batch_op.f('ix_staff_pay_periods_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('staff_schedules', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('staff_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('day_of_week', sa.Integer(), nullable=False), + sa.Column('start_time', sa.Time(), nullable=False), + sa.Column('end_time', sa.Time(), nullable=False), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['staff_id'], ['staff.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('staff_schedules', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_staff_schedules_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_staff_schedules_staff_id'), ['staff_id'], unique=False) + batch_op.create_index(batch_op.f('ix_staff_schedules_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('waitlist', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('customer_name', sa.String(length=150), nullable=False), + sa.Column('customer_phone', sa.String(length=30), nullable=True), + sa.Column('customer_email', sa.String(length=255), nullable=True), + sa.Column('staff_id', sa.Integer(), nullable=True), + sa.Column('service_id', sa.Integer(), nullable=True), + sa.Column('requested_date', sa.Date(), nullable=True), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('notified_at', sa.DateTime(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['service_id'], ['services.id'], ), + sa.ForeignKeyConstraint(['staff_id'], ['staff.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('waitlist', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_waitlist_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_waitlist_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('appointments', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('customer_id', sa.Integer(), nullable=True), + sa.Column('staff_id', sa.Integer(), nullable=True), + sa.Column('service_id', sa.Integer(), nullable=True), + sa.Column('start_time', sa.DateTime(), nullable=False), + sa.Column('end_time', sa.DateTime(), nullable=True), + sa.Column('is_walk_in', sa.Boolean(), nullable=False), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('notes', sa.Text(), nullable=True), + sa.Column('cancellation_reason', sa.Text(), nullable=True), + sa.Column('cancelled_at', sa.DateTime(), nullable=True), + sa.Column('rebook_source', sa.String(length=20), nullable=True), + sa.Column('rebooked_from_transaction_id', sa.Integer(), nullable=True), + sa.Column('created_by', sa.Integer(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['created_by'], ['users.id'], ), + sa.ForeignKeyConstraint(['customer_id'], ['customers.id'], ), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['rebooked_from_transaction_id'], ['transactions.id'], name='fk_appt_rebooked_transaction', use_alter=True), + sa.ForeignKeyConstraint(['service_id'], ['services.id'], ), + sa.ForeignKeyConstraint(['staff_id'], ['staff.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('appointments', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_appointments_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_appointments_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('checkin_queue', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('customer_id', sa.Integer(), nullable=True), + sa.Column('customer_name', sa.String(length=150), nullable=False), + sa.Column('customer_phone', sa.String(length=30), nullable=False), + sa.Column('service_requested', sa.String(length=150), nullable=True), + sa.Column('checked_in_at', sa.DateTime(), nullable=False), + sa.Column('status', sa.String(length=20), nullable=False), + sa.Column('acknowledged_by', sa.Integer(), nullable=True), + sa.Column('acknowledged_at', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['acknowledged_by'], ['users.id'], ), + sa.ForeignKeyConstraint(['customer_id'], ['customers.id'], ), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('checkin_queue', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_checkin_queue_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_checkin_queue_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('gift_cards', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('code', sa.String(length=50), nullable=False), + sa.Column('original_value', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('remaining_balance', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('issued_by', sa.Integer(), nullable=True), + sa.Column('issued_to_customer_id', sa.Integer(), nullable=True), + sa.Column('expires_at', sa.DateTime(), nullable=True), + sa.Column('is_active', sa.Boolean(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['issued_by'], ['users.id'], ), + sa.ForeignKeyConstraint(['issued_to_customer_id'], ['customers.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('tenant_id', 'code', name='uq_gift_card_tenant_code') + ) + with op.batch_alter_table('gift_cards', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_gift_cards_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('appointment_reminders', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('appointment_id', sa.Integer(), nullable=False), + sa.Column('reminder_type', sa.String(length=10), nullable=False), + sa.Column('scheduled_for', sa.DateTime(), nullable=False), + sa.Column('sent_at', sa.DateTime(), nullable=True), + sa.Column('channel', sa.String(length=20), nullable=False), + sa.Column('status', sa.String(length=20), nullable=False), + sa.ForeignKeyConstraint(['appointment_id'], ['appointments.id'], ), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('appointment_reminders', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_appointment_reminders_appointment_id'), ['appointment_id'], unique=False) + batch_op.create_index(batch_op.f('ix_appointment_reminders_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_appointment_reminders_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('transactions', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('appointment_id', sa.Integer(), nullable=True), + sa.Column('customer_id', sa.Integer(), nullable=True), + sa.Column('staff_id', sa.Integer(), nullable=True), + sa.Column('subtotal', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('discount', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('tip_amount', sa.Numeric(precision=8, scale=2), nullable=False), + sa.Column('gift_card_amount', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('total', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('payment_method', sa.String(length=20), nullable=False), + sa.Column('payment_reference', sa.String(length=255), nullable=True), + sa.Column('gift_card_id', sa.Integer(), nullable=True), + sa.Column('review_request_sent_at', sa.DateTime(), nullable=True), + sa.Column('voided_at', sa.DateTime(), nullable=True), + sa.Column('voided_by', sa.Integer(), nullable=True), + sa.Column('void_reason', sa.Text(), nullable=True), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['appointment_id'], ['appointments.id'], name='fk_transaction_appointment', use_alter=True), + sa.ForeignKeyConstraint(['customer_id'], ['customers.id'], ), + sa.ForeignKeyConstraint(['gift_card_id'], ['gift_cards.id'], ), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['staff_id'], ['staff.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.ForeignKeyConstraint(['voided_by'], ['users.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('transactions', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_transactions_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_transactions_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('checkout_reviews', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('transaction_id', sa.Integer(), nullable=False), + sa.Column('customer_id', sa.Integer(), nullable=True), + sa.Column('staff_id', sa.Integer(), nullable=True), + sa.Column('rating', sa.Integer(), nullable=False), + sa.Column('comment', sa.Text(), nullable=True), + sa.Column('is_public_suggested', sa.Boolean(), nullable=False), + sa.Column('google_clicked', sa.Boolean(), nullable=False), + sa.Column('facebook_clicked', sa.Boolean(), nullable=False), + sa.Column('yelp_clicked', sa.Boolean(), nullable=False), + sa.Column('created_at', sa.DateTime(), nullable=False), + sa.ForeignKeyConstraint(['customer_id'], ['customers.id'], ), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['staff_id'], ['staff.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.ForeignKeyConstraint(['transaction_id'], ['transactions.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('checkout_reviews', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_checkout_reviews_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_checkout_reviews_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('commission_log', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('tenant_id', sa.Integer(), nullable=False), + sa.Column('location_id', sa.Integer(), nullable=False), + sa.Column('staff_id', sa.Integer(), nullable=False), + sa.Column('transaction_id', sa.Integer(), nullable=False), + sa.Column('amount', sa.Numeric(precision=10, scale=2), nullable=False), + sa.Column('period', sa.String(length=20), nullable=True), + sa.ForeignKeyConstraint(['location_id'], ['locations.id'], ), + sa.ForeignKeyConstraint(['staff_id'], ['staff.id'], ), + sa.ForeignKeyConstraint(['tenant_id'], ['tenants.id'], ), + sa.ForeignKeyConstraint(['transaction_id'], ['transactions.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('commission_log', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_commission_log_location_id'), ['location_id'], unique=False) + batch_op.create_index(batch_op.f('ix_commission_log_staff_id'), ['staff_id'], unique=False) + batch_op.create_index(batch_op.f('ix_commission_log_tenant_id'), ['tenant_id'], unique=False) + + op.create_table('transaction_items', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('transaction_id', sa.Integer(), nullable=False), + sa.Column('service_id', sa.Integer(), nullable=True), + sa.Column('product_id', sa.Integer(), nullable=True), + sa.Column('qty', sa.Integer(), nullable=False), + sa.Column('unit_price', sa.Numeric(precision=8, scale=2), nullable=False), + sa.Column('original_price', sa.Numeric(precision=8, scale=2), nullable=False), + sa.Column('discount_percent', sa.Numeric(precision=5, scale=2), nullable=False), + sa.Column('promotion_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['product_id'], ['products.id'], ), + sa.ForeignKeyConstraint(['promotion_id'], ['promotions.id'], ), + sa.ForeignKeyConstraint(['service_id'], ['services.id'], ), + sa.ForeignKeyConstraint(['transaction_id'], ['transactions.id'], ), + sa.PrimaryKeyConstraint('id') + ) + with op.batch_alter_table('transaction_items', schema=None) as batch_op: + batch_op.create_index(batch_op.f('ix_transaction_items_transaction_id'), ['transaction_id'], unique=False) + + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + with op.batch_alter_table('transaction_items', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_transaction_items_transaction_id')) + + op.drop_table('transaction_items') + with op.batch_alter_table('commission_log', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_commission_log_tenant_id')) + batch_op.drop_index(batch_op.f('ix_commission_log_staff_id')) + batch_op.drop_index(batch_op.f('ix_commission_log_location_id')) + + op.drop_table('commission_log') + with op.batch_alter_table('checkout_reviews', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_checkout_reviews_tenant_id')) + batch_op.drop_index(batch_op.f('ix_checkout_reviews_location_id')) + + op.drop_table('checkout_reviews') + with op.batch_alter_table('transactions', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_transactions_tenant_id')) + batch_op.drop_index(batch_op.f('ix_transactions_location_id')) + + op.drop_table('transactions') + with op.batch_alter_table('appointment_reminders', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_appointment_reminders_tenant_id')) + batch_op.drop_index(batch_op.f('ix_appointment_reminders_location_id')) + batch_op.drop_index(batch_op.f('ix_appointment_reminders_appointment_id')) + + op.drop_table('appointment_reminders') + with op.batch_alter_table('gift_cards', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_gift_cards_tenant_id')) + + op.drop_table('gift_cards') + with op.batch_alter_table('checkin_queue', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_checkin_queue_tenant_id')) + batch_op.drop_index(batch_op.f('ix_checkin_queue_location_id')) + + op.drop_table('checkin_queue') + with op.batch_alter_table('appointments', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_appointments_tenant_id')) + batch_op.drop_index(batch_op.f('ix_appointments_location_id')) + + op.drop_table('appointments') + with op.batch_alter_table('waitlist', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_waitlist_tenant_id')) + batch_op.drop_index(batch_op.f('ix_waitlist_location_id')) + + op.drop_table('waitlist') + with op.batch_alter_table('staff_schedules', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_staff_schedules_tenant_id')) + batch_op.drop_index(batch_op.f('ix_staff_schedules_staff_id')) + batch_op.drop_index(batch_op.f('ix_staff_schedules_location_id')) + + op.drop_table('staff_schedules') + with op.batch_alter_table('staff_pay_periods', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_staff_pay_periods_tenant_id')) + batch_op.drop_index(batch_op.f('ix_staff_pay_periods_staff_id')) + + op.drop_table('staff_pay_periods') + with op.batch_alter_table('staff_locations', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_staff_locations_tenant_id')) + batch_op.drop_index(batch_op.f('ix_staff_locations_staff_id')) + batch_op.drop_index(batch_op.f('ix_staff_locations_location_id')) + + op.drop_table('staff_locations') + with op.batch_alter_table('staff_clockings', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_staff_clockings_tenant_id')) + batch_op.drop_index(batch_op.f('ix_staff_clockings_staff_id')) + batch_op.drop_index(batch_op.f('ix_staff_clockings_location_id')) + + op.drop_table('staff_clockings') + with op.batch_alter_table('inventory_log', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_inventory_log_tenant_id')) + batch_op.drop_index(batch_op.f('ix_inventory_log_location_id')) + + op.drop_table('inventory_log') + with op.batch_alter_table('customers', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_customers_tenant_id')) + + op.drop_table('customers') + with op.batch_alter_table('staff', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_staff_tenant_id')) + + op.drop_table('staff') + with op.batch_alter_table('promotions', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_promotions_tenant_id')) + + op.drop_table('promotions') + with op.batch_alter_table('location_settings', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_location_settings_tenant_id')) + batch_op.drop_index(batch_op.f('ix_location_settings_location_id')) + + op.drop_table('location_settings') + with op.batch_alter_table('inventory', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_inventory_tenant_id')) + batch_op.drop_index(batch_op.f('ix_inventory_location_id')) + + op.drop_table('inventory') + with op.batch_alter_table('daily_reconciliations', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_daily_reconciliations_tenant_id')) + batch_op.drop_index(batch_op.f('ix_daily_reconciliations_location_id')) + + op.drop_table('daily_reconciliations') + with op.batch_alter_table('users', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_users_tenant_id')) + batch_op.drop_index(batch_op.f('ix_users_email')) + + op.drop_table('users') + with op.batch_alter_table('tenant_settings', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_tenant_settings_tenant_id')) + + op.drop_table('tenant_settings') + with op.batch_alter_table('tenant_setting_overrides', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_tenant_setting_overrides_tenant_id')) + + op.drop_table('tenant_setting_overrides') + with op.batch_alter_table('tenant_billing_history', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_tenant_billing_history_tenant_id')) + + op.drop_table('tenant_billing_history') + with op.batch_alter_table('services', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_services_tenant_id')) + + op.drop_table('services') + with op.batch_alter_table('products', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_products_tenant_id')) + + op.drop_table('products') + with op.batch_alter_table('marketing_campaigns', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_marketing_campaigns_tenant_id')) + + op.drop_table('marketing_campaigns') + with op.batch_alter_table('locations', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_locations_tenant_id')) + + op.drop_table('locations') + with op.batch_alter_table('tenants', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_tenants_slug')) + + op.drop_table('tenants') + with op.batch_alter_table('system_users', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_system_users_email')) + + op.drop_table('system_users') + op.drop_table('plans') + with op.batch_alter_table('jwt_blocklist', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_jwt_blocklist_jti')) + + op.drop_table('jwt_blocklist') + with op.batch_alter_table('audit_log', schema=None) as batch_op: + batch_op.drop_index(batch_op.f('ix_audit_log_created_at')) + + op.drop_table('audit_log') + # ### end Alembic commands ###