feat(roles): add admin store roles page, permission i18n, and menu integration
Some checks failed
Some checks failed
- 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>
This commit is contained in:
@@ -2,15 +2,29 @@
|
||||
|
||||
This document defines the strict import rules that ensure the module system remains decoupled, testable, and resilient. These rules are critical for maintaining a truly modular architecture.
|
||||
|
||||
## Core Principle
|
||||
## Core Principles
|
||||
|
||||
**Core modules NEVER import from optional modules.**
|
||||
### Principle 1: Core modules NEVER import from optional modules
|
||||
|
||||
This is the fundamental rule that enables optional modules to be truly optional. When a core module imports from an optional module:
|
||||
- The app crashes if that module is disabled
|
||||
- You can't test core functionality in isolation
|
||||
- You create a hidden dependency that violates the architecture
|
||||
|
||||
### Principle 2: Services over models — NEVER import another module's models
|
||||
|
||||
**If module A needs data from module B, it MUST call module B's service methods.**
|
||||
|
||||
Modules must NEVER import and query another module's SQLAlchemy models directly. This applies to ALL cross-module interactions — core-to-core, optional-to-core, and optional-to-optional.
|
||||
|
||||
When a module imports another module's models, it:
|
||||
- Couples to the internal schema (column names, relationships, table structure)
|
||||
- Bypasses business logic, validation, and access control in the owning service
|
||||
- Makes refactoring the model owner's schema a breaking change for all consumers
|
||||
- Scatters query logic across multiple modules instead of centralizing it
|
||||
|
||||
**The owning module's service is the ONLY authorized gateway to its data.**
|
||||
|
||||
## Module Classification
|
||||
|
||||
### Core Modules (Always Enabled)
|
||||
@@ -35,30 +49,70 @@ This is the fundamental rule that enables optional modules to be truly optional.
|
||||
|
||||
## Import Rules Matrix
|
||||
|
||||
| From \ To | Core | Optional | Contracts |
|
||||
|-----------|------|----------|-----------|
|
||||
| **Core** | :white_check_mark: | :x: FORBIDDEN | :white_check_mark: |
|
||||
| **Optional** | :white_check_mark: | :warning: With care | :white_check_mark: |
|
||||
| **Contracts** | :x: | :x: | :white_check_mark: |
|
||||
| From \ To | Core Services | Core Models | Optional Services | Optional Models | Contracts |
|
||||
|-----------|--------------|-------------|-------------------|-----------------|-----------|
|
||||
| **Core** | :white_check_mark: | :x: Use services | :x: FORBIDDEN | :x: FORBIDDEN | :white_check_mark: |
|
||||
| **Optional** | :white_check_mark: | :x: Use services | :warning: With care | :x: Use services | :white_check_mark: |
|
||||
| **Contracts** | :x: | :x: | :x: | :x: | :white_check_mark: |
|
||||
|
||||
### Explanation
|
||||
|
||||
1. **Core → Core**: Allowed. Core modules can import from each other (e.g., billing imports from tenancy)
|
||||
1. **Any → Any Services**: Allowed (with core→optional restriction). Import the service, call its methods.
|
||||
|
||||
2. **Core → Optional**: **FORBIDDEN**. This is the most important rule. Core modules must never have direct imports from optional modules.
|
||||
2. **Any → Any Models**: **FORBIDDEN**. Never import another module's SQLAlchemy models. Use that module's service instead.
|
||||
|
||||
3. **Core → Contracts**: Allowed. Contracts define shared protocols and data structures.
|
||||
3. **Core → Optional**: **FORBIDDEN** (both services and models). Use provider protocols instead.
|
||||
|
||||
4. **Optional → Core**: Allowed. Optional modules can use core functionality.
|
||||
4. **Optional → Core Services**: Allowed. Optional modules can call core service methods.
|
||||
|
||||
5. **Optional → Optional**: Allowed with care. Check dependencies in `definition.py` to ensure proper ordering.
|
||||
5. **Optional → Optional Services**: Allowed with care. Declare dependency in `definition.py`.
|
||||
|
||||
6. **Optional → Contracts**: Allowed. This is how optional modules implement protocols.
|
||||
6. **Any → Contracts**: Allowed. Contracts define shared protocols and data structures.
|
||||
|
||||
7. **Contracts → Anything**: Contracts should only depend on stdlib/typing/Protocol. No module imports.
|
||||
|
||||
## Anti-Patterns (DO NOT DO)
|
||||
|
||||
### Cross-Module Model Import (MOD-025)
|
||||
|
||||
```python
|
||||
# app/modules/orders/services/order_service.py
|
||||
|
||||
# BAD: Importing and querying another module's models
|
||||
from app.modules.catalog.models import Product
|
||||
|
||||
class OrderService:
|
||||
def get_order_with_products(self, db, order_id):
|
||||
order = db.query(Order).filter_by(id=order_id).first()
|
||||
# BAD: Direct query on catalog's model
|
||||
products = db.query(Product).filter(Product.id.in_(product_ids)).all()
|
||||
return order, products
|
||||
```
|
||||
|
||||
```python
|
||||
# GOOD: Call the owning module's service
|
||||
from app.modules.catalog.services import product_service
|
||||
|
||||
class OrderService:
|
||||
def get_order_with_products(self, db, order_id):
|
||||
order = db.query(Order).filter_by(id=order_id).first()
|
||||
# GOOD: Catalog service owns Product data access
|
||||
products = product_service.get_products_by_ids(db, product_ids)
|
||||
return order, products
|
||||
```
|
||||
|
||||
### Cross-Module Aggregation Query
|
||||
|
||||
```python
|
||||
# BAD: Counting another module's models directly
|
||||
from app.modules.orders.models import Order
|
||||
count = db.query(func.count(Order.id)).filter_by(store_id=store_id).scalar()
|
||||
|
||||
# GOOD: Ask the owning service
|
||||
from app.modules.orders.services import order_service
|
||||
count = order_service.get_order_count(db, store_id=store_id)
|
||||
```
|
||||
|
||||
### Direct Import from Optional Module
|
||||
|
||||
```python
|
||||
@@ -97,6 +151,36 @@ def process_import(job: MarketplaceImportJob) -> None: # Crashes if disabled
|
||||
|
||||
## Approved Patterns
|
||||
|
||||
### 0. Cross-Module Service Calls (Primary Pattern)
|
||||
|
||||
The default way to access another module's data is through its service layer:
|
||||
|
||||
```python
|
||||
# app/modules/inventory/services/inventory_service.py
|
||||
|
||||
# GOOD: Import the service, not the model
|
||||
from app.modules.catalog.services import product_service
|
||||
|
||||
class InventoryService:
|
||||
def get_stock_for_product(self, db, product_id):
|
||||
# Verify product exists via catalog service
|
||||
product = product_service.get_product_by_id(db, product_id)
|
||||
if not product:
|
||||
raise InventoryError("Product not found")
|
||||
# Query own models
|
||||
return db.query(StockLevel).filter_by(product_id=product_id).first()
|
||||
```
|
||||
|
||||
Each module should expose these standard service methods for external consumers:
|
||||
|
||||
| Method Pattern | Purpose |
|
||||
|---------------|---------|
|
||||
| `get_{entity}_by_id(db, id)` | Single entity lookup |
|
||||
| `list_{entities}(db, **filters)` | Filtered list |
|
||||
| `get_{entity}_count(db, **filters)` | Count query |
|
||||
| `search_{entities}(db, query, **filters)` | Text search |
|
||||
| `get_{entities}_by_ids(db, ids)` | Batch lookup |
|
||||
|
||||
### 1. Provider Protocol Pattern (Metrics & Widgets)
|
||||
|
||||
Use the provider protocol pattern for cross-module data:
|
||||
@@ -190,6 +274,8 @@ The architecture validator (`scripts/validate/validate_architecture.py`) include
|
||||
| IMPORT-001 | ERROR | Core module imports from optional module |
|
||||
| IMPORT-002 | WARNING | Optional module imports from unrelated optional module |
|
||||
| IMPORT-003 | INFO | Consider using protocol pattern instead of direct import |
|
||||
| MOD-025 | ERROR | Module imports models from another module (use services) |
|
||||
| MOD-026 | ERROR | Cross-module data access not going through service layer |
|
||||
|
||||
Run validation:
|
||||
```bash
|
||||
@@ -291,7 +377,9 @@ grep -r "from app.modules.orders" app/modules/core/ && exit 1
|
||||
| Rule | Enforcement |
|
||||
|------|-------------|
|
||||
| Core → Optional = FORBIDDEN | Architecture validation, CI checks |
|
||||
| Use Protocol pattern | Code review, documentation |
|
||||
| Cross-module model imports = FORBIDDEN | MOD-025 rule, code review |
|
||||
| Use services for cross-module data | MOD-026 rule, code review |
|
||||
| Use Protocol pattern for core→optional | Code review, documentation |
|
||||
| Lazy factory functions | Required for definition.py |
|
||||
| TYPE_CHECKING imports | Required for type hints across modules |
|
||||
| Registry-based discovery | Required for all cross-module access |
|
||||
@@ -301,6 +389,8 @@ Following these rules ensures:
|
||||
- Testing can be done in isolation
|
||||
- New modules can be added without modifying core
|
||||
- The app remains stable when modules fail
|
||||
- Module schemas can evolve independently
|
||||
- Data access logic is centralized in the owning service
|
||||
|
||||
## Related Documentation
|
||||
|
||||
@@ -308,3 +398,4 @@ Following these rules ensures:
|
||||
- [Metrics Provider Pattern](metrics-provider-pattern.md) - Numeric statistics architecture
|
||||
- [Widget Provider Pattern](widget-provider-pattern.md) - Dashboard widgets architecture
|
||||
- [Architecture Violations Status](architecture-violations-status.md) - Current violation tracking
|
||||
- [Cross-Module Migration Plan](cross-module-migration-plan.md) - Migration plan for resolving all cross-module violations
|
||||
|
||||
Reference in New Issue
Block a user