refactor: fix architecture violations with provider patterns and dependency inversion

Major changes:
- Add AuditProvider protocol for cross-module audit logging
- Move customer order operations to orders module (dependency inversion)
- Add customer order metrics via MetricsProvider pattern
- Fix missing db parameter in get_admin_context() calls
- Move ProductMedia relationship to catalog module (proper ownership)
- Add marketplace breakdown stats to marketplace_widgets

New files:
- contracts/audit.py - AuditProviderProtocol
- core/services/audit_aggregator.py - Aggregates audit providers
- monitoring/services/audit_provider.py - Monitoring audit implementation
- orders/services/customer_order_service.py - Customer order operations
- orders/routes/api/vendor_customer_orders.py - Customer order endpoints
- catalog/services/product_media_service.py - Product media service
- Architecture documentation for patterns

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-04 21:32:32 +01:00
parent bd43e21940
commit 39dff4ab7d
34 changed files with 2751 additions and 407 deletions

View File

@@ -284,6 +284,9 @@ def ship_order_item(
# ============================================================================
# Import sub-routers
from app.modules.orders.routes.api.vendor_customer_orders import (
vendor_customer_orders_router,
)
from app.modules.orders.routes.api.vendor_exceptions import vendor_exceptions_router
from app.modules.orders.routes.api.vendor_invoices import vendor_invoices_router
@@ -291,3 +294,4 @@ from app.modules.orders.routes.api.vendor_invoices import vendor_invoices_router
vendor_router.include_router(_orders_router, tags=["vendor-orders"])
vendor_router.include_router(vendor_exceptions_router, tags=["vendor-order-exceptions"])
vendor_router.include_router(vendor_invoices_router, tags=["vendor-invoices"])
vendor_router.include_router(vendor_customer_orders_router, tags=["vendor-customer-orders"])

View File

@@ -0,0 +1,163 @@
# app/modules/orders/routes/api/vendor_customer_orders.py
"""
Vendor customer order endpoints.
These endpoints provide customer-order data, owned by the orders module.
The orders module owns the relationship between customers and orders,
similar to how catalog owns the ProductMedia relationship.
Vendor Context: Uses token_vendor_id from JWT token.
"""
import logging
from datetime import datetime
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel
from sqlalchemy.orm import Session
from app.api.deps import get_current_vendor_api, require_module_access
from app.core.database import get_db
from app.modules.enums import FrontendType
from app.modules.orders.services.customer_order_service import customer_order_service
from app.modules.orders.services.order_metrics import order_metrics_provider
from models.schema.auth import UserContext
logger = logging.getLogger(__name__)
# Router for customer-order endpoints
vendor_customer_orders_router = APIRouter(
prefix="/customers",
dependencies=[Depends(require_module_access("orders", FrontendType.VENDOR))],
)
# ============================================================================
# Response Models
# ============================================================================
class CustomerOrderItem(BaseModel):
"""Order summary for customer order list."""
id: int
order_number: str
status: str
total: float
created_at: datetime
class Config:
from_attributes = True
class CustomerOrdersResponse(BaseModel):
"""Response for customer orders list."""
orders: list[CustomerOrderItem]
total: int
skip: int
limit: int
class CustomerOrderStatistic(BaseModel):
"""Single statistic value."""
key: str
value: int | float | str
label: str
icon: str | None = None
unit: str | None = None
class CustomerOrderStatsResponse(BaseModel):
"""Response for customer order statistics."""
customer_id: int
statistics: list[CustomerOrderStatistic]
# ============================================================================
# Endpoints
# ============================================================================
@vendor_customer_orders_router.get(
"/{customer_id}/orders",
response_model=CustomerOrdersResponse,
)
def get_customer_orders(
customer_id: int,
skip: int = Query(0, ge=0),
limit: int = Query(50, ge=1, le=100),
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get order history for a specific customer.
This endpoint is owned by the orders module because:
- Orders module owns the Order model and customer-order relationship
- Customers module is agnostic to what uses customers
Similar to how catalog owns ProductMedia (product-media relationship)
while CMS provides generic MediaFile storage.
"""
orders, total = customer_order_service.get_customer_orders(
db=db,
vendor_id=current_user.token_vendor_id,
customer_id=customer_id,
skip=skip,
limit=limit,
)
return CustomerOrdersResponse(
orders=[
CustomerOrderItem(
id=o.id,
order_number=o.order_number,
status=o.status,
total=o.total_cents / 100 if o.total_cents else 0,
created_at=o.created_at,
)
for o in orders
],
total=total,
skip=skip,
limit=limit,
)
@vendor_customer_orders_router.get(
"/{customer_id}/order-stats",
response_model=CustomerOrderStatsResponse,
)
def get_customer_order_stats(
customer_id: int,
current_user: UserContext = Depends(get_current_vendor_api),
db: Session = Depends(get_db),
):
"""
Get order statistics for a specific customer.
Uses the MetricsProvider pattern to provide customer-level order metrics.
Returns statistics like total orders, total spent, average order value, etc.
"""
metrics = order_metrics_provider.get_customer_order_metrics(
db=db,
vendor_id=current_user.token_vendor_id,
customer_id=customer_id,
)
return CustomerOrderStatsResponse(
customer_id=customer_id,
statistics=[
CustomerOrderStatistic(
key=m.key,
value=m.value,
label=m.label,
icon=m.icon,
unit=m.unit,
)
for m in metrics
],
)