docs: migrate module documentation to single source of truth
Move 39 documentation files from top-level docs/ into each module's docs/ folder, accessible via symlinks from docs/modules/. Create data-model.md files for 10 modules with full schema documentation. Replace originals with redirect stubs. Remove empty guide stubs. Modules migrated: tenancy, billing, loyalty, marketplace, orders, messaging, cms, catalog, inventory, hosting, prospecting. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,345 +1 @@
|
||||
# Customer-Orders Architecture
|
||||
|
||||
This document describes the consumer-agnostic customer architecture, following the same pattern as [Media Architecture](media-architecture.md).
|
||||
|
||||
## Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ CONSUMER MODULES │
|
||||
│ │
|
||||
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
||||
│ │ Orders │ │ Loyalty │ │ Future │ │
|
||||
│ │ │ │ (future) │ │ Module │ │
|
||||
│ │ Order model │ │LoyaltyPoints│ │ XxxCustomer │ │
|
||||
│ │ (customer_id)│ │(customer_id)│ │ │ │
|
||||
│ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │
|
||||
│ │ │ │ │
|
||||
│ └──────────────────┼──────────────────┘ │
|
||||
│ │ │
|
||||
│ ▼ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Customers Module │ │
|
||||
│ │ │ │
|
||||
│ │ Customer (generic, consumer-agnostic storage) │ │
|
||||
│ │ CustomerService (CRUD, authentication, profile management) │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Design Principles
|
||||
|
||||
### 1. Consumer-Agnostic Customer Storage
|
||||
|
||||
The customers module provides **generic customer storage** without knowing what entities will reference customers:
|
||||
|
||||
- `Customer` stores customer data (email, name, addresses, preferences)
|
||||
- `CustomerService` handles CRUD, authentication, and profile management
|
||||
- Customers module has **no knowledge** of orders, loyalty points, or any specific consumers
|
||||
|
||||
### 2. Consumer-Owned Relationships
|
||||
|
||||
Each module that references customers defines its **own relationship**:
|
||||
|
||||
- **Orders**: `Order.customer_id` links orders to customers
|
||||
- **Future Loyalty**: Would define `LoyaltyPoints.customer_id`
|
||||
- **Future Subscriptions**: Would define `Subscription.customer_id`
|
||||
|
||||
This follows the principle: **The consumer knows what it needs, the provider doesn't need to know who uses it.**
|
||||
|
||||
### 3. Correct Dependency Direction
|
||||
|
||||
```
|
||||
WRONG (Hidden Dependency):
|
||||
Customers → Orders (customers imports Order model)
|
||||
|
||||
CORRECT:
|
||||
Orders → Customers (orders references Customer via FK)
|
||||
```
|
||||
|
||||
Optional modules (orders) depend on core modules (customers), never the reverse.
|
||||
|
||||
## Key Components
|
||||
|
||||
### Customer Model (Customers Module)
|
||||
|
||||
```python
|
||||
# app/modules/customers/models/customer.py
|
||||
|
||||
class Customer(Base, TimestampMixin):
|
||||
"""Generic customer - consumer-agnostic."""
|
||||
|
||||
__tablename__ = "customers"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
store_id = Column(Integer, ForeignKey("stores.id"), nullable=False)
|
||||
|
||||
# Authentication
|
||||
email = Column(String(255), nullable=False)
|
||||
hashed_password = Column(String(255))
|
||||
|
||||
# Profile
|
||||
first_name = Column(String(100))
|
||||
last_name = Column(String(100))
|
||||
phone = Column(String(50))
|
||||
customer_number = Column(String(50), unique=True)
|
||||
|
||||
# Preferences
|
||||
marketing_consent = Column(Boolean, default=False)
|
||||
preferred_language = Column(String(10))
|
||||
|
||||
# Status
|
||||
is_active = Column(Boolean, default=True)
|
||||
|
||||
# Note: Consumer-specific relationships (orders, loyalty points, etc.)
|
||||
# are defined in their respective modules. Customers module doesn't
|
||||
# know about specific consumers.
|
||||
```
|
||||
|
||||
### CustomerService (Customers Module)
|
||||
|
||||
The `CustomerService` provides generic operations:
|
||||
|
||||
```python
|
||||
# app/modules/customers/services/customer_service.py
|
||||
|
||||
class CustomerService:
|
||||
"""Generic customer operations - consumer-agnostic."""
|
||||
|
||||
def create_customer(self, db, store_id, customer_data):
|
||||
"""Create a new customer."""
|
||||
...
|
||||
|
||||
def get_customer(self, db, store_id, customer_id):
|
||||
"""Get a customer by ID."""
|
||||
...
|
||||
|
||||
def update_customer(self, db, store_id, customer_id, customer_data):
|
||||
"""Update customer profile."""
|
||||
...
|
||||
|
||||
def login_customer(self, db, store_id, email, password):
|
||||
"""Authenticate a customer."""
|
||||
...
|
||||
|
||||
# Note: Customer order methods have been moved to the orders module.
|
||||
# Use orders.services.customer_order_service for order-related operations.
|
||||
```
|
||||
|
||||
### Order Model (Orders Module)
|
||||
|
||||
```python
|
||||
# app/modules/orders/models/order.py
|
||||
|
||||
class Order(Base, TimestampMixin):
|
||||
"""Order with customer reference - orders owns the relationship."""
|
||||
|
||||
__tablename__ = "orders"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
store_id = Column(Integer, ForeignKey("stores.id"), nullable=False)
|
||||
|
||||
# Customer reference - orders module owns this relationship
|
||||
customer_id = Column(Integer, ForeignKey("customers.id"))
|
||||
|
||||
# Order data
|
||||
order_number = Column(String(50), unique=True)
|
||||
status = Column(String(20), default="pending")
|
||||
total_cents = Column(Integer)
|
||||
...
|
||||
|
||||
# Relationship to customer
|
||||
customer = relationship("Customer", lazy="joined")
|
||||
```
|
||||
|
||||
### CustomerOrderService (Orders Module)
|
||||
|
||||
```python
|
||||
# app/modules/orders/services/customer_order_service.py
|
||||
|
||||
class CustomerOrderService:
|
||||
"""Customer-order operations - owned by orders module."""
|
||||
|
||||
def get_customer_orders(self, db, store_id, customer_id, skip=0, limit=50):
|
||||
"""Get orders for a specific customer."""
|
||||
...
|
||||
|
||||
def get_recent_orders(self, db, store_id, customer_id, limit=5):
|
||||
"""Get recent orders for a customer."""
|
||||
...
|
||||
|
||||
def get_order_count(self, db, store_id, customer_id):
|
||||
"""Get total order count for a customer."""
|
||||
...
|
||||
```
|
||||
|
||||
### Customer Order Metrics (Orders Module)
|
||||
|
||||
Order statistics for customers use the MetricsProvider pattern:
|
||||
|
||||
```python
|
||||
# app/modules/orders/services/order_metrics.py
|
||||
|
||||
class OrderMetricsProvider:
|
||||
"""Metrics provider including customer-level order metrics."""
|
||||
|
||||
def get_customer_order_metrics(self, db, store_id, customer_id, context=None):
|
||||
"""
|
||||
Get order metrics for a specific customer.
|
||||
|
||||
Returns MetricValue objects for:
|
||||
- total_orders: Total orders placed
|
||||
- total_spent: Total amount spent
|
||||
- avg_order_value: Average order value
|
||||
- last_order_date: Date of most recent order
|
||||
- first_order_date: Date of first order
|
||||
"""
|
||||
...
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Customers Module Endpoints
|
||||
|
||||
Customer CRUD operations (no order data):
|
||||
|
||||
```
|
||||
GET /api/store/customers → List customers
|
||||
GET /api/store/customers/{id} → Customer details (no order stats)
|
||||
PUT /api/store/customers/{id} → Update customer
|
||||
PUT /api/store/customers/{id}/status → Toggle active status
|
||||
```
|
||||
|
||||
### Orders Module Endpoints
|
||||
|
||||
Customer order data (owned by orders):
|
||||
|
||||
```
|
||||
GET /api/store/customers/{id}/orders → Customer's order history
|
||||
GET /api/store/customers/{id}/order-stats → Customer's order statistics
|
||||
```
|
||||
|
||||
## Adding Customer References to a New Module
|
||||
|
||||
When creating a module that references customers (e.g., a loyalty module):
|
||||
|
||||
### Step 1: Reference Customer via Foreign Key
|
||||
|
||||
```python
|
||||
# app/modules/loyalty/models/loyalty_points.py
|
||||
|
||||
class LoyaltyPoints(Base):
|
||||
"""Loyalty points - owned by loyalty module."""
|
||||
|
||||
__tablename__ = "loyalty_points"
|
||||
|
||||
id = Column(Integer, primary_key=True)
|
||||
store_id = Column(Integer, ForeignKey("stores.id"))
|
||||
|
||||
# Reference to customer - loyalty module owns this
|
||||
customer_id = Column(Integer, ForeignKey("customers.id"))
|
||||
|
||||
points_balance = Column(Integer, default=0)
|
||||
tier = Column(String(20), default="bronze")
|
||||
|
||||
# Relationship
|
||||
customer = relationship("Customer", lazy="joined")
|
||||
```
|
||||
|
||||
### Step 2: Create Your Service
|
||||
|
||||
```python
|
||||
# app/modules/loyalty/services/customer_loyalty_service.py
|
||||
|
||||
class CustomerLoyaltyService:
|
||||
"""Customer loyalty operations - owned by loyalty module."""
|
||||
|
||||
def get_customer_points(self, db, store_id, customer_id):
|
||||
"""Get loyalty points for a customer."""
|
||||
...
|
||||
|
||||
def add_points(self, db, store_id, customer_id, points, reason):
|
||||
"""Add points to customer's balance."""
|
||||
...
|
||||
```
|
||||
|
||||
### Step 3: Add Routes in Your Module
|
||||
|
||||
```python
|
||||
# app/modules/loyalty/routes/api/store.py
|
||||
|
||||
@router.get("/customers/{customer_id}/loyalty")
|
||||
def get_customer_loyalty(customer_id: int, ...):
|
||||
"""Get loyalty information for a customer."""
|
||||
return loyalty_service.get_customer_points(db, store_id, customer_id)
|
||||
```
|
||||
|
||||
## Benefits of This Architecture
|
||||
|
||||
1. **Module Independence**: Orders can be disabled without affecting customers
|
||||
2. **Extensibility**: New modules easily reference customers
|
||||
3. **No Hidden Dependencies**: Dependencies flow in one direction
|
||||
4. **Clean Separation**: Customers handles identity, consumers handle their domain
|
||||
5. **Testability**: Can test customers without any consumer modules
|
||||
6. **Single Responsibility**: Each module owns its domain
|
||||
|
||||
## Anti-Patterns to Avoid
|
||||
|
||||
### Don't: Import Consumer Models in Customers
|
||||
|
||||
```python
|
||||
# BAD - Creates hidden dependency
|
||||
class CustomerService:
|
||||
def get_customer_orders(self, db, customer_id):
|
||||
from app.modules.orders.models import Order # Wrong!
|
||||
return db.query(Order).filter(Order.customer_id == customer_id).all()
|
||||
```
|
||||
|
||||
### Don't: Add Consumer-Specific Fields to Customer
|
||||
|
||||
```python
|
||||
# BAD - Customer shouldn't know about orders
|
||||
class Customer(Base):
|
||||
# These create coupling to orders module
|
||||
total_orders = Column(Integer) # Wrong approach
|
||||
last_order_date = Column(DateTime) # Wrong approach
|
||||
```
|
||||
|
||||
Instead, query order data from the orders module when needed.
|
||||
|
||||
### Don't: Put Consumer Routes in Customers Module
|
||||
|
||||
```python
|
||||
# BAD - customers/routes shouldn't serve order data
|
||||
@router.get("/customers/{id}/orders")
|
||||
def get_customer_orders(customer_id: int):
|
||||
from app.modules.orders.models import Order # Wrong!
|
||||
...
|
||||
```
|
||||
|
||||
## Migration Note
|
||||
|
||||
Previously, the customers module had methods that imported from orders:
|
||||
|
||||
```python
|
||||
# OLD (removed)
|
||||
class CustomerService:
|
||||
def get_customer_orders(self, db, store_id, customer_id):
|
||||
from app.modules.orders.models import Order # Lazy import
|
||||
...
|
||||
|
||||
def get_customer_statistics(self, db, store_id, customer_id):
|
||||
from app.modules.orders.models import Order # Lazy import
|
||||
...
|
||||
```
|
||||
|
||||
These have been moved to the orders module:
|
||||
- `get_customer_orders()` → `orders.services.customer_order_service`
|
||||
- `get_customer_statistics()` → `orders.services.order_metrics.get_customer_order_metrics()`
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Media Architecture](media-architecture.md) - Similar pattern for media files
|
||||
- [Module System Architecture](module-system.md) - Module structure and dependencies
|
||||
- [Cross-Module Import Rules](cross-module-import-rules.md) - Import restrictions
|
||||
- [Metrics Provider Pattern](metrics-provider-pattern.md) - Provider pattern for statistics
|
||||
This document has moved to the orders module docs: [Architecture](../modules/orders/architecture.md)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -127,6 +127,11 @@ app/modules/analytics/
|
||||
├── definition.py # ModuleDefinition (REQUIRED for auto-discovery)
|
||||
├── config.py # Environment config (auto-discovered)
|
||||
├── exceptions.py # Module-specific exceptions
|
||||
├── docs/ # Module documentation (source of truth)
|
||||
│ ├── index.md # Module overview (REQUIRED)
|
||||
│ ├── data-model.md # Entity relationships (optional)
|
||||
│ ├── api.md # API reference (optional)
|
||||
│ └── business-logic.md # Complex logic docs (optional)
|
||||
├── routes/
|
||||
│ ├── __init__.py
|
||||
│ ├── api/ # API endpoints (auto-discovered)
|
||||
|
||||
@@ -1,291 +1 @@
|
||||
# Product Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
The product management system uses an **independent copy pattern** where store products (`Product`) are fully independent entities that can optionally reference a marketplace source (`MarketplaceProduct`) for display purposes only.
|
||||
|
||||
## Core Principles
|
||||
|
||||
| Principle | Description |
|
||||
|-----------|-------------|
|
||||
| **Full Independence** | Store products have all their own fields - no inheritance or fallback to marketplace |
|
||||
| **Optional Source Reference** | `marketplace_product_id` is nullable - products can be created directly |
|
||||
| **No Reset Functionality** | No "reset to source" - products are independent from the moment of creation |
|
||||
| **Source for Display Only** | Source comparison info is read-only, used for "view original" display |
|
||||
|
||||
---
|
||||
|
||||
## Architecture Diagram
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ MarketplaceProduct │
|
||||
│ (Central Repository - raw imported data from marketplaces) │
|
||||
│ │
|
||||
│ - marketplace_product_id (unique) │
|
||||
│ - gtin, mpn, sku │
|
||||
│ - brand, price_cents, sale_price_cents │
|
||||
│ - is_digital, product_type_enum │
|
||||
│ - translations (via MarketplaceProductTranslation) │
|
||||
└──────────────────────────────────────────────────────────────────────┘
|
||||
|
||||
╳ No runtime dependency
|
||||
│
|
||||
│ Optional FK (for "view source" display only)
|
||||
│ marketplace_product_id (nullable)
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Product │
|
||||
│ (Store's Independent Product - fully standalone) │
|
||||
│ │
|
||||
│ === IDENTIFIERS === │
|
||||
│ - store_id (required) │
|
||||
│ - store_sku │
|
||||
│ - gtin, gtin_type │
|
||||
│ │
|
||||
│ === PRODUCT TYPE (own columns) === │
|
||||
│ - is_digital (Boolean) │
|
||||
│ - product_type (String: physical, digital, service, subscription) │
|
||||
│ │
|
||||
│ === PRICING === │
|
||||
│ - price_cents, sale_price_cents │
|
||||
│ - currency, tax_rate_percent │
|
||||
│ │
|
||||
│ === CONTENT === │
|
||||
│ - brand, condition, availability │
|
||||
│ - primary_image_url, additional_images │
|
||||
│ - translations (via ProductTranslation) │
|
||||
│ │
|
||||
│ === STATUS === │
|
||||
│ - is_active, is_featured │
|
||||
│ │
|
||||
│ === SUPPLIER === │
|
||||
│ - supplier, cost_cents │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Product Creation Patterns
|
||||
|
||||
### 1. From Marketplace Source (Import)
|
||||
|
||||
When copying from a marketplace product:
|
||||
- All fields are **copied** at creation time
|
||||
- `marketplace_product_id` is set for source reference
|
||||
- No ongoing relationship - product is immediately independent
|
||||
|
||||
```python
|
||||
# Service copies all fields at import time
|
||||
product = Product(
|
||||
store_id=store.id,
|
||||
marketplace_product_id=marketplace_product.id, # Source reference
|
||||
# All fields copied - no inheritance
|
||||
brand=marketplace_product.brand,
|
||||
price=marketplace_product.price,
|
||||
is_digital=marketplace_product.is_digital,
|
||||
product_type=marketplace_product.product_type_enum,
|
||||
# ... all other fields
|
||||
)
|
||||
```
|
||||
|
||||
### 2. Direct Creation (No Marketplace Source)
|
||||
|
||||
Stores can create products directly without a marketplace source:
|
||||
|
||||
```python
|
||||
product = Product(
|
||||
store_id=store.id,
|
||||
marketplace_product_id=None, # No source
|
||||
store_sku="DIRECT_001",
|
||||
brand="MyBrand",
|
||||
price=29.99,
|
||||
is_digital=True,
|
||||
product_type="digital",
|
||||
is_active=True,
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Key Fields
|
||||
|
||||
### Product Type Fields
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `is_digital` | Boolean | `False` | Whether product is digital (no physical shipping) |
|
||||
| `product_type` | String(20) | `"physical"` | Product type: physical, digital, service, subscription |
|
||||
|
||||
These are **independent columns** on Product, not derived from MarketplaceProduct.
|
||||
|
||||
### Source Reference
|
||||
|
||||
| Field | Type | Nullable | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `marketplace_product_id` | Integer FK | **Yes** | Optional reference to source MarketplaceProduct |
|
||||
|
||||
---
|
||||
|
||||
## Inventory Handling
|
||||
|
||||
Digital and physical products have different inventory behavior:
|
||||
|
||||
```python
|
||||
@property
|
||||
def has_unlimited_inventory(self) -> bool:
|
||||
"""Digital products have unlimited inventory."""
|
||||
return self.is_digital
|
||||
|
||||
@property
|
||||
def total_inventory(self) -> int:
|
||||
"""Get total inventory across all locations."""
|
||||
if self.is_digital:
|
||||
return Product.UNLIMITED_INVENTORY # 999999
|
||||
return sum(inv.quantity for inv in self.inventory_entries)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Source Comparison (Display Only)
|
||||
|
||||
For products with a marketplace source, we provide comparison info for display:
|
||||
|
||||
```python
|
||||
def get_source_comparison_info(self) -> dict:
|
||||
"""Get current values with source values for comparison.
|
||||
|
||||
Used for "view original source" display feature.
|
||||
"""
|
||||
mp = self.marketplace_product
|
||||
return {
|
||||
"price": self.price,
|
||||
"price_source": mp.price if mp else None,
|
||||
"brand": self.brand,
|
||||
"brand_source": mp.brand if mp else None,
|
||||
# ... other fields
|
||||
}
|
||||
```
|
||||
|
||||
This is **read-only** - there's no mechanism to "reset" to source values.
|
||||
|
||||
---
|
||||
|
||||
## UI Behavior
|
||||
|
||||
### Detail Page
|
||||
|
||||
| Product Type | Source Info Card | Edit Button Text |
|
||||
|-------------|------------------|------------------|
|
||||
| Marketplace-sourced | Shows source info with "View Source" link | "Edit Overrides" |
|
||||
| Directly created | Shows "Direct Creation" badge | "Edit Product" |
|
||||
|
||||
### Info Banner
|
||||
|
||||
- **Marketplace-sourced**: Purple banner - "Store Product Catalog Entry"
|
||||
- **Directly created**: Blue banner - "Directly Created Product"
|
||||
|
||||
---
|
||||
|
||||
## Database Schema
|
||||
|
||||
### Product Table Key Columns
|
||||
|
||||
```sql
|
||||
CREATE TABLE products (
|
||||
id INTEGER PRIMARY KEY,
|
||||
store_id INTEGER NOT NULL REFERENCES stores(id),
|
||||
marketplace_product_id INTEGER REFERENCES marketplace_products(id), -- Nullable!
|
||||
|
||||
-- Product Type (independent columns)
|
||||
is_digital BOOLEAN DEFAULT FALSE,
|
||||
product_type VARCHAR(20) DEFAULT 'physical',
|
||||
|
||||
-- Identifiers
|
||||
store_sku VARCHAR,
|
||||
gtin VARCHAR,
|
||||
gtin_type VARCHAR(10),
|
||||
brand VARCHAR,
|
||||
|
||||
-- Pricing (in cents)
|
||||
price_cents INTEGER,
|
||||
sale_price_cents INTEGER,
|
||||
currency VARCHAR(3) DEFAULT 'EUR',
|
||||
tax_rate_percent INTEGER DEFAULT 17,
|
||||
availability VARCHAR,
|
||||
|
||||
-- Media
|
||||
primary_image_url VARCHAR,
|
||||
additional_images JSON,
|
||||
|
||||
-- Status
|
||||
is_active BOOLEAN DEFAULT TRUE,
|
||||
is_featured BOOLEAN DEFAULT FALSE,
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMP,
|
||||
updated_at TIMESTAMP
|
||||
);
|
||||
|
||||
-- Index for product type queries
|
||||
CREATE INDEX idx_product_is_digital ON products(is_digital);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration History
|
||||
|
||||
| Migration | Description |
|
||||
|-----------|-------------|
|
||||
| `x2c3d4e5f6g7` | Made `marketplace_product_id` nullable |
|
||||
| `y3d4e5f6g7h8` | Added `is_digital` and `product_type` columns to products |
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Create Product (Admin)
|
||||
|
||||
```
|
||||
POST /api/v1/admin/store-products
|
||||
{
|
||||
"store_id": 1,
|
||||
"translations": {
|
||||
"en": {"title": "Product Name", "description": "..."},
|
||||
"fr": {"title": "Nom du produit", "description": "..."}
|
||||
},
|
||||
"store_sku": "SKU001",
|
||||
"brand": "BrandName",
|
||||
"price": 29.99,
|
||||
"is_digital": false,
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
|
||||
### Update Product (Admin)
|
||||
|
||||
```
|
||||
PATCH /api/v1/admin/store-products/{id}
|
||||
{
|
||||
"is_digital": true,
|
||||
"price": 39.99,
|
||||
"translations": {
|
||||
"en": {"title": "Updated Name"}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
Key test scenarios:
|
||||
|
||||
1. **Direct Product Creation** - Create without marketplace source
|
||||
2. **Digital Product Inventory** - Verify unlimited inventory for digital
|
||||
3. **is_digital Column** - Verify it's an independent column, not derived
|
||||
4. **Source Comparison** - Verify read-only source info display
|
||||
|
||||
See:
|
||||
- `tests/unit/models/database/test_product.py`
|
||||
- `tests/integration/api/v1/admin/test_store_products.py`
|
||||
This document has moved to the catalog module docs: [Architecture](../modules/catalog/architecture.md)
|
||||
|
||||
@@ -1,286 +1,3 @@
|
||||
# Tenancy Module Migration Plan
|
||||
|
||||
This document outlines the complete migration plan for the tenancy module, which manages the multi-tenant organizational hierarchy: platforms, merchants, stores, and users.
|
||||
|
||||
## Tenancy Module Domain
|
||||
|
||||
The tenancy module owns **identity and organizational hierarchy**:
|
||||
|
||||
- **Platforms** - Top-level SaaS instances
|
||||
- **Merchants** - Business entities that own stores
|
||||
- **Stores** - Storefronts/merchant accounts
|
||||
- **Users** - Admin users, store team members
|
||||
- **Authentication** - Login, tokens, sessions
|
||||
- **Teams** - Store team management
|
||||
- **Domains** - Custom domain configuration
|
||||
|
||||
## Migration Overview
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ CURRENT STATE │
|
||||
│ │
|
||||
│ app/api/v1/admin/ app/api/v1/store/ app/services/ │
|
||||
│ ├── admin_users.py ├── auth.py ├── store_service │
|
||||
│ ├── merchants.py ├── profile.py ├── merchant_service │
|
||||
│ ├── platforms.py ├── team.py ├── platform_service │
|
||||
│ ├── stores.py └── ... ├── auth_service │
|
||||
│ ├── users.py └── ... │
|
||||
│ └── auth.py │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ TARGET STATE │
|
||||
│ │
|
||||
│ app/modules/tenancy/ │
|
||||
│ ├── routes/api/ │
|
||||
│ │ ├── admin.py # All admin tenancy routes │
|
||||
│ │ └── store.py # All store tenancy routes │
|
||||
│ ├── services/ │
|
||||
│ │ ├── store_service.py │
|
||||
│ │ ├── merchant_service.py │
|
||||
│ │ ├── platform_service.py │
|
||||
│ │ ├── auth_service.py │
|
||||
│ │ └── team_service.py │
|
||||
│ ├── models/ │
|
||||
│ │ ├── store.py │
|
||||
│ │ ├── merchant.py │
|
||||
│ │ ├── platform.py │
|
||||
│ │ └── user.py │
|
||||
│ └── schemas/ │
|
||||
│ └── ... │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## Files to Migrate to Tenancy
|
||||
|
||||
### Routes (Admin API)
|
||||
|
||||
| Current Location | Target Location | Description |
|
||||
|-----------------|-----------------|-------------|
|
||||
| `app/api/v1/admin/admin_users.py` | `tenancy/routes/api/admin_users.py` | Admin user CRUD |
|
||||
| `app/api/v1/admin/merchants.py` | `tenancy/routes/api/admin_merchants.py` | Merchant management |
|
||||
| `app/api/v1/admin/platforms.py` | `tenancy/routes/api/admin_platforms.py` | Platform management |
|
||||
| `app/api/v1/admin/stores.py` | `tenancy/routes/api/admin_stores.py` | Store management |
|
||||
| `app/api/v1/admin/store_domains.py` | `tenancy/routes/api/admin_store_domains.py` | Domain configuration |
|
||||
| `app/api/v1/admin/users.py` | `tenancy/routes/api/admin_users.py` | Platform users |
|
||||
| `app/api/v1/admin/auth.py` | `tenancy/routes/api/admin_auth.py` | Admin authentication |
|
||||
|
||||
### Routes (Store API)
|
||||
|
||||
| Current Location | Target Location | Description |
|
||||
|-----------------|-----------------|-------------|
|
||||
| `app/api/v1/store/auth.py` | `tenancy/routes/api/store_auth.py` | Store authentication |
|
||||
| `app/api/v1/store/profile.py` | `tenancy/routes/api/store_profile.py` | Store profile |
|
||||
| `app/api/v1/store/team.py` | `tenancy/routes/api/store_team.py` | Team management |
|
||||
|
||||
### Services
|
||||
|
||||
| Current Location | Target Location | Description |
|
||||
|-----------------|-----------------|-------------|
|
||||
| `app/services/store_service.py` | `tenancy/services/store_service.py` | Core store operations |
|
||||
| `app/services/merchant_service.py` | `tenancy/services/merchant_service.py` | Merchant management |
|
||||
| `app/services/platform_service.py` | `tenancy/services/platform_service.py` | Platform management |
|
||||
| `app/services/admin_service.py` | `tenancy/services/admin_service.py` | Admin user operations |
|
||||
| `app/services/admin_platform_service.py` | `tenancy/services/admin_platform_service.py` | Admin-platform relations |
|
||||
| `app/services/store_domain_service.py` | `tenancy/services/store_domain_service.py` | Domain management |
|
||||
| `app/services/store_team_service.py` | `tenancy/services/store_team_service.py` | Team management |
|
||||
| `app/services/team_service.py` | `tenancy/services/team_service.py` | Team operations |
|
||||
| `app/services/auth_service.py` | `tenancy/services/auth_service.py` | Authentication logic |
|
||||
| `app/services/platform_signup_service.py` | `tenancy/services/platform_signup_service.py` | Platform onboarding |
|
||||
|
||||
### Models
|
||||
|
||||
| Current Location | Target Location | Description |
|
||||
|-----------------|-----------------|-------------|
|
||||
| `models/database/store.py` | `tenancy/models/store.py` | Store entity |
|
||||
| `models/database/merchant.py` | `tenancy/models/merchant.py` | Merchant entity |
|
||||
| `models/database/platform.py` | `tenancy/models/platform.py` | Platform entity |
|
||||
| `models/database/admin.py` | `tenancy/models/admin.py` | Admin user entity |
|
||||
| `models/database/admin_platform.py` | `tenancy/models/admin_platform.py` | Admin-Platform relation |
|
||||
| `models/database/store_domain.py` | `tenancy/models/store_domain.py` | Store domains |
|
||||
| `models/database/store_platform.py` | `tenancy/models/store_platform.py` | Store-Platform relation |
|
||||
| `models/database/user.py` | `tenancy/models/user.py` | User base model |
|
||||
|
||||
### Schemas
|
||||
|
||||
| Current Location | Target Location | Description |
|
||||
|-----------------|-----------------|-------------|
|
||||
| `models/schema/store.py` | `tenancy/schemas/store.py` | Store schemas |
|
||||
| `models/schema/merchant.py` | `tenancy/schemas/merchant.py` | Merchant schemas |
|
||||
| `models/schema/platform.py` | `tenancy/schemas/platform.py` | Platform schemas |
|
||||
| `models/schema/admin.py` | `tenancy/schemas/admin.py` | Admin schemas |
|
||||
| `models/schema/auth.py` | `tenancy/schemas/auth.py` | Auth schemas |
|
||||
|
||||
## Files to Keep in ROOT (Framework Level)
|
||||
|
||||
These are **framework infrastructure**, not domain logic:
|
||||
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `app/api/v1/admin/modules.py` | Module system management |
|
||||
| `app/api/v1/admin/module_config.py` | Module configuration |
|
||||
| `app/api/v1/admin/menu_config.py` | Menu system |
|
||||
| `app/api/v1/admin/settings.py` | System settings |
|
||||
| `models/database/base.py` | SQLAlchemy base |
|
||||
| `models/database/platform_module.py` | Module enablement |
|
||||
| `models/database/admin_menu_config.py` | Menu configuration |
|
||||
|
||||
## Files to Migrate to OTHER Modules
|
||||
|
||||
### → `modules/monitoring/` (or `dev_tools`)
|
||||
|
||||
| File | Target Module | Reason |
|
||||
|------|---------------|--------|
|
||||
| `admin/background_tasks.py` | monitoring | Task monitoring |
|
||||
| `admin/code_quality.py` | dev_tools | Dev tools |
|
||||
| `admin/logs.py` | monitoring | Log viewer |
|
||||
| `admin/monitoring.py` | monitoring | Monitoring |
|
||||
| `admin/platform_health.py` | monitoring | Health checks |
|
||||
| `admin/tests.py` | dev_tools | Test runner |
|
||||
| `admin/audit.py` | monitoring | Audit logs |
|
||||
|
||||
### → `modules/messaging/`
|
||||
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `admin/messages.py` | Message management |
|
||||
| `admin/notifications.py` | Notification management |
|
||||
| `admin/email_templates.py` | Email templates |
|
||||
| `store/messages.py` | Store messages |
|
||||
| `store/notifications.py` | Store notifications |
|
||||
| `store/email_settings.py` | Email settings |
|
||||
| `store/email_templates.py` | Email templates |
|
||||
|
||||
### → `modules/cms/`
|
||||
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `admin/media.py` | Media library |
|
||||
| `admin/images.py` | Image management |
|
||||
| `store/media.py` | Store media |
|
||||
| `admin/store_themes.py` | Theme management |
|
||||
|
||||
### → `modules/core/` (new module)
|
||||
|
||||
| File | Reason |
|
||||
|------|--------|
|
||||
| `admin/dashboard.py` | Admin dashboard |
|
||||
| `store/dashboard.py` | Store dashboard |
|
||||
| `store/settings.py` | Store settings |
|
||||
|
||||
## Target Module Structure
|
||||
|
||||
After migration, tenancy module will have this structure:
|
||||
|
||||
```
|
||||
app/modules/tenancy/
|
||||
├── __init__.py
|
||||
├── definition.py
|
||||
├── exceptions.py
|
||||
├── config.py # Environment configuration
|
||||
├── routes/
|
||||
│ ├── __init__.py
|
||||
│ ├── api/
|
||||
│ │ ├── __init__.py
|
||||
│ │ ├── admin.py # Aggregates admin sub-routers
|
||||
│ │ ├── admin_users.py # Admin user management
|
||||
│ │ ├── admin_merchants.py # Merchant management
|
||||
│ │ ├── admin_platforms.py # Platform management
|
||||
│ │ ├── admin_stores.py # Store management
|
||||
│ │ ├── admin_store_domains.py
|
||||
│ │ ├── admin_auth.py # Admin authentication
|
||||
│ │ ├── store.py # Aggregates store sub-routers
|
||||
│ │ ├── store_auth.py # Store authentication
|
||||
│ │ ├── store_profile.py # Store profile
|
||||
│ │ ├── store_team.py # Team management
|
||||
│ │ └── store_info.py # Public store lookup (DONE)
|
||||
│ └── pages/
|
||||
│ ├── __init__.py
|
||||
│ ├── admin.py # Admin HTML pages
|
||||
│ └── store.py # Store HTML pages
|
||||
├── services/
|
||||
│ ├── __init__.py
|
||||
│ ├── store_service.py
|
||||
│ ├── merchant_service.py
|
||||
│ ├── platform_service.py
|
||||
│ ├── admin_service.py
|
||||
│ ├── auth_service.py
|
||||
│ ├── team_service.py
|
||||
│ └── store_domain_service.py
|
||||
├── models/
|
||||
│ ├── __init__.py
|
||||
│ ├── store.py
|
||||
│ ├── merchant.py
|
||||
│ ├── platform.py
|
||||
│ ├── admin.py
|
||||
│ ├── user.py
|
||||
│ ├── store_domain.py
|
||||
│ └── store_platform.py
|
||||
├── schemas/
|
||||
│ ├── __init__.py
|
||||
│ ├── store.py
|
||||
│ ├── merchant.py
|
||||
│ ├── platform.py
|
||||
│ ├── admin.py
|
||||
│ └── auth.py
|
||||
├── templates/
|
||||
│ └── tenancy/
|
||||
│ ├── admin/
|
||||
│ └── store/
|
||||
├── static/
|
||||
│ ├── admin/js/
|
||||
│ └── store/js/
|
||||
└── locales/
|
||||
├── en.json
|
||||
├── de.json
|
||||
├── fr.json
|
||||
└── lb.json
|
||||
```
|
||||
|
||||
## Module Ownership Summary
|
||||
|
||||
| Module | Owns | Key Principle |
|
||||
|--------|------|---------------|
|
||||
| **tenancy** | Stores, Merchants, Platforms, Users, Auth, Teams | Identity & organizational hierarchy |
|
||||
| **core** | Dashboard, Settings | Foundational non-domain features |
|
||||
| **messaging** | Messages, Notifications, Email | Communication |
|
||||
| **cms** | Media, Images, Themes, Content | Content management |
|
||||
| **monitoring** | Logs, Health, Tasks, Audit | Observability |
|
||||
| **dev_tools** | Code quality, Tests | Development tools |
|
||||
| **ROOT** | Module system, Menu config, Base models | Framework infrastructure |
|
||||
|
||||
## Migration Order
|
||||
|
||||
Recommended order for migrating tenancy:
|
||||
|
||||
1. **Phase 1: Services** (no route changes)
|
||||
- Move services to `tenancy/services/`
|
||||
- Update imports throughout codebase
|
||||
- Keep re-exports in `app/services/` temporarily
|
||||
|
||||
2. **Phase 2: Models** (careful - many dependencies)
|
||||
- Move models to `tenancy/models/`
|
||||
- Update all imports
|
||||
- May need re-exports in `models/database/`
|
||||
|
||||
3. **Phase 3: Schemas**
|
||||
- Move schemas to `tenancy/schemas/`
|
||||
- Update route imports
|
||||
|
||||
4. **Phase 4: Routes**
|
||||
- Move routes to `tenancy/routes/api/`
|
||||
- Update aggregation in admin/store `__init__.py`
|
||||
- Delete legacy route files
|
||||
|
||||
5. **Phase 5: Cleanup**
|
||||
- Remove re-exports
|
||||
- Delete empty legacy files
|
||||
- Update documentation
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Module System Architecture](module-system.md)
|
||||
- [Module Auto-Discovery Migration](../development/migration/module-autodiscovery-migration.md)
|
||||
- [Multi-Tenant Architecture](multi-tenant.md)
|
||||
This document has moved to the tenancy module docs: [Tenancy Migration Plan](../modules/tenancy/migration.md)
|
||||
|
||||
Reference in New Issue
Block a user