# 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
