- 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>
294 lines
12 KiB
Python
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")
|