# ================================================================ # services/orders/app/schemas/procurement_schemas.py # ================================================================ """ Procurement Schemas - Request/response models for procurement plans """ import uuid from datetime import datetime, date from decimal import Decimal from typing import Optional, List, Dict, Any from pydantic import BaseModel, Field, ConfigDict # ================================================================ # BASE SCHEMAS # ================================================================ class ProcurementBase(BaseModel): """Base schema for procurement entities""" model_config = ConfigDict(from_attributes=True, str_strip_whitespace=True) # ================================================================ # PROCUREMENT REQUIREMENT SCHEMAS # ================================================================ class ProcurementRequirementBase(ProcurementBase): """Base procurement requirement schema""" product_id: uuid.UUID product_name: str = Field(..., min_length=1, max_length=200) product_sku: Optional[str] = Field(None, max_length=100) product_category: Optional[str] = Field(None, max_length=100) product_type: str = Field(default="ingredient", max_length=50) required_quantity: Decimal = Field(..., gt=0) unit_of_measure: str = Field(..., min_length=1, max_length=50) safety_stock_quantity: Decimal = Field(default=Decimal("0.000"), ge=0) total_quantity_needed: Decimal = Field(..., gt=0) current_stock_level: Decimal = Field(default=Decimal("0.000"), ge=0) reserved_stock: Decimal = Field(default=Decimal("0.000"), ge=0) available_stock: Decimal = Field(default=Decimal("0.000"), ge=0) net_requirement: Decimal = Field(..., ge=0) order_demand: Decimal = Field(default=Decimal("0.000"), ge=0) production_demand: Decimal = Field(default=Decimal("0.000"), ge=0) forecast_demand: Decimal = Field(default=Decimal("0.000"), ge=0) buffer_demand: Decimal = Field(default=Decimal("0.000"), ge=0) required_by_date: date lead_time_buffer_days: int = Field(default=1, ge=0) suggested_order_date: date latest_order_date: date priority: str = Field(default="normal", pattern="^(critical|high|normal|low)$") risk_level: str = Field(default="low", pattern="^(low|medium|high|critical)$") preferred_supplier_id: Optional[uuid.UUID] = None backup_supplier_id: Optional[uuid.UUID] = None supplier_name: Optional[str] = Field(None, max_length=200) supplier_lead_time_days: Optional[int] = Field(None, ge=0) minimum_order_quantity: Optional[Decimal] = Field(None, ge=0) estimated_unit_cost: Optional[Decimal] = Field(None, ge=0) estimated_total_cost: Optional[Decimal] = Field(None, ge=0) last_purchase_cost: Optional[Decimal] = Field(None, ge=0) class ProcurementRequirementCreate(ProcurementRequirementBase): """Schema for creating procurement requirements""" special_requirements: Optional[str] = None storage_requirements: Optional[str] = Field(None, max_length=200) shelf_life_days: Optional[int] = Field(None, gt=0) quality_specifications: Optional[Dict[str, Any]] = None procurement_notes: Optional[str] = None class ProcurementRequirementUpdate(ProcurementBase): """Schema for updating procurement requirements""" status: Optional[str] = Field(None, pattern="^(pending|approved|ordered|partially_received|received|cancelled)$") priority: Optional[str] = Field(None, pattern="^(critical|high|normal|low)$") approved_quantity: Optional[Decimal] = Field(None, ge=0) approved_cost: Optional[Decimal] = Field(None, ge=0) purchase_order_id: Optional[uuid.UUID] = None purchase_order_number: Optional[str] = Field(None, max_length=50) ordered_quantity: Optional[Decimal] = Field(None, ge=0) expected_delivery_date: Optional[date] = None actual_delivery_date: Optional[date] = None received_quantity: Optional[Decimal] = Field(None, ge=0) delivery_status: Optional[str] = Field(None, pattern="^(pending|in_transit|delivered|delayed|cancelled)$") procurement_notes: Optional[str] = None class ProcurementRequirementResponse(ProcurementRequirementBase): """Schema for procurement requirement responses""" id: uuid.UUID plan_id: uuid.UUID requirement_number: str status: str created_at: datetime updated_at: datetime purchase_order_id: Optional[uuid.UUID] = None purchase_order_number: Optional[str] = None ordered_quantity: Decimal ordered_at: Optional[datetime] = None expected_delivery_date: Optional[date] = None actual_delivery_date: Optional[date] = None received_quantity: Decimal delivery_status: str fulfillment_rate: Optional[Decimal] = None on_time_delivery: Optional[bool] = None quality_rating: Optional[Decimal] = None approved_quantity: Optional[Decimal] = None approved_cost: Optional[Decimal] = None approved_at: Optional[datetime] = None approved_by: Optional[uuid.UUID] = None special_requirements: Optional[str] = None storage_requirements: Optional[str] = None shelf_life_days: Optional[int] = None quality_specifications: Optional[Dict[str, Any]] = None procurement_notes: Optional[str] = None # ================================================================ # PROCUREMENT PLAN SCHEMAS # ================================================================ class ProcurementPlanBase(ProcurementBase): """Base procurement plan schema""" plan_date: date plan_period_start: date plan_period_end: date planning_horizon_days: int = Field(default=14, gt=0) plan_type: str = Field(default="regular", pattern="^(regular|emergency|seasonal|urgent)$") priority: str = Field(default="normal", pattern="^(critical|high|normal|low)$") business_model: Optional[str] = Field(None, pattern="^(individual_bakery|central_bakery)$") procurement_strategy: str = Field(default="just_in_time", pattern="^(just_in_time|bulk|mixed|bulk_order)$") safety_stock_buffer: Decimal = Field(default=Decimal("20.00"), ge=0, le=100) supply_risk_level: str = Field(default="low", pattern="^(low|medium|high|critical)$") demand_forecast_confidence: Optional[Decimal] = Field(None, ge=1, le=10) seasonality_adjustment: Decimal = Field(default=Decimal("0.00")) special_requirements: Optional[str] = None class ProcurementPlanCreate(ProcurementPlanBase): """Schema for creating procurement plans""" tenant_id: uuid.UUID requirements: Optional[List[ProcurementRequirementCreate]] = [] class ProcurementPlanUpdate(ProcurementBase): """Schema for updating procurement plans""" status: Optional[str] = Field(None, pattern="^(draft|pending_approval|approved|in_execution|completed|cancelled)$") priority: Optional[str] = Field(None, pattern="^(critical|high|normal|low)$") approved_at: Optional[datetime] = None approved_by: Optional[uuid.UUID] = None execution_started_at: Optional[datetime] = None execution_completed_at: Optional[datetime] = None special_requirements: Optional[str] = None seasonal_adjustments: Optional[Dict[str, Any]] = None class ProcurementPlanResponse(ProcurementPlanBase): """Schema for procurement plan responses""" id: uuid.UUID tenant_id: uuid.UUID plan_number: str status: str total_requirements: int total_estimated_cost: Decimal total_approved_cost: Decimal cost_variance: Decimal total_demand_orders: int total_demand_quantity: Decimal total_production_requirements: Decimal primary_suppliers_count: int backup_suppliers_count: int supplier_diversification_score: Optional[Decimal] = None approved_at: Optional[datetime] = None approved_by: Optional[uuid.UUID] = None execution_started_at: Optional[datetime] = None execution_completed_at: Optional[datetime] = None fulfillment_rate: Optional[Decimal] = None on_time_delivery_rate: Optional[Decimal] = None cost_accuracy: Optional[Decimal] = None quality_score: Optional[Decimal] = None created_at: datetime updated_at: datetime created_by: Optional[uuid.UUID] = None updated_by: Optional[uuid.UUID] = None requirements: List[ProcurementRequirementResponse] = [] # ================================================================ # SUMMARY SCHEMAS # ================================================================ class ProcurementSummary(ProcurementBase): """Summary of procurement plans""" total_plans: int active_plans: int total_requirements: int pending_requirements: int critical_requirements: int total_estimated_cost: Decimal total_approved_cost: Decimal cost_variance: Decimal average_fulfillment_rate: Optional[Decimal] = None average_on_time_delivery: Optional[Decimal] = None top_suppliers: List[Dict[str, Any]] = [] critical_items: List[Dict[str, Any]] = [] class DashboardData(ProcurementBase): """Dashboard data for procurement overview""" current_plan: Optional[ProcurementPlanResponse] = None summary: ProcurementSummary upcoming_deliveries: List[Dict[str, Any]] = [] overdue_requirements: List[Dict[str, Any]] = [] low_stock_alerts: List[Dict[str, Any]] = [] performance_metrics: Dict[str, Any] = {} # ================================================================ # REQUEST SCHEMAS # ================================================================ class GeneratePlanRequest(ProcurementBase): """Request to generate procurement plan""" plan_date: Optional[date] = None force_regenerate: bool = False planning_horizon_days: int = Field(default=14, gt=0, le=30) include_safety_stock: bool = True safety_stock_percentage: Decimal = Field(default=Decimal("20.00"), ge=0, le=100) class ForecastRequest(ProcurementBase): """Request parameters for demand forecasting""" target_date: date horizon_days: int = Field(default=1, gt=0, le=7) include_confidence_intervals: bool = True product_ids: Optional[List[uuid.UUID]] = None # ================================================================ # RESPONSE SCHEMAS # ================================================================ class GeneratePlanResponse(ProcurementBase): """Response from plan generation""" success: bool message: str plan: Optional[ProcurementPlanResponse] = None warnings: List[str] = [] errors: List[str] = [] class PaginatedProcurementPlans(ProcurementBase): """Paginated list of procurement plans""" plans: List[ProcurementPlanResponse] total: int page: int limit: int has_more: bool