refactor: migrate modules from re-exports to canonical implementations

Move actual code implementations into module directories:
- orders: 5 services, 4 models, order/invoice schemas
- inventory: 3 services, 2 models, 30+ schemas
- customers: 3 services, 2 models, customer schemas
- messaging: 3 services, 2 models, message/notification schemas
- monitoring: background_tasks_service
- marketplace: 5+ services including letzshop submodule
- dev_tools: code_quality_service, test_runner_service
- billing: billing_service
- contracts: definition.py

Legacy files in app/services/, models/database/, models/schema/
now re-export from canonical module locations for backwards
compatibility. Architecture validator passes with 0 errors.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-29 21:28:56 +01:00
parent b5a803cde8
commit de83875d0a
99 changed files with 19413 additions and 15357 deletions

View File

@@ -1,242 +1,23 @@
# app/services/admin_customer_service.py
"""
Admin customer management service.
LEGACY LOCATION - Re-exports from module for backwards compatibility.
Handles customer operations for admin users across all vendors.
The canonical implementation is now in:
app/modules/customers/services/admin_customer_service.py
This file exists to maintain backwards compatibility with code that
imports from the old location. All new code should import directly
from the module:
from app.modules.customers.services import admin_customer_service
"""
import logging
from typing import Any
from app.modules.customers.services.admin_customer_service import (
admin_customer_service,
AdminCustomerService,
)
from sqlalchemy import func
from sqlalchemy.orm import Session
from app.exceptions.customer import CustomerNotFoundException
from models.database.customer import Customer
from models.database.vendor import Vendor
logger = logging.getLogger(__name__)
class AdminCustomerService:
"""Service for admin-level customer management across vendors."""
def list_customers(
self,
db: Session,
vendor_id: int | None = None,
search: str | None = None,
is_active: bool | None = None,
skip: int = 0,
limit: int = 20,
) -> tuple[list[dict[str, Any]], int]:
"""
Get paginated list of customers across all vendors.
Args:
db: Database session
vendor_id: Optional vendor ID filter
search: Search by email, name, or customer number
is_active: Filter by active status
skip: Number of records to skip
limit: Maximum records to return
Returns:
Tuple of (customers list, total count)
"""
# Build query
query = db.query(Customer).join(Vendor, Customer.vendor_id == Vendor.id)
# Apply filters
if vendor_id:
query = query.filter(Customer.vendor_id == vendor_id)
if search:
search_term = f"%{search}%"
query = query.filter(
(Customer.email.ilike(search_term))
| (Customer.first_name.ilike(search_term))
| (Customer.last_name.ilike(search_term))
| (Customer.customer_number.ilike(search_term))
)
if is_active is not None:
query = query.filter(Customer.is_active == is_active)
# Get total count
total = query.count()
# Get paginated results with vendor info
customers = (
query.add_columns(Vendor.name.label("vendor_name"), Vendor.vendor_code)
.order_by(Customer.created_at.desc())
.offset(skip)
.limit(limit)
.all()
)
# Format response
result = []
for row in customers:
customer = row[0]
vendor_name = row[1]
vendor_code = row[2]
customer_dict = {
"id": customer.id,
"vendor_id": customer.vendor_id,
"email": customer.email,
"first_name": customer.first_name,
"last_name": customer.last_name,
"phone": customer.phone,
"customer_number": customer.customer_number,
"marketing_consent": customer.marketing_consent,
"preferred_language": customer.preferred_language,
"last_order_date": customer.last_order_date,
"total_orders": customer.total_orders,
"total_spent": float(customer.total_spent) if customer.total_spent else 0,
"is_active": customer.is_active,
"created_at": customer.created_at,
"updated_at": customer.updated_at,
"vendor_name": vendor_name,
"vendor_code": vendor_code,
}
result.append(customer_dict)
return result, total
def get_customer_stats(
self,
db: Session,
vendor_id: int | None = None,
) -> dict[str, Any]:
"""
Get customer statistics.
Args:
db: Database session
vendor_id: Optional vendor ID filter
Returns:
Dict with customer statistics
"""
query = db.query(Customer)
if vendor_id:
query = query.filter(Customer.vendor_id == vendor_id)
total = query.count()
active = query.filter(Customer.is_active == True).count() # noqa: E712
inactive = query.filter(Customer.is_active == False).count() # noqa: E712
with_orders = query.filter(Customer.total_orders > 0).count()
# Total spent across all customers
total_spent_result = query.with_entities(func.sum(Customer.total_spent)).scalar()
total_spent = float(total_spent_result) if total_spent_result else 0
# Average order value
total_orders_result = query.with_entities(func.sum(Customer.total_orders)).scalar()
total_orders = int(total_orders_result) if total_orders_result else 0
avg_order_value = total_spent / total_orders if total_orders > 0 else 0
return {
"total": total,
"active": active,
"inactive": inactive,
"with_orders": with_orders,
"total_spent": total_spent,
"total_orders": total_orders,
"avg_order_value": round(avg_order_value, 2),
}
def get_customer(
self,
db: Session,
customer_id: int,
) -> dict[str, Any]:
"""
Get customer details by ID.
Args:
db: Database session
customer_id: Customer ID
Returns:
Customer dict with vendor info
Raises:
CustomerNotFoundException: If customer not found
"""
result = (
db.query(Customer)
.join(Vendor, Customer.vendor_id == Vendor.id)
.add_columns(Vendor.name.label("vendor_name"), Vendor.vendor_code)
.filter(Customer.id == customer_id)
.first()
)
if not result:
raise CustomerNotFoundException(str(customer_id))
customer = result[0]
return {
"id": customer.id,
"vendor_id": customer.vendor_id,
"email": customer.email,
"first_name": customer.first_name,
"last_name": customer.last_name,
"phone": customer.phone,
"customer_number": customer.customer_number,
"marketing_consent": customer.marketing_consent,
"preferred_language": customer.preferred_language,
"last_order_date": customer.last_order_date,
"total_orders": customer.total_orders,
"total_spent": float(customer.total_spent) if customer.total_spent else 0,
"is_active": customer.is_active,
"created_at": customer.created_at,
"updated_at": customer.updated_at,
"vendor_name": result[1],
"vendor_code": result[2],
}
def toggle_customer_status(
self,
db: Session,
customer_id: int,
admin_email: str,
) -> dict[str, Any]:
"""
Toggle customer active status.
Args:
db: Database session
customer_id: Customer ID
admin_email: Admin user email for logging
Returns:
Dict with customer ID, new status, and message
Raises:
CustomerNotFoundException: If customer not found
"""
customer = db.query(Customer).filter(Customer.id == customer_id).first()
if not customer:
raise CustomerNotFoundException(str(customer_id))
customer.is_active = not customer.is_active
db.flush()
db.refresh(customer)
status = "activated" if customer.is_active else "deactivated"
logger.info(f"Customer {customer.email} {status} by admin {admin_email}")
return {
"id": customer.id,
"is_active": customer.is_active,
"message": f"Customer {status} successfully",
}
# Singleton instance
admin_customer_service = AdminCustomerService()
__all__ = [
"admin_customer_service",
"AdminCustomerService",
]