Files
orion/alembic/versions/core_001_initial.py
Samir Boulahtit aad18c27ab
Some checks failed
CI / ruff (push) Successful in 11s
CI / validate (push) Has been cancelled
CI / dependency-scanning (push) Has been cancelled
CI / docs (push) Has been cancelled
CI / deploy (push) Has been cancelled
CI / pytest (push) Has started running
refactor: remove all backward compatibility code across 70 files
Clean up 28 backward compatibility instances identified in the codebase.
The app is not live, so all shims are replaced with the target architecture:

- Remove legacy Inventory.location column (use bin_location exclusively)
- Remove dashboard _extract_metric_value helper (use flat metrics dict)
- Remove legacy stat field duplicates (total_stores, total_imports, etc.)
- Remove 13 re-export shims and class aliases across modules
- Remove module-enabling JSON fallback (use PlatformModule junction table)
- Remove menu_to_legacy_format() conversion (return dataclasses directly)
- Remove title/description from MarketplaceProductBase schema
- Clean billing convenience method docstrings
- Clean test fixtures and backward-compat comments
- Add PlatformModule seeding to init_production.py

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 13:20:29 +01:00

334 lines
21 KiB
Python

"""core initial - tenancy, platform, admin tables
Revision ID: core_001
Revises:
Create Date: 2026-02-07
"""
from alembic import op
import sqlalchemy as sa
revision = "core_001"
down_revision = None
branch_labels = None
depends_on = None
def upgrade() -> None:
# --- platforms ---
op.create_table(
"platforms",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("code", sa.String(50), unique=True, nullable=False, index=True, comment="Unique platform identifier (e.g., 'oms', 'loyalty', 'sites')"),
sa.Column("name", sa.String(100), nullable=False, comment="Display name (e.g., 'Orion OMS')"),
sa.Column("description", sa.Text(), nullable=True, comment="Platform description for admin/marketing purposes"),
sa.Column("domain", sa.String(255), unique=True, nullable=True, index=True, comment="Production domain (e.g., 'omsflow.lu', 'rewardflow.lu')"),
sa.Column("path_prefix", sa.String(50), unique=True, nullable=True, index=True, comment="Development path prefix (e.g., 'oms' for localhost:9999/oms/*)"),
sa.Column("logo", sa.String(500), nullable=True, comment="Logo URL for light mode"),
sa.Column("logo_dark", sa.String(500), nullable=True, comment="Logo URL for dark mode"),
sa.Column("favicon", sa.String(500), nullable=True, comment="Favicon URL"),
sa.Column("theme_config", sa.JSON(), nullable=True, server_default="{}", comment="Theme configuration (colors, fonts, etc.)"),
sa.Column("default_language", sa.String(5), nullable=False, server_default="fr", comment="Default language code (e.g., 'fr', 'en', 'de')"),
sa.Column("supported_languages", sa.JSON(), nullable=False, comment="List of supported language codes"),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default="true", comment="Whether the platform is active and accessible"),
sa.Column("is_public", sa.Boolean(), nullable=False, server_default="true", comment="Whether the platform is visible in public listings"),
sa.Column("settings", sa.JSON(), nullable=True, server_default="{}", comment="Platform-specific settings and feature flags"),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
)
op.create_index("idx_platform_active", "platforms", ["is_active"])
op.create_index("idx_platform_public", "platforms", ["is_public", "is_active"])
# --- users ---
op.create_table(
"users",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("email", sa.String(), unique=True, nullable=False, index=True),
sa.Column("username", sa.String(), unique=True, nullable=False, index=True),
sa.Column("first_name", sa.String(), nullable=True),
sa.Column("last_name", sa.String(), nullable=True),
sa.Column("hashed_password", sa.String(), nullable=False),
sa.Column("role", sa.String(), nullable=False, server_default="store"),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default="true"),
sa.Column("is_email_verified", sa.Boolean(), nullable=False, server_default="false"),
sa.Column("is_super_admin", sa.Boolean(), nullable=False, server_default="false"),
sa.Column("preferred_language", sa.String(5), nullable=True),
sa.Column("last_login", sa.DateTime(), nullable=True),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
)
# --- merchants ---
op.create_table(
"merchants",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("name", sa.String(), nullable=False, index=True),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("owner_user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=False),
sa.Column("contact_email", sa.String(), nullable=False),
sa.Column("contact_phone", sa.String(), nullable=True),
sa.Column("website", sa.String(), nullable=True),
sa.Column("business_address", sa.Text(), nullable=True),
sa.Column("tax_number", sa.String(), nullable=True),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default="true"),
sa.Column("is_verified", sa.Boolean(), nullable=False, server_default="false"),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
)
# --- stores ---
op.create_table(
"stores",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("merchant_id", sa.Integer(), sa.ForeignKey("merchants.id"), nullable=False, index=True),
sa.Column("store_code", sa.String(), unique=True, nullable=False, index=True),
sa.Column("subdomain", sa.String(100), unique=True, nullable=False, index=True),
sa.Column("name", sa.String(), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("letzshop_csv_url_fr", sa.String(), nullable=True),
sa.Column("letzshop_csv_url_en", sa.String(), nullable=True),
sa.Column("letzshop_csv_url_de", sa.String(), nullable=True),
sa.Column("letzshop_store_id", sa.String(100), unique=True, nullable=True, index=True),
sa.Column("letzshop_store_slug", sa.String(200), nullable=True, index=True),
sa.Column("letzshop_default_tax_rate", sa.Integer(), nullable=False, server_default="17"),
sa.Column("letzshop_boost_sort", sa.String(10), nullable=True, server_default="5.0"),
sa.Column("letzshop_delivery_method", sa.String(100), nullable=True, server_default="package_delivery"),
sa.Column("letzshop_preorder_days", sa.Integer(), nullable=True, server_default="1"),
sa.Column("is_active", sa.Boolean(), nullable=True, server_default="true"),
sa.Column("is_verified", sa.Boolean(), nullable=True, server_default="false"),
sa.Column("contact_email", sa.String(255), nullable=True),
sa.Column("contact_phone", sa.String(50), nullable=True),
sa.Column("website", sa.String(255), nullable=True),
sa.Column("business_address", sa.Text(), nullable=True),
sa.Column("tax_number", sa.String(100), nullable=True),
sa.Column("default_language", sa.String(5), nullable=False, server_default="fr"),
sa.Column("dashboard_language", sa.String(5), nullable=False, server_default="fr"),
sa.Column("storefront_language", sa.String(5), nullable=False, server_default="fr"),
sa.Column("storefront_languages", sa.JSON(), nullable=False),
sa.Column("storefront_locale", sa.String(10), nullable=True),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
)
# --- roles ---
op.create_table(
"roles",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("store_id", sa.Integer(), sa.ForeignKey("stores.id"), nullable=False),
sa.Column("name", sa.String(100), nullable=False),
sa.Column("permissions", sa.JSON(), nullable=True),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
)
# --- store_users ---
op.create_table(
"store_users",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("store_id", sa.Integer(), sa.ForeignKey("stores.id"), nullable=False),
sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=False),
sa.Column("user_type", sa.String(), nullable=False, server_default="member"),
sa.Column("role_id", sa.Integer(), sa.ForeignKey("roles.id"), nullable=True),
sa.Column("invited_by", sa.Integer(), sa.ForeignKey("users.id"), nullable=True),
sa.Column("invitation_token", sa.String(), nullable=True, index=True),
sa.Column("invitation_sent_at", sa.DateTime(), nullable=True),
sa.Column("invitation_accepted_at", sa.DateTime(), nullable=True),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default="false"),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
)
# --- store_domains ---
op.create_table(
"store_domains",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("store_id", sa.Integer(), sa.ForeignKey("stores.id", ondelete="CASCADE"), nullable=False),
sa.Column("domain", sa.String(255), unique=True, nullable=False, index=True),
sa.Column("is_primary", sa.Boolean(), nullable=False, server_default="false"),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default="true"),
sa.Column("ssl_status", sa.String(50), nullable=True, server_default="pending"),
sa.Column("ssl_verified_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("verification_token", sa.String(100), unique=True, nullable=True),
sa.Column("is_verified", sa.Boolean(), nullable=False, server_default="false"),
sa.Column("verified_at", sa.DateTime(timezone=True), nullable=True),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.UniqueConstraint("store_id", "domain", name="uq_store_domain"),
)
op.create_index("idx_domain_active", "store_domains", ["domain", "is_active"])
op.create_index("idx_store_domain_primary", "store_domains", ["store_id", "is_primary"])
# --- admin_platforms ---
op.create_table(
"admin_platforms",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, comment="Reference to the admin user"),
sa.Column("platform_id", sa.Integer(), sa.ForeignKey("platforms.id", ondelete="CASCADE"), nullable=False, index=True, comment="Reference to the platform"),
sa.Column("is_active", sa.Boolean(), nullable=False, server_default="true", comment="Whether the admin assignment is active"),
sa.Column("assigned_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False, comment="When the admin was assigned to this platform"),
sa.Column("assigned_by_user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True, comment="Super admin who made this assignment"),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.UniqueConstraint("user_id", "platform_id", name="uq_admin_platform"),
)
op.create_index("idx_admin_platform_active", "admin_platforms", ["user_id", "platform_id", "is_active"])
op.create_index("idx_admin_platform_user_active", "admin_platforms", ["user_id", "is_active"])
# --- platform_modules ---
op.create_table(
"platform_modules",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("platform_id", sa.Integer(), sa.ForeignKey("platforms.id", ondelete="CASCADE"), nullable=False, comment="Platform this module configuration belongs to"),
sa.Column("module_code", sa.String(50), nullable=False, comment="Module code (e.g., 'billing', 'inventory', 'orders')"),
sa.Column("is_enabled", sa.Boolean(), nullable=False, server_default="true", comment="Whether this module is currently enabled for the platform"),
sa.Column("enabled_at", sa.DateTime(timezone=True), nullable=True, comment="When the module was last enabled"),
sa.Column("enabled_by_user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True, comment="User who enabled the module"),
sa.Column("disabled_at", sa.DateTime(timezone=True), nullable=True, comment="When the module was last disabled"),
sa.Column("disabled_by_user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="SET NULL"), nullable=True, comment="User who disabled the module"),
sa.Column("config", sa.JSON(), nullable=False, server_default="{}", comment="Module-specific configuration for this platform"),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.UniqueConstraint("platform_id", "module_code", name="uq_platform_module"),
)
op.create_index("idx_platform_module_platform_id", "platform_modules", ["platform_id"])
op.create_index("idx_platform_module_code", "platform_modules", ["module_code"])
op.create_index("idx_platform_module_enabled", "platform_modules", ["platform_id", "is_enabled"])
# --- admin_audit_logs ---
op.create_table(
"admin_audit_logs",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("admin_user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=False, index=True),
sa.Column("action", sa.String(100), nullable=False, index=True),
sa.Column("target_type", sa.String(50), nullable=False, index=True),
sa.Column("target_id", sa.String(100), nullable=False, index=True),
sa.Column("details", sa.JSON(), nullable=True),
sa.Column("ip_address", sa.String(45), nullable=True),
sa.Column("user_agent", sa.Text(), nullable=True),
sa.Column("request_id", sa.String(100), nullable=True),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
)
# --- admin_settings ---
op.create_table(
"admin_settings",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("key", sa.String(100), unique=True, nullable=False, index=True),
sa.Column("value", sa.Text(), nullable=False),
sa.Column("value_type", sa.String(20), nullable=True, server_default="string"),
sa.Column("category", sa.String(50), nullable=True, index=True),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("is_encrypted", sa.Boolean(), nullable=True, server_default="false"),
sa.Column("is_public", sa.Boolean(), nullable=True, server_default="false"),
sa.Column("last_modified_by_user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=True),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
)
# --- platform_alerts ---
op.create_table(
"platform_alerts",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("alert_type", sa.String(50), nullable=False, index=True),
sa.Column("severity", sa.String(20), nullable=False, index=True),
sa.Column("title", sa.String(200), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("affected_stores", sa.JSON(), nullable=True),
sa.Column("affected_systems", sa.JSON(), nullable=True),
sa.Column("is_resolved", sa.Boolean(), nullable=True, server_default="false", index=True),
sa.Column("resolved_at", sa.DateTime(), nullable=True),
sa.Column("resolved_by_user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=True),
sa.Column("resolution_notes", sa.Text(), nullable=True),
sa.Column("auto_generated", sa.Boolean(), nullable=True, server_default="true"),
sa.Column("occurrence_count", sa.Integer(), nullable=True, server_default="1"),
sa.Column("first_occurred_at", sa.DateTime(), nullable=False),
sa.Column("last_occurred_at", sa.DateTime(), nullable=False),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
)
# --- admin_sessions ---
op.create_table(
"admin_sessions",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("admin_user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=False, index=True),
sa.Column("session_token", sa.String(255), unique=True, nullable=False, index=True),
sa.Column("ip_address", sa.String(45), nullable=False),
sa.Column("user_agent", sa.Text(), nullable=True),
sa.Column("login_at", sa.DateTime(), nullable=False, index=True),
sa.Column("last_activity_at", sa.DateTime(), nullable=False),
sa.Column("logout_at", sa.DateTime(), nullable=True),
sa.Column("is_active", sa.Boolean(), nullable=True, server_default="true", index=True),
sa.Column("logout_reason", sa.String(50), nullable=True),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
)
# --- application_logs ---
op.create_table(
"application_logs",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("timestamp", sa.DateTime(), nullable=False, index=True),
sa.Column("level", sa.String(20), nullable=False, index=True),
sa.Column("logger_name", sa.String(200), nullable=False, index=True),
sa.Column("module", sa.String(200), nullable=True),
sa.Column("function_name", sa.String(100), nullable=True),
sa.Column("line_number", sa.Integer(), nullable=True),
sa.Column("message", sa.Text(), nullable=False),
sa.Column("exception_type", sa.String(200), nullable=True),
sa.Column("exception_message", sa.Text(), nullable=True),
sa.Column("stack_trace", sa.Text(), nullable=True),
sa.Column("request_id", sa.String(100), nullable=True, index=True),
sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=True, index=True),
sa.Column("store_id", sa.Integer(), sa.ForeignKey("stores.id"), nullable=True, index=True),
sa.Column("context", sa.JSON(), nullable=True),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
)
# --- admin_menu_configs ---
op.create_table(
"admin_menu_configs",
sa.Column("id", sa.Integer(), primary_key=True, index=True),
sa.Column("frontend_type", sa.Enum("platform", "admin", "store", "storefront", "merchant", name="frontendtype"), nullable=False, index=True, comment="Which frontend this config applies to (admin or store)"),
sa.Column("platform_id", sa.Integer(), sa.ForeignKey("platforms.id", ondelete="CASCADE"), nullable=True, index=True, comment="Platform scope - applies to users/stores of this platform"),
sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=True, index=True, comment="User scope - applies to this specific super admin (admin frontend only)"),
sa.Column("menu_item_id", sa.String(50), nullable=False, index=True, comment="Menu item identifier from registry (e.g., 'products', 'inventory')"),
sa.Column("is_visible", sa.Boolean(), nullable=False, server_default="true", comment="Whether this menu item is visible (False = hidden)"),
sa.Column("created_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.Column("updated_at", sa.DateTime(), server_default=sa.func.now(), nullable=False),
sa.UniqueConstraint("frontend_type", "platform_id", "menu_item_id", name="uq_frontend_platform_menu_config"),
sa.UniqueConstraint("frontend_type", "user_id", "menu_item_id", name="uq_frontend_user_menu_config"),
sa.CheckConstraint(
"(platform_id IS NOT NULL AND user_id IS NULL) OR (platform_id IS NULL AND user_id IS NOT NULL)",
name="ck_admin_menu_config_scope",
),
sa.CheckConstraint(
"(user_id IS NULL) OR (frontend_type = 'admin')",
name="ck_user_scope_admin_only",
),
)
op.create_index("idx_admin_menu_config_frontend_platform", "admin_menu_configs", ["frontend_type", "platform_id"])
op.create_index("idx_admin_menu_config_frontend_user", "admin_menu_configs", ["frontend_type", "user_id"])
op.create_index("idx_admin_menu_config_platform_visible", "admin_menu_configs", ["platform_id", "is_visible"])
op.create_index("idx_admin_menu_config_user_visible", "admin_menu_configs", ["user_id", "is_visible"])
def downgrade() -> None:
op.drop_table("admin_menu_configs")
op.drop_table("application_logs")
op.drop_table("admin_sessions")
op.drop_table("platform_alerts")
op.drop_table("admin_settings")
op.drop_table("admin_audit_logs")
op.drop_table("platform_modules")
op.drop_table("admin_platforms")
op.drop_table("store_domains")
op.drop_table("store_users")
op.drop_table("roles")
op.drop_table("stores")
op.drop_table("merchants")
op.drop_table("users")
op.drop_table("platforms")
sa.Enum(name="frontendtype").drop(op.get_bind(), checkfirst=True)