# Module System Architecture

The Wizamart platform uses a **plug-and-play modular architecture** where modules are fully self-contained and automatically discovered. Simply create a module directory with the required structure, and the framework handles registration, routing, and resource loading automatically.

## Key Features

- **Auto-Discovery**: Modules are automatically discovered from `app/modules/*/definition.py`
- **Zero Configuration**: No changes to `main.py`, `registry.py`, or other framework files needed
- **Self-Contained**: Each module owns its routes, services, models, templates, and translations
- **Hot-Pluggable**: Add or remove modules by simply adding/removing directories

## Overview

```
┌─────────────────────────────────────────────────────────────────────┐
│                        FRAMEWORK LAYER                               │
│  (Infrastructure that modules depend on - not modules themselves)    │
│                                                                      │
│  Config │ Database │ Auth │ Permissions │ Observability │ Celery    │
└─────────────────────────────────────────────────────────────────────┘
                                  │
                                  ▼
┌─────────────────────────────────────────────────────────────────────┐
│                    AUTO-DISCOVERED MODULE LAYER                      │
│                                                                      │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │              CORE MODULES (Always Enabled)                   │    │
│  │     core  │  tenancy  │  cms  │  customers                  │    │
│  └─────────────────────────────────────────────────────────────┘    │
│                                                                      │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │           OPTIONAL MODULES (Per-Platform)                    │    │
│  │  payments │ billing │ inventory │ orders │ marketplace │ ...│    │
│  └─────────────────────────────────────────────────────────────┘    │
│                                                                      │
│  ┌─────────────────────────────────────────────────────────────┐    │
│  │            INTERNAL MODULES (Admin Only)                     │    │
│  │              dev-tools  │  monitoring                        │    │
│  └─────────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────────┘
```

## Auto-Discovery System

All module components are automatically discovered by the framework:

| Component | Discovery Location | Auto-Loaded By |
|-----------|-------------------|----------------|
| **Registry** | `*/definition.py` | `app/modules/discovery.py` |
| **Configuration** | `*/config.py` | `app/modules/config.py` |
| **API Routes** | `*/routes/api/*.py` | `app/modules/routes.py` |
| **Page Routes** | `*/routes/pages/*.py` | `app/modules/routes.py` |
| **Tasks** | `*/tasks/__init__.py` | `app/modules/tasks.py` |
| **Templates** | `*/templates/` | `app/templates_config.py` |
| **Static Files** | `*/static/` | `main.py` |
| **Locales** | `*/locales/*.json` | `app/utils/i18n.py` |
| **Migrations** | `*/migrations/versions/` | `app/modules/migrations.py` |

### Creating a New Module (Zero Framework Changes)

```bash
# 1. Create module directory
mkdir -p app/modules/mymodule/{routes/{api,pages},services,models,schemas,templates/mymodule/vendor,static/vendor/js,locales,tasks}

# 2. Create required files
touch app/modules/mymodule/__init__.py
touch app/modules/mymodule/definition.py
touch app/modules/mymodule/exceptions.py

# 3. That's it! The framework auto-discovers and registers everything.
```

## Three-Tier Classification

### Core Modules (4)

Core modules are **always enabled** and cannot be disabled. They provide fundamental platform functionality.

| Module | Description | Key Features |
|--------|-------------|--------------|
| `core` | Dashboard, settings, profile | Basic platform operation |
| `tenancy` | Platform, company, vendor, admin user management | Multi-tenant infrastructure |
| `cms` | Content pages, media library, themes | Content management |
| `customers` | Customer database, profiles, segmentation | Customer data management |

### Optional Modules (10)

Optional modules can be **enabled or disabled per platform**. They provide additional functionality that may not be needed by all platforms.

| Module | Dependencies | Description |
|--------|--------------|-------------|
| `cart` | - | Shopping cart management, session-based carts |
| `catalog` | - | Customer-facing product browsing |
| `checkout` | `cart`, `orders`, `payments` | Cart-to-order conversion, checkout flow |
| `payments` | - | Payment gateway integrations (Stripe, PayPal, etc.) |
| `billing` | `payments` | Platform subscriptions, vendor invoices |
| `inventory` | - | Stock management, locations |
| `orders` | `payments` | Order management, customer checkout |
| `marketplace` | `inventory` | Letzshop integration |
| `analytics` | - | Reports, dashboards |
| `messaging` | - | Messages, notifications |

