# app/services/admin_customer_service.py """ Admin customer management service. Handles customer operations for admin users across all vendors. """ import logging from typing import Any 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()