- Add admin store roles page with merchant→store cascading for superadmin and store-only selection for platform admin - Add permission catalog API with translated labels/descriptions (en/fr/de/lb) - Add permission translations to all 15 module locale files (60 files total) - Add info icon tooltips for permission descriptions in role editor - Add store roles menu item and admin menu item in module definition - Fix store-selector.js URL construction bug when apiEndpoint has query params - Add admin store roles API (CRUD + platform scoping) - Add integration tests for admin store roles and permission catalog Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
18 KiB
Cross-Module Import Migration Plan
Created: 2026-02-26 Status: In Progress Rules: MOD-025, MOD-026
This document tracks the migration of all cross-module model imports to proper service-based access patterns.
Overview
| Category | Description | Files | Priority | Status |
|---|---|---|---|---|
| Cat 5 | UserContext legacy import path | 74 | URGENT | Pending |
| Cat 1 | Direct queries on another module's models | ~47 | URGENT | Pending |
| Cat 2 | Creating instances across module boundaries | ~15 | URGENT | Pending |
| Cat 3 | Aggregation/count queries across boundaries | ~11 | URGENT | Pending |
| Cat 4 | Join queries involving another module's models | ~4 | URGENT | Pending |
| P5 | Provider pattern gaps (widgets, metrics) | ~8 modules | Incremental | Pending |
| P6 | Route variable naming standardization | ~109 files | Low | Deferred |
Cat 5: Move UserContext to Tenancy Module (74 files)
Priority: URGENT — mechanical, low risk, high impact Approach: Move definition, update all imports in one batch
What
UserContext is defined in models/schema/auth.py (legacy location). Per MOD-019, schemas belong in their module. UserContext is a tenancy concern (user identity, platform/store context).
Steps
-
Move
UserContextclass frommodels/schema/auth.pytoapp/modules/tenancy/schemas/auth.py- Keep all properties and methods intact
- Also move related schemas used alongside it:
UserLogin,LogoutResponse,StoreUserResponse
-
Add re-export in legacy location (temporary backwards compat):
# models/schema/auth.py from app.modules.tenancy.schemas.auth import UserContext # noqa: F401 -
Update all 74 import sites from:
from models.schema.auth import UserContextto:
from app.modules.tenancy.schemas.auth import UserContext -
Remove legacy re-export once all imports are updated
Files to Update (by module)
app/api/ (1 file)
app/api/deps.py
app/modules/tenancy/ (15 files)
routes/api/admin_merchants.pyroutes/api/admin_module_config.pyroutes/api/admin_merchant_domains.pyroutes/api/admin_modules.pyroutes/api/admin_platforms.pyroutes/api/admin_store_domains.pyroutes/api/admin_stores.pyroutes/api/admin_users.pyroutes/api/merchant.pyroutes/api/store_auth.py(also importsLogoutResponse,StoreUserResponse,UserLogin)routes/api/store_profile.pyroutes/api/store_team.pyroutes/pages/merchant.pyservices/admin_platform_service.pytests/integration/test_merchant_routes.py
app/modules/core/ (12 files)
routes/api/admin_dashboard.pyroutes/api/admin_menu_config.pyroutes/api/admin_settings.pyroutes/api/merchant_menu.pyroutes/api/store_dashboard.pyroutes/api/store_menu.pyroutes/api/store_settings.pyroutes/pages/merchant.pytests/integration/test_merchant_dashboard_routes.pytests/integration/test_merchant_menu_routes.pytests/integration/test_store_dashboard_routes.py
app/modules/billing/ (9 files)
routes/api/admin.pyroutes/api/admin_features.pyroutes/api/store.pyroutes/api/store_addons.pyroutes/api/store_checkout.pyroutes/api/store_features.pyroutes/api/store_usage.pyroutes/pages/merchant.pytests/integration/test_merchant_routes.py
app/modules/marketplace/ (8 files)
routes/api/admin_letzshop.pyroutes/api/admin_marketplace.pyroutes/api/admin_products.pyroutes/api/store_letzshop.pyroutes/api/store_marketplace.pyroutes/api/store_onboarding.pytests/integration/test_store_page_routes.pytests/unit/test_store_page_routes.py
app/modules/messaging/ (7 files)
routes/api/admin_email_templates.pyroutes/api/admin_messages.pyroutes/api/admin_notifications.pyroutes/api/store_email_settings.pyroutes/api/store_email_templates.pyroutes/api/store_messages.pyroutes/api/store_notifications.py
app/modules/orders/ (6 files)
routes/api/admin.pyroutes/api/admin_exceptions.pyroutes/api/store.pyroutes/api/store_customer_orders.pyroutes/api/store_exceptions.pyroutes/api/store_invoices.py
app/modules/monitoring/ (6 files)
routes/api/admin_audit.pyroutes/api/admin_code_quality.pyroutes/api/admin_logs.pyroutes/api/admin_platform_health.pyroutes/api/admin_tasks.pyroutes/api/admin_tests.py
app/modules/cms/ (4 files)
routes/api/admin_images.pyroutes/api/admin_media.pyroutes/api/admin_store_themes.pyroutes/api/store_media.py
app/modules/catalog/ (2 files)
routes/api/admin.pyroutes/api/store.py
app/modules/customers/ (2 files)
routes/api/admin.pyroutes/api/store.py
app/modules/inventory/ (2 files)
routes/api/admin.pyroutes/api/store.py
app/modules/loyalty/ (2 files)
routes/pages/merchant.pytests/conftest.py
app/modules/payments/ (1 file)
routes/api/store.py
tests/ (1 file)
tests/unit/api/test_deps.py
docs/ (2 files — update code examples)
docs/architecture/user-context-pattern.mddocs/proposals/decouple-modules.md
Cat 1: Direct Queries → Service Methods (~47 violations)
Priority: URGENT — requires creating new service methods first Approach: Per-module, add service methods then migrate consumers
Required New Service Methods
Tenancy Module (most consumed)
The tenancy module needs these public service methods for cross-module consumers:
# app/modules/tenancy/services/merchant_service.py (new or extend existing)
class MerchantService:
def get_merchant_by_id(self, db, merchant_id) -> Merchant | None
def get_merchant_by_owner_id(self, db, owner_user_id) -> Merchant | None
def get_merchants_for_platform(self, db, platform_id, **filters) -> list[Merchant]
def get_merchant_count(self, db, platform_id=None) -> int
def search_merchants(self, db, query, platform_id=None) -> list[Merchant]
# app/modules/tenancy/services/store_service.py (extend existing)
class StoreService:
def get_store_by_id(self, db, store_id) -> Store | None
def get_stores_for_merchant(self, db, merchant_id) -> list[Store]
def get_stores_for_platform(self, db, platform_id, **filters) -> list[Store]
def get_store_count(self, db, merchant_id=None, platform_id=None) -> int
def get_active_store_count(self, db, platform_id) -> int
# app/modules/tenancy/services/platform_service.py (extend existing)
class PlatformService:
def get_platform_by_id(self, db, platform_id) -> Platform | None
def list_platforms(self, db) -> list[Platform]
# app/modules/tenancy/services/user_service.py (extend existing)
class UserService:
def get_user_by_id(self, db, user_id) -> User | None
def get_user_by_email(self, db, email) -> User | None
def get_store_users(self, db, store_id) -> list[StoreUser]
Catalog Module
# app/modules/catalog/services/product_service.py (extend existing)
class ProductService:
def get_product_by_id(self, db, product_id) -> Product | None
def get_products_by_ids(self, db, product_ids) -> list[Product]
def get_product_count(self, db, store_id=None) -> int
Orders Module
# app/modules/orders/services/order_service.py (extend existing)
class OrderService:
def get_order_by_id(self, db, order_id) -> Order | None
def get_order_count(self, db, store_id=None, **filters) -> int
def get_orders_for_store(self, db, store_id, **filters) -> list[Order]
Migration Per Consuming Module
billing → tenancy (13 direct queries)
| File | What it queries | Replace with |
|---|---|---|
services/admin_subscription_service.py:279 |
db.query(Platform) |
platform_service.get_platform_by_id() |
services/admin_subscription_service.py:285 |
db.query(Store) |
store_service.get_store_by_id() |
services/admin_subscription_service.py:362 |
db.query(Store).filter() |
store_service.get_stores_for_merchant() |
services/billing_service.py:158 |
db.query(Store) |
store_service.get_store_by_id() |
services/billing_service.py:497 |
db.query(Merchant) |
merchant_service.get_merchant_by_id() |
services/feature_service.py:118,145 |
db.query(StorePlatform) |
store_service.get_store_platform() |
services/store_platform_sync_service.py:14 |
db.query(StorePlatform) |
store_service methods |
services/stripe_service.py:26,297,316 |
db.query(Merchant/Store) |
merchant_service/store_service |
services/subscription_service.py:56,74,178,191 |
db.query(Platform/Store) |
service methods |
services/usage_service.py:22 |
db.query(Store) |
store_service |
loyalty → tenancy (10 direct queries)
| File | What it queries | Replace with |
|---|---|---|
services/card_service.py |
db.query(Store/User) |
store_service/user_service |
services/program_service.py |
db.query(Merchant) |
merchant_service |
services/admin_loyalty_service.py |
db.query(Merchant) |
merchant_service |
marketplace → tenancy (5 direct queries)
| File | What it queries | Replace with |
|---|---|---|
services/letzshop/order_service.py:76 |
db.query(Store) |
store_service.get_store_by_id() |
services/letzshop/order_service.py:100,110 |
db.query(Order) |
order_service.get_order_count() |
services/marketplace_metrics.py:203 |
db.query(StorePlatform) |
store_service.get_store_count() |
core → tenancy (3 direct queries)
| File | What it queries | Replace with |
|---|---|---|
services/auth_service.py:156 |
db.query(Merchant) |
merchant_service.get_merchant_by_owner_id() |
services/auth_service.py:25 |
db.query(Store) |
store_service.get_store_by_id() |
services/menu_service.py:351 |
db.query(Store).join() |
store_service.get_stores_for_merchant() |
analytics → tenancy/catalog (4 direct queries)
| File | What it queries | Replace with |
|---|---|---|
services/stats_service.py:69 |
db.query(Product) |
product_service.get_product_count() |
services/stats_service.py:75 |
db.query(Product) |
product_service methods |
services/stats_service.py:88-92 |
db.query(MarketplaceProduct) |
marketplace service |
services/stats_service.py:229 |
db.query(Product) aggregation |
product_service |
Other modules (various)
| Module | File | Replace with |
|---|---|---|
inventory |
services/inventory_transaction_service.py:236 |
order_service.get_order_by_id() |
cms |
services/cms_features.py:160 |
store_service.get_store_count() |
customers |
services/admin_customer_service.py:16 |
store_service.get_store_by_id() |
Cat 2: Model Creation Across Boundaries (~15 violations)
Priority: URGENT Approach: Add create/factory methods to owning services
Most of these are in test fixtures (acceptable exception) but a few are in production code:
Production Code Violations
| File | Creates | Replace with |
|---|---|---|
contracts/audit.py:117 |
AdminAuditLog() |
Use audit provider log_action() |
billing/services/store_platform_sync_service.py |
StorePlatform() |
store_service.create_store_platform() |
marketplace/services/letzshop/order_service.py |
Order/OrderItem() |
order_service.create_order() |
Test Fixtures (Acceptable — Document as Exception)
Test files creating models from other modules for integration test setup is acceptable but should be documented:
# ACCEPTABLE in tests — document with comment:
# Test fixture: creates tenancy models for integration test setup
merchant = Merchant(name="Test", ...)
Cat 3: Aggregation/Count Queries (~11 violations)
Priority: URGENT Approach: Add count/stats methods to owning services
| File | Query | Replace with |
|---|---|---|
marketplace/services/letzshop/order_service.py:100 |
func.count(Order.id) |
order_service.get_order_count() |
marketplace/services/letzshop/order_service.py:110 |
func.count(Order.id) |
order_service.get_order_count() |
catalog/services/catalog_features.py:92 |
StorePlatform count |
store_service.get_active_store_count() |
cms/services/cms_features.py:160 |
StorePlatform count |
store_service.get_active_store_count() |
marketplace/services/marketplace_metrics.py:203 |
StorePlatform count |
store_service.get_active_store_count() |
customers/services/customer_features.py |
Store count | store_service.get_store_count() |
inventory/services/inventory_features.py |
Store count | store_service.get_store_count() |
orders/services/order_features.py |
Store count | store_service.get_store_count() |
Cat 4: Join Queries (~4 violations)
Priority: URGENT Approach: Decompose into service calls or add service methods
| File | Join | Resolution |
|---|---|---|
catalog/services/store_product_service.py:19 |
.join(Store) |
Use store_service.get_store_by_id() + own query |
core/services/menu_service.py:351 |
.join(Store) |
store_service.get_stores_for_merchant() |
customers/services/admin_customer_service.py:16 |
.join(Store) |
store_service.get_store_by_id() + own query |
messaging/services/messaging_service.py:653 |
.join(StoreUser) |
user_service.get_store_users() + filter |
P5: Provider Pattern Gaps (Incremental)
Priority: MEDIUM — implement as each module is touched
Widget Providers to Add
Currently only 2 modules (marketplace, tenancy) provide dashboard widgets. These modules have valuable dashboard data:
| Module | Widget Ideas | Implementation |
|---|---|---|
| orders | Recent orders list, order status breakdown | services/order_widgets.py |
| billing | Subscription status, revenue trend | services/billing_widgets.py |
| catalog | Product summary, category breakdown | services/catalog_widgets.py |
| inventory | Stock summary, low-stock alerts | services/inventory_widgets.py |
| loyalty | Program stats, member engagement | services/loyalty_widgets.py |
| customers | Customer growth, segments | services/customer_widgets.py |
Metrics Providers to Add
| Module | Metric Ideas | Implementation |
|---|---|---|
| loyalty | Active programs, total enrollments, points issued | services/loyalty_metrics.py |
| payments | Transaction volume, success rate, gateway stats | services/payment_metrics.py |
| analytics | Report count, active dashboards | services/analytics_metrics.py |
Implementation Template
# app/modules/{module}/services/{module}_widgets.py
from app.modules.contracts.widgets import (
DashboardWidgetProviderProtocol,
ListWidget,
BreakdownWidget,
)
class {Module}WidgetProvider:
@property
def widget_category(self) -> str:
return "{module}"
def get_store_widgets(self, db, store_id, context=None):
# Return list of ListWidget/BreakdownWidget
...
def get_platform_widgets(self, db, platform_id, context=None):
...
{module}_widget_provider = {Module}WidgetProvider()
Register in definition.py:
def _get_widget_provider():
from app.modules.{module}.services.{module}_widgets import {module}_widget_provider
return {module}_widget_provider
{module}_module = ModuleDefinition(
...
widget_provider=_get_widget_provider,
)
P6: Route Variable Naming (Deferred)
Priority: LOW — cosmetic, no functional impact
Count: ~109 files use admin_router/store_router instead of router
Per MOD-010, route files should export a router variable. Many files use admin_router or store_router instead. The route discovery system currently handles both patterns.
Decision: Defer to a future cleanup sprint. This is purely naming consistency and has no architectural impact.
Execution Order
Phase 1: Foundation (Do First)
- Cat 5: Move UserContext to
tenancy.schemas.auth— mechanical, enables clean imports - Add service methods to tenancy — most modules depend on tenancy, need methods first
Phase 2: High-Impact Migrations (URGENT)
- Cat 1 - billing→tenancy: 13 violations, highest count
- Cat 1 - loyalty→tenancy: 10 violations
- Cat 1 - marketplace→tenancy/catalog/orders: 10 violations
- Cat 1 - core→tenancy: 3 violations
- Cat 1 - analytics→tenancy/catalog: 4 violations
Phase 3: Remaining Migrations (URGENT)
- Cat 2: Model creation violations (3 production files)
- Cat 3: All aggregation queries (11 files)
- Cat 4: All join queries (4 files)
- Cat 1: Remaining modules (cms, customers, inventory, messaging, monitoring)
Phase 4: Provider Enrichment (Incremental)
- P5: Add widget providers to orders, billing, catalog (highest value)
- P5: Add metrics providers to loyalty, payments
- P5: Add remaining widget providers as modules are touched
Phase 5: Cleanup (Deferred)
- P6: Route variable naming standardization
Testing Strategy
For each migration:
- Add/verify service method has a unit test
- Run existing integration tests to confirm no regressions
- Run
python scripts/validate/validate_architecture.pyto verify violation count decreases
Related Documentation
- Cross-Module Import Rules — The rules being enforced
- Architecture Violations Status — Violation tracking
- Module System Architecture — Module structure reference