# services/sales/app/schemas/sales.py """ Sales Service Pydantic Schemas """ from pydantic import BaseModel, Field, validator from typing import Optional, List from datetime import datetime from uuid import UUID from decimal import Decimal class SalesDataBase(BaseModel): """Base sales data schema""" # Product reference - REQUIRED reference to inventory service inventory_product_id: UUID = Field(..., description="Reference to inventory service product") quantity_sold: int = Field(..., gt=0, description="Quantity sold") unit_price: Optional[Decimal] = Field(None, ge=0, description="Unit price") revenue: Decimal = Field(..., gt=0, description="Total revenue") cost_of_goods: Optional[Decimal] = Field(None, ge=0, description="Cost of goods sold") discount_applied: Optional[Decimal] = Field(0, ge=0, le=100, description="Discount percentage") location_id: Optional[str] = Field(None, max_length=100, description="Location identifier") sales_channel: Optional[str] = Field("in_store", description="Sales channel") source: str = Field("manual", description="Data source") notes: Optional[str] = Field(None, description="Additional notes") weather_condition: Optional[str] = Field(None, max_length=50, description="Weather condition") is_holiday: bool = Field(False, description="Holiday flag") is_weekend: bool = Field(False, description="Weekend flag") @validator('sales_channel') def validate_sales_channel(cls, v): allowed_channels = ['in_store', 'online', 'delivery', 'wholesale'] if v not in allowed_channels: raise ValueError(f'Sales channel must be one of: {allowed_channels}') return v @validator('source') def validate_source(cls, v): allowed_sources = ['manual', 'pos', 'online', 'import', 'api', 'csv', 'demo_clone'] if v not in allowed_sources: raise ValueError(f'Source must be one of: {allowed_sources}') return v class SalesDataCreate(SalesDataBase): """Schema for creating sales data""" tenant_id: Optional[UUID] = Field(None, description="Tenant ID (set automatically)") date: datetime = Field(..., description="Sale date and time") class SalesDataUpdate(BaseModel): """Schema for updating sales data""" # Note: product_name and product_category fields removed - use inventory service for product management # product_name: Optional[str] = Field(None, min_length=1, max_length=255) # DEPRECATED # product_category: Optional[str] = Field(None, max_length=100) # DEPRECATED # product_sku: Optional[str] = Field(None, max_length=100) # DEPRECATED - use inventory service quantity_sold: Optional[int] = Field(None, gt=0) unit_price: Optional[Decimal] = Field(None, ge=0) revenue: Optional[Decimal] = Field(None, gt=0) cost_of_goods: Optional[Decimal] = Field(None, ge=0) discount_applied: Optional[Decimal] = Field(None, ge=0, le=100) location_id: Optional[str] = Field(None, max_length=100) sales_channel: Optional[str] = None notes: Optional[str] = None weather_condition: Optional[str] = Field(None, max_length=50) is_holiday: Optional[bool] = None is_weekend: Optional[bool] = None validation_notes: Optional[str] = None is_validated: Optional[bool] = None class SalesDataResponse(SalesDataBase): """Schema for sales data responses""" id: UUID tenant_id: UUID date: datetime is_validated: bool = False validation_notes: Optional[str] = None created_at: datetime updated_at: datetime created_by: Optional[UUID] = None profit_margin: Optional[float] = Field(None, description="Calculated profit margin") class Config: from_attributes = True class SalesDataQuery(BaseModel): """Schema for sales data queries""" start_date: Optional[datetime] = None end_date: Optional[datetime] = None # Note: product_name and product_category filtering now requires inventory service integration # product_name: Optional[str] = None # DEPRECATED - use inventory_product_id or join with inventory service # product_category: Optional[str] = None # DEPRECATED - use inventory service categories inventory_product_id: Optional[UUID] = None # Filter by specific inventory product ID location_id: Optional[str] = None sales_channel: Optional[str] = None source: Optional[str] = None is_validated: Optional[bool] = None limit: int = Field(50, ge=1, le=1000, description="Number of records to return") offset: int = Field(0, ge=0, description="Number of records to skip") order_by: str = Field("date", description="Field to order by") order_direction: str = Field("desc", description="Order direction") @validator('order_direction') def validate_order_direction(cls, v): if v.lower() not in ['asc', 'desc']: raise ValueError('Order direction must be "asc" or "desc"') return v.lower() # Product schemas removed - using inventory service as single source of truth # Product data is accessed via inventory service client # Analytics schemas class SalesAnalytics(BaseModel): """Sales analytics response""" total_revenue: Decimal total_quantity: int total_transactions: int average_transaction_value: Decimal top_products: List[dict] sales_by_channel: dict sales_by_day: List[dict] class ProductSalesAnalytics(BaseModel): """Product-specific sales analytics""" inventory_product_id: UUID # Reference to inventory service product # Note: product_name can be fetched from inventory service using inventory_product_id total_revenue: Decimal total_quantity: int total_transactions: int average_price: Decimal growth_rate: Optional[float] = None