### Internal Modules (2)

Internal modules are **admin-only tools** not exposed to customers or vendors.

| Module | Description |
|--------|-------------|
| `dev-tools` | Component library, icon browser |
| `monitoring` | Logs, background tasks, Flower, Grafana integration |

## Self-Contained Module Structure

Every module follows this standardized structure:

```
app/modules/analytics/
├── __init__.py              # Module package marker
├── definition.py            # ModuleDefinition (REQUIRED for auto-discovery)
├── config.py                # Environment config (auto-discovered)
├── exceptions.py            # Module-specific exceptions
├── routes/
│   ├── __init__.py
│   ├── api/                 # API endpoints (auto-discovered)
│   │   ├── __init__.py
│   │   ├── admin.py         # Must export: router = APIRouter()
│   │   └── vendor.py        # Must export: router = APIRouter()
│   └── pages/               # HTML page routes (auto-discovered)
│       ├── __init__.py
│       └── vendor.py        # Must export: router = APIRouter()
├── services/
│   ├── __init__.py
│   └── stats_service.py
├── models/
│   ├── __init__.py
│   └── report.py
├── schemas/
│   ├── __init__.py
│   └── stats.py
├── templates/               # Auto-discovered by Jinja2
│   └── analytics/
│       └── vendor/
│           └── analytics.html
├── static/                  # Auto-mounted at /static/modules/analytics/
│   └── vendor/
│       └── js/
│           └── analytics.js
├── locales/                 # Auto-loaded translations
│   ├── en.json
│   ├── de.json
│   ├── fr.json
│   └── lu.json
├── tasks/                   # Auto-discovered by Celery
│   ├── __init__.py          # REQUIRED for Celery discovery
│   └── reports.py
└── migrations/              # Auto-discovered by Alembic
    ├── __init__.py          # REQUIRED for discovery
    └── versions/
        ├── __init__.py      # REQUIRED for discovery
        └── analytics_001_create_reports.py
```

## Module Definition

Each module must have a `definition.py` with a `ModuleDefinition` instance:

```python
# app/modules/analytics/definition.py
from app.modules.base import ModuleDefinition
from models.database.admin_menu_config import FrontendType

analytics_module = ModuleDefinition(
    # Identity
    code="analytics",
    name="Analytics & Reporting",
    description="Dashboard analytics, custom reports, and data exports.",
    version="1.0.0",

    # Classification (determines tier)
    is_core=False,      # Set True for core modules
    is_internal=False,  # Set True for admin-only modules

    # Dependencies
    requires=[],  # List other module codes this depends on

    # Features (for tier-based gating)
    features=[
        "basic_reports",
        "analytics_dashboard",
        "custom_reports",
    ],

    # Menu items per frontend
    menu_items={
        FrontendType.ADMIN: [],  # Analytics uses dashboard
        FrontendType.VENDOR: ["analytics"],
    },

    # Self-contained module configuration
    is_self_contained=True,
    services_path="app.modules.analytics.services",
    models_path="app.modules.analytics.models",
    schemas_path="app.modules.analytics.schemas",
    exceptions_path="app.modules.analytics.exceptions",
    templates_path="templates",
    locales_path="locales",
)
```

### ModuleDefinition Fields

| Field | Type | Description |
|-------|------|-------------|
| `code` | `str` | Unique identifier (e.g., "billing") |
| `name` | `str` | Display name |
| `description` | `str` | What the module provides |
| `version` | `str` | Semantic version (default: "1.0.0") |
| `requires` | `list[str]` | Module codes this depends on |
| `features` | `list[str]` | Feature codes for tier gating |
| `menu_items` | `dict` | Menu items per frontend type |
| `is_core` | `bool` | Cannot be disabled if True |
| `is_internal` | `bool` | Admin-only if True |
| `is_self_contained` | `bool` | Uses self-contained structure |

## Route Auto-Discovery

Routes in `routes/api/` and `routes/pages/` are automatically discovered and registered.

### API Routes (`routes/api/vendor.py`)

