# app/services/vendor_product_service.py """ Vendor product service for managing vendor-specific product catalogs. This module provides: - Vendor product catalog browsing - Product search and filtering - Product statistics - Product removal from catalogs """ import logging from sqlalchemy import func from sqlalchemy.orm import Session, joinedload from app.exceptions import ProductNotFoundException from models.database.product import Product from models.database.vendor import Vendor logger = logging.getLogger(__name__) class VendorProductService: """Service for vendor product catalog operations.""" def get_products( self, db: Session, skip: int = 0, limit: int = 50, search: str | None = None, vendor_id: int | None = None, is_active: bool | None = None, is_featured: bool | None = None, language: str = "en", ) -> tuple[list[dict], int]: """ Get vendor products with search and filtering. Returns: Tuple of (products list as dicts, total count) """ query = ( db.query(Product) .join(Vendor, Product.vendor_id == Vendor.id) .options( joinedload(Product.vendor), joinedload(Product.marketplace_product), ) ) if search: search_term = f"%{search}%" query = query.filter(Product.vendor_sku.ilike(search_term)) if vendor_id: query = query.filter(Product.vendor_id == vendor_id) if is_active is not None: query = query.filter(Product.is_active == is_active) if is_featured is not None: query = query.filter(Product.is_featured == is_featured) total = query.count() products = ( query.order_by(Product.updated_at.desc()).offset(skip).limit(limit).all() ) result = [] for product in products: result.append(self._build_product_list_item(product, language)) return result, total def get_product_stats(self, db: Session) -> dict: """Get vendor product statistics for admin dashboard.""" total = db.query(func.count(Product.id)).scalar() or 0 active = ( db.query(func.count(Product.id)) .filter(Product.is_active == True) # noqa: E712 .scalar() or 0 ) inactive = total - active featured = ( db.query(func.count(Product.id)) .filter(Product.is_featured == True) # noqa: E712 .scalar() or 0 ) # Digital/physical counts digital = ( db.query(func.count(Product.id)) .join(Product.marketplace_product) .filter(Product.marketplace_product.has(is_digital=True)) .scalar() or 0 ) physical = total - digital # Count by vendor vendor_counts = ( db.query( Vendor.name, func.count(Product.id), ) .join(Vendor, Product.vendor_id == Vendor.id) .group_by(Vendor.name) .all() ) by_vendor = {name or "unknown": count for name, count in vendor_counts} return { "total": total, "active": active, "inactive": inactive, "featured": featured, "digital": digital, "physical": physical, "by_vendor": by_vendor, } def get_catalog_vendors(self, db: Session) -> list[dict]: """Get list of vendors with products in their catalogs.""" vendors = ( db.query(Vendor.id, Vendor.name, Vendor.vendor_code) .join(Product, Vendor.id == Product.vendor_id) .distinct() .all() ) return [ {"id": v.id, "name": v.name, "vendor_code": v.vendor_code} for v in vendors ] def get_product_detail(self, db: Session, product_id: int) -> dict: """Get detailed vendor product information including override info.""" product = ( db.query(Product) .options( joinedload(Product.vendor), joinedload(Product.marketplace_product), joinedload(Product.translations), ) .filter(Product.id == product_id) .first() ) if not product: raise ProductNotFoundException(product_id) mp = product.marketplace_product override_info = product.get_override_info() # Get marketplace product translations mp_translations = {} if mp: for t in mp.translations: mp_translations[t.language] = { "title": t.title, "description": t.description, "short_description": t.short_description, } # Get vendor translations (overrides) vendor_translations = {} for t in product.translations: vendor_translations[t.language] = { "title": t.title, "description": t.description, } return { "id": product.id, "vendor_id": product.vendor_id, "vendor_name": product.vendor.name if product.vendor else None, "vendor_code": product.vendor.vendor_code if product.vendor else None, "marketplace_product_id": product.marketplace_product_id, "vendor_sku": product.vendor_sku, # Override info **override_info, # Vendor-specific fields "is_featured": product.is_featured, "is_active": product.is_active, "display_order": product.display_order, "min_quantity": product.min_quantity, "max_quantity": product.max_quantity, # Supplier tracking "supplier": product.supplier, "supplier_product_id": product.supplier_product_id, "supplier_cost": product.supplier_cost, "margin_percent": product.margin_percent, # Digital fulfillment "download_url": product.download_url, "license_type": product.license_type, "fulfillment_email_template": product.fulfillment_email_template, # Source info from marketplace product "source_marketplace": mp.marketplace if mp else None, "source_vendor": mp.vendor_name if mp else None, "source_gtin": mp.gtin if mp else None, "source_sku": mp.sku if mp else None, # Translations "marketplace_translations": mp_translations, "vendor_translations": vendor_translations, # Timestamps "created_at": product.created_at.isoformat() if product.created_at else None, "updated_at": product.updated_at.isoformat() if product.updated_at else None, } def remove_product(self, db: Session, product_id: int) -> dict: """Remove a product from vendor catalog.""" product = db.query(Product).filter(Product.id == product_id).first() if not product: raise ProductNotFoundException(product_id) vendor_name = product.vendor.name if product.vendor else "Unknown" db.delete(product) db.flush() logger.info(f"Removed product {product_id} from vendor {vendor_name} catalog") return {"message": f"Product removed from {vendor_name}'s catalog"} def _build_product_list_item(self, product: Product, language: str) -> dict: """Build a product list item dict.""" mp = product.marketplace_product # Get title from marketplace product translations title = None if mp: title = mp.get_title(language) return { "id": product.id, "vendor_id": product.vendor_id, "vendor_name": product.vendor.name if product.vendor else None, "vendor_code": product.vendor.vendor_code if product.vendor else None, "marketplace_product_id": product.marketplace_product_id, "vendor_sku": product.vendor_sku, "title": title, "brand": product.effective_brand, "effective_price": product.effective_price, "effective_currency": product.effective_currency, "is_active": product.is_active, "is_featured": product.is_featured, "is_digital": product.is_digital, "image_url": product.effective_primary_image_url, "source_marketplace": mp.marketplace if mp else None, "source_vendor": mp.vendor_name if mp else None, "created_at": product.created_at.isoformat() if product.created_at else None, "updated_at": product.updated_at.isoformat() if product.updated_at else None, } # Create service instance vendor_product_service = VendorProductService()