# 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_name: str = Field(..., min_length=1, max_length=255, description="Product name") product_category: Optional[str] = Field(None, max_length=100, description="Product category") product_sku: Optional[str] = Field(None, max_length=100, description="Product SKU") 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'] 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""" product_name: Optional[str] = Field(None, min_length=1, max_length=255) product_category: Optional[str] = Field(None, max_length=100) product_sku: Optional[str] = Field(None, max_length=100) 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 product_name: Optional[str] = None product_category: Optional[str] = None 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 class ProductBase(BaseModel): """Base product schema""" name: str = Field(..., min_length=1, max_length=255, description="Product name") sku: Optional[str] = Field(None, max_length=100, description="Stock Keeping Unit") category: Optional[str] = Field(None, max_length=100, description="Product category") subcategory: Optional[str] = Field(None, max_length=100, description="Product subcategory") description: Optional[str] = Field(None, description="Product description") unit_of_measure: str = Field("unit", description="Unit of measure") weight: Optional[float] = Field(None, gt=0, description="Weight in grams") volume: Optional[float] = Field(None, gt=0, description="Volume in ml") base_price: Optional[Decimal] = Field(None, ge=0, description="Base selling price") cost_price: Optional[Decimal] = Field(None, ge=0, description="Cost price") is_seasonal: bool = Field(False, description="Seasonal product flag") seasonal_start: Optional[datetime] = Field(None, description="Season start date") seasonal_end: Optional[datetime] = Field(None, description="Season end date") class ProductCreate(ProductBase): """Schema for creating products""" tenant_id: Optional[UUID] = Field(None, description="Tenant ID (set automatically)") class ProductUpdate(BaseModel): """Schema for updating products""" name: Optional[str] = Field(None, min_length=1, max_length=255) sku: Optional[str] = Field(None, max_length=100) category: Optional[str] = Field(None, max_length=100) subcategory: Optional[str] = Field(None, max_length=100) description: Optional[str] = None unit_of_measure: Optional[str] = None weight: Optional[float] = Field(None, gt=0) volume: Optional[float] = Field(None, gt=0) base_price: Optional[Decimal] = Field(None, ge=0) cost_price: Optional[Decimal] = Field(None, ge=0) is_active: Optional[bool] = None is_seasonal: Optional[bool] = None seasonal_start: Optional[datetime] = None seasonal_end: Optional[datetime] = None class ProductResponse(ProductBase): """Schema for product responses""" id: UUID tenant_id: UUID is_active: bool = True created_at: datetime updated_at: datetime class Config: from_attributes = True # 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""" product_name: str total_revenue: Decimal total_quantity: int total_transactions: int average_price: Decimal growth_rate: Optional[float] = None