```python
# app/modules/analytics/routes/api/vendor.py
from fastapi import APIRouter, Depends
from app.api.deps import get_current_vendor_api, get_db

router = APIRouter()  # MUST be named 'router' for auto-discovery

@router.get("")
def get_analytics(
    current_user = Depends(get_current_vendor_api),
    db = Depends(get_db),
):
    """Get vendor analytics."""
    pass
```

**Auto-registered at:** `/api/v1/vendor/analytics`

### Page Routes (`routes/pages/vendor.py`)

```python
# app/modules/analytics/routes/pages/vendor.py
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse

router = APIRouter()  # MUST be named 'router' for auto-discovery

@router.get("/{vendor_code}/analytics", response_class=HTMLResponse)
async def analytics_page(request: Request, vendor_code: str):
    """Render analytics page."""
    pass
```

**Auto-registered at:** `/vendor/{vendor_code}/analytics`

## Framework Layer

The Framework Layer provides infrastructure that modules depend on. These are **not modules** - they're always available and cannot be disabled.

| Component | Location | Purpose |
|-----------|----------|---------|
| Config | `app/core/config.py` | Settings management |
| Database | `app/core/database.py` | SQLAlchemy sessions |
| Logging | `app/core/logging.py` | Structured logging |
| Permissions | `app/core/permissions.py` | RBAC definitions |
| Feature Gate | `app/core/feature_gate.py` | Tier-based access |
| Celery | `app/core/celery_config.py` | Task queue |
| Observability | `app/core/observability.py` | Health checks, metrics, Sentry |
| Auth Middleware | `middleware/auth.py` | JWT authentication |
| Context Middleware | `middleware/platform_context.py` | Multi-tenancy |
| Dependencies | `app/api/deps.py` | FastAPI DI |
| Base Exceptions | `app/exceptions/base.py` | Exception hierarchy |

## Module Dependencies

Modules can depend on other modules. When enabling a module, its dependencies are automatically enabled.

```
                         payments
                        ↙        ↘
                   billing        orders ←──┐
                                     ↑      │
              inventory              │      │
                  ↓                cart     │
             marketplace               ↘    │
                                      checkout
```

**Dependency Rules:**

1. Core modules cannot depend on optional modules
2. Enabling a module auto-enables its dependencies
3. Disabling a module auto-disables modules that depend on it
4. Circular dependencies are not allowed

## Module Registry

The registry auto-discovers all modules:

```python
from app.modules.registry import (
    MODULES,           # All modules (auto-discovered)
    CORE_MODULES,      # Core only
    OPTIONAL_MODULES,  # Optional only
    INTERNAL_MODULES,  # Internal only
    get_module,
    get_core_module_codes,
    get_module_tier,
)

# Get a specific module
billing = get_module("billing")

# Check module tier
tier = get_module_tier("billing")  # Returns "optional"

# Get all core module codes
core_codes = get_core_module_codes()  # {"core", "tenancy", "cms", "customers"}
```

## Module Service

The `ModuleService` manages module enablement per platform:

```python
from app.modules.service import module_service

# Check if module is enabled
if module_service.is_module_enabled(db, platform_id, "billing"):
    pass

# Get all enabled modules for a platform
modules = module_service.get_platform_modules(db, platform_id)

# Enable a module (auto-enables dependencies)
module_service.enable_module(db, platform_id, "billing", user_id=current_user.id)

# Disable a module (auto-disables dependents)
module_service.disable_module(db, platform_id, "billing", user_id=current_user.id)
```

## Module Configuration

Modules can have environment-based configuration using Pydantic Settings. The `config.py` file is auto-discovered by `app/modules/config.py`.

```python
# app/modules/marketplace/config.py
from pydantic import Field
from pydantic_settings import BaseSettings

class MarketplaceConfig(BaseSettings):
    """Configuration for marketplace module."""

    # Settings loaded from env vars with MARKETPLACE_ prefix
    api_timeout: int = Field(default=30, description="API timeout in seconds")
    batch_size: int = Field(default=100, description="Import batch size")
    max_retries: int = Field(default=3, description="Max retry attempts")

    model_config = {"env_prefix": "MARKETPLACE_"}

# Export for auto-discovery
config_class = MarketplaceConfig
config = MarketplaceConfig()
```

