293 lines
11 KiB
Python
293 lines
11 KiB
Python
# ================================================================
|
|
# 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)$")
|
|
priority: str = Field(default="normal", pattern="^(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)$")
|
|
|
|
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="^(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 |