Files
orion/docs/architecture/cross-module-migration-plan.md
Samir Boulahtit f95db7c0b1
Some checks failed
CI / ruff (push) Successful in 9s
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
feat(roles): add admin store roles page, permission i18n, and menu integration
- 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>
2026-02-26 23:31:27 +01:00

465 lines
18 KiB
Markdown

# 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
1. **Move `UserContext` class** from `models/schema/auth.py` to `app/modules/tenancy/schemas/auth.py`
- Keep all properties and methods intact
- Also move related schemas used alongside it: `UserLogin`, `LogoutResponse`, `StoreUserResponse`
2. **Add re-export in legacy location** (temporary backwards compat):
```python
# models/schema/auth.py
from app.modules.tenancy.schemas.auth import UserContext # noqa: F401
```
3. **Update all 74 import sites** from:
```python
from models.schema.auth import UserContext
```
to:
```python
from app.modules.tenancy.schemas.auth import UserContext
```
4. **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.py`
- `routes/api/admin_module_config.py`
- `routes/api/admin_merchant_domains.py`
- `routes/api/admin_modules.py`
- `routes/api/admin_platforms.py`
- `routes/api/admin_store_domains.py`
- `routes/api/admin_stores.py`
- `routes/api/admin_users.py`
- `routes/api/merchant.py`
- `routes/api/store_auth.py` (also imports `LogoutResponse`, `StoreUserResponse`, `UserLogin`)
- `routes/api/store_profile.py`
- `routes/api/store_team.py`
- `routes/pages/merchant.py`
- `services/admin_platform_service.py`
- `tests/integration/test_merchant_routes.py`
**app/modules/core/** (12 files)
- `routes/api/admin_dashboard.py`
- `routes/api/admin_menu_config.py`
- `routes/api/admin_settings.py`
- `routes/api/merchant_menu.py`
- `routes/api/store_dashboard.py`
- `routes/api/store_menu.py`
- `routes/api/store_settings.py`
- `routes/pages/merchant.py`
- `tests/integration/test_merchant_dashboard_routes.py`
- `tests/integration/test_merchant_menu_routes.py`
- `tests/integration/test_store_dashboard_routes.py`
**app/modules/billing/** (9 files)
- `routes/api/admin.py`
- `routes/api/admin_features.py`
- `routes/api/store.py`
- `routes/api/store_addons.py`
- `routes/api/store_checkout.py`
- `routes/api/store_features.py`
- `routes/api/store_usage.py`
- `routes/pages/merchant.py`
- `tests/integration/test_merchant_routes.py`
**app/modules/marketplace/** (8 files)
- `routes/api/admin_letzshop.py`
- `routes/api/admin_marketplace.py`
- `routes/api/admin_products.py`
- `routes/api/store_letzshop.py`
- `routes/api/store_marketplace.py`
- `routes/api/store_onboarding.py`
- `tests/integration/test_store_page_routes.py`
- `tests/unit/test_store_page_routes.py`
**app/modules/messaging/** (7 files)
- `routes/api/admin_email_templates.py`
- `routes/api/admin_messages.py`
- `routes/api/admin_notifications.py`
- `routes/api/store_email_settings.py`
- `routes/api/store_email_templates.py`
- `routes/api/store_messages.py`
- `routes/api/store_notifications.py`
**app/modules/orders/** (6 files)
- `routes/api/admin.py`
- `routes/api/admin_exceptions.py`
- `routes/api/store.py`
- `routes/api/store_customer_orders.py`
- `routes/api/store_exceptions.py`
- `routes/api/store_invoices.py`
**app/modules/monitoring/** (6 files)
- `routes/api/admin_audit.py`
- `routes/api/admin_code_quality.py`
- `routes/api/admin_logs.py`
- `routes/api/admin_platform_health.py`
- `routes/api/admin_tasks.py`
- `routes/api/admin_tests.py`
**app/modules/cms/** (4 files)
- `routes/api/admin_images.py`
- `routes/api/admin_media.py`
- `routes/api/admin_store_themes.py`
- `routes/api/store_media.py`
**app/modules/catalog/** (2 files)
- `routes/api/admin.py`
- `routes/api/store.py`
**app/modules/customers/** (2 files)
- `routes/api/admin.py`
- `routes/api/store.py`
**app/modules/inventory/** (2 files)
- `routes/api/admin.py`
- `routes/api/store.py`
**app/modules/loyalty/** (2 files)
- `routes/pages/merchant.py`
- `tests/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.md`
- `docs/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:
```python
# 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
```python
# 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
```python
# 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:
```python
# 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
```python
# 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`:
```python
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)
1. **Cat 5**: Move UserContext to `tenancy.schemas.auth` — mechanical, enables clean imports
2. **Add service methods to tenancy** — most modules depend on tenancy, need methods first
### Phase 2: High-Impact Migrations (URGENT)
3. **Cat 1 - billing→tenancy**: 13 violations, highest count
4. **Cat 1 - loyalty→tenancy**: 10 violations
5. **Cat 1 - marketplace→tenancy/catalog/orders**: 10 violations
6. **Cat 1 - core→tenancy**: 3 violations
7. **Cat 1 - analytics→tenancy/catalog**: 4 violations
### Phase 3: Remaining Migrations (URGENT)
8. **Cat 2**: Model creation violations (3 production files)
9. **Cat 3**: All aggregation queries (11 files)
10. **Cat 4**: All join queries (4 files)
11. **Cat 1**: Remaining modules (cms, customers, inventory, messaging, monitoring)
### Phase 4: Provider Enrichment (Incremental)
12. **P5**: Add widget providers to orders, billing, catalog (highest value)
13. **P5**: Add metrics providers to loyalty, payments
14. **P5**: Add remaining widget providers as modules are touched
### Phase 5: Cleanup (Deferred)
15. **P6**: Route variable naming standardization
---
## Testing Strategy
For each migration:
1. Add/verify service method has a unit test
2. Run existing integration tests to confirm no regressions
3. Run `python scripts/validate/validate_architecture.py` to verify violation count decreases
## Related Documentation
- [Cross-Module Import Rules](cross-module-import-rules.md) — The rules being enforced
- [Architecture Violations Status](architecture-violations-status.md) — Violation tracking
- [Module System Architecture](module-system.md) — Module structure reference