**Usage:**

```python
# Direct import
from app.modules.marketplace.config import config
timeout = config.api_timeout

# Via discovery
from app.modules.config import get_module_config
config = get_module_config("marketplace")
```

**Environment variables:**
```bash
MARKETPLACE_API_TIMEOUT=60
MARKETPLACE_BATCH_SIZE=500
```

## Module Migrations

Each module owns its database migrations in the `migrations/versions/` directory. Alembic auto-discovers these via `app/modules/migrations.py`.

### Migration Structure

```
app/modules/cms/migrations/
├── __init__.py           # REQUIRED for discovery
└── versions/
    ├── __init__.py       # REQUIRED for discovery
    ├── cms_001_create_content_pages.py
    ├── cms_002_add_sections.py
    └── cms_003_add_media_library.py
```

### Naming Convention

```
{module_code}_{sequence}_{description}.py
```

### Migration Template

```python
# app/modules/cms/migrations/versions/cms_001_create_content_pages.py
"""Create content_pages table.

Revision ID: cms_001
Create Date: 2026-01-28
"""
from alembic import op
import sqlalchemy as sa

revision = "cms_001"
down_revision = None
branch_labels = ("cms",)  # Module-specific branch

def upgrade() -> None:
    op.create_table(
        "content_pages",
        sa.Column("id", sa.Integer(), primary_key=True),
        sa.Column("vendor_id", sa.Integer(), sa.ForeignKey("vendors.id")),
        sa.Column("slug", sa.String(100), nullable=False),
        sa.Column("title", sa.String(200), nullable=False),
    )

def downgrade() -> None:
    op.drop_table("content_pages")
```

### Running Migrations

Module migrations are automatically discovered:

```bash
# Run all migrations (core + modules)
alembic upgrade head

# View migration history
alembic history
```

### Current State

Currently, all migrations reside in central `alembic/versions/`. The module-specific directories are in place for:
- **New modules**: Should create migrations in their own `migrations/versions/`
- **Future reorganization**: Existing migrations will be moved to modules pre-production

## Architecture Validation Rules

The architecture validator (`scripts/validate_architecture.py`) enforces module structure:

| Rule | Severity | Description |
|------|----------|-------------|
| MOD-001 | ERROR | Self-contained modules must have required directories |
| MOD-002 | WARNING | Services must contain actual code, not re-exports |
| MOD-003 | WARNING | Schemas must contain actual code, not re-exports |
| MOD-004 | WARNING | Routes must import from module, not legacy locations |
| MOD-005 | WARNING | Modules with UI must have templates and static |
| MOD-006 | INFO | Modules should have locales for i18n |
| MOD-007 | ERROR | Definition paths must match directory structure |
| MOD-008 | WARNING | Self-contained modules must have exceptions.py |
| MOD-009 | ERROR | Modules must have definition.py for auto-discovery |
| MOD-010 | WARNING | Route files must export `router` variable |
| MOD-011 | WARNING | Tasks directory must have `__init__.py` |
| MOD-012 | INFO | Locales should have all language files |
| MOD-013 | INFO | config.py should export `config` or `config_class` |
| MOD-014 | WARNING | Migrations must follow naming convention |
| MOD-015 | WARNING | Migrations directory must have `__init__.py` files |

Run validation:
```bash
python scripts/validate_architecture.py
```

## Best Practices

### Do

- Keep modules focused on a single domain
- Use `requires` for hard dependencies
- Provide `health_check` for critical modules
- Use events for cross-module communication
- Follow the standard directory structure
- Export `router` variable in route files
- Include all supported languages in locales

### Don't

- Create circular dependencies
- Make core modules depend on optional modules
- Put framework-level code in modules
- Skip migration naming conventions
- Forget `__init__.py` in tasks directory
- Manually register modules in registry.py (use auto-discovery)

## Related Documentation

- [Creating Modules](../development/creating-modules.md) - Step-by-step guide
- [Menu Management](menu-management.md) - Sidebar configuration
- [Observability](observability.md) - Health checks integration
- [Feature Gating](../implementation/feature-gating-system.md) - Tier-based access
