Files
orion/alembic/versions_backup/n2c3d4e5f6a7_add_features_table.py
Samir Boulahtit f20266167d
Some checks failed
CI / ruff (push) Failing after 7s
CI / pytest (push) Failing after 1s
CI / architecture (push) Failing after 9s
CI / dependency-scanning (push) Successful in 27s
CI / audit (push) Successful in 8s
CI / docs (push) Has been skipped
fix(lint): auto-fix ruff violations and tune lint rules
- Auto-fixed 4,496 lint issues (import sorting, modern syntax, etc.)
- Added ignore rules for patterns intentional in this codebase:
  E402 (late imports), E712 (SQLAlchemy filters), B904 (raise from),
  SIM108/SIM105/SIM117 (readability preferences)
- Added per-file ignores for tests and scripts
- Excluded broken scripts/rename_terminology.py (has curly quotes)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 23:10:42 +01:00

294 lines
12 KiB
Python

"""add features table and seed data
Revision ID: n2c3d4e5f6a7
Revises: ba2c0ce78396
Create Date: 2025-12-31 10:00:00.000000
"""
import json
from collections.abc import Sequence
import sqlalchemy as sa
from alembic import op
# revision identifiers, used by Alembic.
revision: str = "n2c3d4e5f6a7"
down_revision: str | None = "ba2c0ce78396"
branch_labels: str | Sequence[str] | None = None
depends_on: str | Sequence[str] | None = None
# ============================================================================
# Feature Definitions
# ============================================================================
# category, code, name, description, ui_location, ui_icon, ui_route, display_order
FEATURES = [
# Orders (category: orders)
("orders", "order_management", "Order Management", "View and manage orders", "sidebar", "clipboard-list", "/vendor/{code}/orders", 1),
("orders", "order_bulk_actions", "Bulk Order Actions", "Process multiple orders at once", "inline", None, None, 2),
("orders", "order_export", "Order Export", "Export orders to CSV/Excel", "inline", "download", None, 3),
("orders", "automation_rules", "Automation Rules", "Automatic order processing rules", "sidebar", "cog", "/vendor/{code}/automation", 4),
# Inventory (category: inventory)
("inventory", "inventory_basic", "Basic Inventory", "Track product stock levels", "sidebar", "cube", "/vendor/{code}/inventory", 1),
("inventory", "inventory_locations", "Warehouse Locations", "Manage multiple warehouse locations", "inline", "map-pin", None, 2),
("inventory", "inventory_purchase_orders", "Purchase Orders", "Create and manage purchase orders", "sidebar", "shopping-cart", "/vendor/{code}/purchase-orders", 3),
("inventory", "low_stock_alerts", "Low Stock Alerts", "Get notified when stock is low", "inline", "bell", None, 4),
# Analytics (category: analytics)
("analytics", "basic_reports", "Basic Reports", "Essential sales and order reports", "sidebar", "chart-pie", "/vendor/{code}/reports", 1),
("analytics", "analytics_dashboard", "Analytics Dashboard", "Advanced analytics with charts and trends", "sidebar", "chart-bar", "/vendor/{code}/analytics", 2),
("analytics", "custom_reports", "Custom Reports", "Build custom report configurations", "inline", "document-report", None, 3),
("analytics", "export_reports", "Export Reports", "Export reports to various formats", "inline", "download", None, 4),
# Invoicing (category: invoicing)
("invoicing", "invoice_lu", "Luxembourg Invoicing", "Generate compliant Luxembourg invoices", "sidebar", "document-text", "/vendor/{code}/invoices", 1),
("invoicing", "invoice_eu_vat", "EU VAT Support", "Handle EU VAT for cross-border sales", "inline", "globe", None, 2),
("invoicing", "invoice_bulk", "Bulk Invoicing", "Generate invoices in bulk", "inline", "document-duplicate", None, 3),
("invoicing", "accounting_export", "Accounting Export", "Export to accounting software formats", "inline", "calculator", None, 4),
# Integrations (category: integrations)
("integrations", "letzshop_sync", "Letzshop Sync", "Sync orders and products with Letzshop", "settings", "refresh", None, 1),
("integrations", "api_access", "API Access", "REST API access for custom integrations", "settings", "code", "/vendor/{code}/settings/api", 2),
("integrations", "webhooks", "Webhooks", "Receive real-time event notifications", "settings", "lightning-bolt", "/vendor/{code}/settings/webhooks", 3),
("integrations", "custom_integrations", "Custom Integrations", "Connect with any third-party service", "settings", "puzzle", None, 4),
# Team (category: team)
("team", "single_user", "Single User", "One user account", "api", None, None, 1),
("team", "team_basic", "Team Access", "Invite team members", "sidebar", "users", "/vendor/{code}/team", 2),
("team", "team_roles", "Team Roles", "Role-based permissions for team members", "inline", "shield-check", None, 3),
("team", "audit_log", "Audit Log", "Track all user actions", "sidebar", "clipboard-check", "/vendor/{code}/audit-log", 4),
# Branding (category: branding)
("branding", "basic_shop", "Basic Shop", "Your shop on the platform", "api", None, None, 1),
("branding", "custom_domain", "Custom Domain", "Use your own domain name", "settings", "globe-alt", None, 2),
("branding", "white_label", "White Label", "Remove platform branding entirely", "settings", "color-swatch", None, 3),
# Customers (category: customers)
("customers", "customer_view", "Customer View", "View customer information", "sidebar", "user-group", "/vendor/{code}/customers", 1),
("customers", "customer_export", "Customer Export", "Export customer data", "inline", "download", None, 2),
("customers", "customer_messaging", "Customer Messaging", "Send messages to customers", "inline", "chat", None, 3),
]
# ============================================================================
# Tier Feature Assignments
# ============================================================================
# tier_code -> list of feature codes
TIER_FEATURES = {
"essential": [
"order_management",
"inventory_basic",
"basic_reports",
"invoice_lu",
"letzshop_sync",
"single_user",
"basic_shop",
"customer_view",
],
"professional": [
# All Essential features
"order_management",
"order_bulk_actions",
"order_export",
"inventory_basic",
"inventory_locations",
"inventory_purchase_orders",
"low_stock_alerts",
"basic_reports",
"invoice_lu",
"invoice_eu_vat",
"letzshop_sync",
"team_basic",
"basic_shop",
"customer_view",
"customer_export",
],
"business": [
# All Professional features
"order_management",
"order_bulk_actions",
"order_export",
"automation_rules",
"inventory_basic",
"inventory_locations",
"inventory_purchase_orders",
"low_stock_alerts",
"basic_reports",
"analytics_dashboard",
"custom_reports",
"export_reports",
"invoice_lu",
"invoice_eu_vat",
"invoice_bulk",
"accounting_export",
"letzshop_sync",
"api_access",
"webhooks",
"team_basic",
"team_roles",
"audit_log",
"basic_shop",
"custom_domain",
"customer_view",
"customer_export",
"customer_messaging",
],
"enterprise": [
# All features
"order_management",
"order_bulk_actions",
"order_export",
"automation_rules",
"inventory_basic",
"inventory_locations",
"inventory_purchase_orders",
"low_stock_alerts",
"basic_reports",
"analytics_dashboard",
"custom_reports",
"export_reports",
"invoice_lu",
"invoice_eu_vat",
"invoice_bulk",
"accounting_export",
"letzshop_sync",
"api_access",
"webhooks",
"custom_integrations",
"team_basic",
"team_roles",
"audit_log",
"basic_shop",
"custom_domain",
"white_label",
"customer_view",
"customer_export",
"customer_messaging",
],
}
# Minimum tier for each feature (for upgrade prompts)
# Maps feature_code -> tier_code
MINIMUM_TIER = {
# Essential
"order_management": "essential",
"inventory_basic": "essential",
"basic_reports": "essential",
"invoice_lu": "essential",
"letzshop_sync": "essential",
"single_user": "essential",
"basic_shop": "essential",
"customer_view": "essential",
# Professional
"order_bulk_actions": "professional",
"order_export": "professional",
"inventory_locations": "professional",
"inventory_purchase_orders": "professional",
"low_stock_alerts": "professional",
"invoice_eu_vat": "professional",
"team_basic": "professional",
"customer_export": "professional",
# Business
"automation_rules": "business",
"analytics_dashboard": "business",
"custom_reports": "business",
"export_reports": "business",
"invoice_bulk": "business",
"accounting_export": "business",
"api_access": "business",
"webhooks": "business",
"team_roles": "business",
"audit_log": "business",
"custom_domain": "business",
"customer_messaging": "business",
# Enterprise
"custom_integrations": "enterprise",
"white_label": "enterprise",
}
def upgrade() -> None:
# Create features table
op.create_table(
"features",
sa.Column("id", sa.Integer(), nullable=False),
sa.Column("code", sa.String(50), nullable=False),
sa.Column("name", sa.String(100), nullable=False),
sa.Column("description", sa.Text(), nullable=True),
sa.Column("category", sa.String(50), nullable=False),
sa.Column("ui_location", sa.String(50), nullable=True),
sa.Column("ui_icon", sa.String(50), nullable=True),
sa.Column("ui_route", sa.String(100), nullable=True),
sa.Column("ui_badge_text", sa.String(20), nullable=True),
sa.Column("minimum_tier_id", sa.Integer(), nullable=True),
sa.Column("is_active", sa.Boolean(), nullable=False, default=True),
sa.Column("is_visible", sa.Boolean(), nullable=False, default=True),
sa.Column("display_order", sa.Integer(), nullable=False, default=0),
sa.Column("created_at", sa.DateTime(), nullable=False),
sa.Column("updated_at", sa.DateTime(), nullable=False),
sa.ForeignKeyConstraint(["minimum_tier_id"], ["subscription_tiers.id"]),
sa.PrimaryKeyConstraint("id"),
)
op.create_index("ix_features_code", "features", ["code"], unique=True)
op.create_index("ix_features_category", "features", ["category"], unique=False)
op.create_index("idx_feature_category_order", "features", ["category", "display_order"])
op.create_index("idx_feature_active_visible", "features", ["is_active", "is_visible"])
# Get connection for data operations
conn = op.get_bind()
# Get tier IDs
tier_ids = {}
result = conn.execute(sa.text("SELECT id, code FROM subscription_tiers"))
for row in result:
tier_ids[row[1]] = row[0]
# Insert features
sa.func.now()
for category, code, name, description, ui_location, ui_icon, ui_route, display_order in FEATURES:
minimum_tier_code = MINIMUM_TIER.get(code)
minimum_tier_id = tier_ids.get(minimum_tier_code) if minimum_tier_code else None
conn.execute(
sa.text("""
INSERT INTO features (code, name, description, category, ui_location, ui_icon, ui_route,
minimum_tier_id, is_active, is_visible, display_order, created_at, updated_at)
VALUES (:code, :name, :description, :category, :ui_location, :ui_icon, :ui_route,
:minimum_tier_id, true, true, :display_order, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
"""),
{
"code": code,
"name": name,
"description": description,
"category": category,
"ui_location": ui_location,
"ui_icon": ui_icon,
"ui_route": ui_route,
"minimum_tier_id": minimum_tier_id,
"display_order": display_order,
},
)
# Update subscription_tiers with feature arrays
for tier_code, features in TIER_FEATURES.items():
features_json = json.dumps(features)
conn.execute(
sa.text("UPDATE subscription_tiers SET features = :features WHERE code = :code"),
{"features": features_json, "code": tier_code},
)
def downgrade() -> None:
# Clear features from subscription_tiers
conn = op.get_bind()
conn.execute(sa.text("UPDATE subscription_tiers SET features = '[]'"))
# Drop features table
op.drop_index("idx_feature_active_visible", table_name="features")
op.drop_index("idx_feature_category_order", table_name="features")
op.drop_index("ix_features_category", table_name="features")
op.drop_index("ix_features_code", table_name="features")
op.drop_table("features")