Initial commit - production deployment
This commit is contained in:
79
services/procurement/app/schemas/__init__.py
Normal file
79
services/procurement/app/schemas/__init__.py
Normal file
@@ -0,0 +1,79 @@
|
||||
# ================================================================
|
||||
# services/procurement/app/schemas/__init__.py
|
||||
# ================================================================
|
||||
"""
|
||||
Pydantic schemas for Procurement Service
|
||||
"""
|
||||
|
||||
from .procurement_schemas import (
|
||||
ProcurementRequirementBase,
|
||||
ProcurementRequirementCreate,
|
||||
ProcurementRequirementUpdate,
|
||||
ProcurementRequirementResponse,
|
||||
ProcurementPlanBase,
|
||||
ProcurementPlanCreate,
|
||||
ProcurementPlanUpdate,
|
||||
ProcurementPlanResponse,
|
||||
ProcurementSummary,
|
||||
DashboardData,
|
||||
GeneratePlanRequest,
|
||||
GeneratePlanResponse,
|
||||
AutoGenerateProcurementRequest,
|
||||
AutoGenerateProcurementResponse,
|
||||
PaginatedProcurementPlans,
|
||||
)
|
||||
|
||||
from .purchase_order_schemas import (
|
||||
PurchaseOrderCreate,
|
||||
PurchaseOrderUpdate,
|
||||
PurchaseOrderApproval,
|
||||
PurchaseOrderResponse,
|
||||
PurchaseOrderSummary,
|
||||
PurchaseOrderItemCreate,
|
||||
PurchaseOrderItemResponse,
|
||||
DeliveryCreate,
|
||||
DeliveryUpdate,
|
||||
DeliveryResponse,
|
||||
DeliveryItemCreate,
|
||||
DeliveryItemResponse,
|
||||
SupplierInvoiceCreate,
|
||||
SupplierInvoiceUpdate,
|
||||
SupplierInvoiceResponse,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
# Procurement Plan schemas
|
||||
"ProcurementRequirementBase",
|
||||
"ProcurementRequirementCreate",
|
||||
"ProcurementRequirementUpdate",
|
||||
"ProcurementRequirementResponse",
|
||||
"ProcurementPlanBase",
|
||||
"ProcurementPlanCreate",
|
||||
"ProcurementPlanUpdate",
|
||||
"ProcurementPlanResponse",
|
||||
"ProcurementSummary",
|
||||
"DashboardData",
|
||||
"GeneratePlanRequest",
|
||||
"GeneratePlanResponse",
|
||||
"AutoGenerateProcurementRequest",
|
||||
"AutoGenerateProcurementResponse",
|
||||
"PaginatedProcurementPlans",
|
||||
# Purchase Order schemas
|
||||
"PurchaseOrderCreate",
|
||||
"PurchaseOrderUpdate",
|
||||
"PurchaseOrderApproval",
|
||||
"PurchaseOrderResponse",
|
||||
"PurchaseOrderSummary",
|
||||
"PurchaseOrderItemCreate",
|
||||
"PurchaseOrderItemResponse",
|
||||
# Delivery schemas
|
||||
"DeliveryCreate",
|
||||
"DeliveryUpdate",
|
||||
"DeliveryResponse",
|
||||
"DeliveryItemCreate",
|
||||
"DeliveryItemResponse",
|
||||
# Invoice schemas
|
||||
"SupplierInvoiceCreate",
|
||||
"SupplierInvoiceUpdate",
|
||||
"SupplierInvoiceResponse",
|
||||
]
|
||||
368
services/procurement/app/schemas/procurement_schemas.py
Normal file
368
services/procurement/app/schemas/procurement_schemas.py
Normal file
@@ -0,0 +1,368 @@
|
||||
# ================================================================
|
||||
# services/procurement/app/schemas/procurement_schemas.py
|
||||
# ================================================================
|
||||
"""
|
||||
Procurement Schemas - Request/response models for procurement plans
|
||||
Migrated from Orders Service with additions for local production support
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
# Smart procurement calculation metadata
|
||||
calculation_method: Optional[str] = Field(None, max_length=100)
|
||||
ai_suggested_quantity: Optional[Decimal] = Field(None, ge=0)
|
||||
adjusted_quantity: Optional[Decimal] = Field(None, ge=0)
|
||||
adjustment_reason: Optional[str] = None
|
||||
price_tier_applied: Optional[Dict[str, Any]] = None
|
||||
supplier_minimum_applied: bool = False
|
||||
storage_limit_applied: bool = False
|
||||
reorder_rule_applied: bool = False
|
||||
|
||||
# NEW: Local production support fields
|
||||
is_locally_produced: bool = False
|
||||
recipe_id: Optional[uuid.UUID] = None
|
||||
parent_requirement_id: Optional[uuid.UUID] = None
|
||||
bom_explosion_level: int = Field(default=0, ge=0)
|
||||
|
||||
|
||||
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
|
||||
|
||||
# Smart procurement calculation metadata
|
||||
calculation_method: Optional[str] = None
|
||||
ai_suggested_quantity: Optional[Decimal] = None
|
||||
adjusted_quantity: Optional[Decimal] = None
|
||||
adjustment_reason: Optional[str] = None
|
||||
price_tier_applied: Optional[Dict[str, Any]] = None
|
||||
supplier_minimum_applied: bool = False
|
||||
storage_limit_applied: bool = False
|
||||
reorder_rule_applied: bool = False
|
||||
|
||||
# NEW: Local production support fields
|
||||
is_locally_produced: bool = False
|
||||
recipe_id: Optional[uuid.UUID] = None
|
||||
parent_requirement_id: Optional[uuid.UUID] = None
|
||||
bom_explosion_level: int = 0
|
||||
|
||||
|
||||
# ================================================================
|
||||
# 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
|
||||
|
||||
# NEW: Track forecast and production schedule links
|
||||
forecast_id: Optional[uuid.UUID] = None
|
||||
production_schedule_id: 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 AutoGenerateProcurementRequest(ProcurementBase):
|
||||
"""
|
||||
Request to auto-generate procurement plan (called by Orchestrator)
|
||||
|
||||
This is the main entry point for orchestrated procurement planning.
|
||||
The Orchestrator calls Forecasting Service first, then passes forecast data here.
|
||||
|
||||
NEW: Accepts cached data snapshots from Orchestrator to eliminate duplicate API calls.
|
||||
"""
|
||||
forecast_data: Dict[str, Any] = Field(..., description="Forecast data from Forecasting Service")
|
||||
production_schedule_id: Optional[uuid.UUID] = Field(None, description="Production schedule ID if available")
|
||||
target_date: Optional[date] = Field(None, description="Target date for the plan")
|
||||
planning_horizon_days: int = Field(default=14, gt=0, le=30)
|
||||
safety_stock_percentage: Decimal = Field(default=Decimal("20.00"), ge=0, le=100)
|
||||
auto_create_pos: bool = Field(True, description="Automatically create purchase orders")
|
||||
auto_approve_pos: bool = Field(False, description="Auto-approve qualifying purchase orders")
|
||||
|
||||
# NEW: Cached data from Orchestrator
|
||||
inventory_data: Optional[Dict[str, Any]] = Field(None, description="Cached inventory snapshot from Orchestrator")
|
||||
suppliers_data: Optional[Dict[str, Any]] = Field(None, description="Cached suppliers snapshot from Orchestrator")
|
||||
recipes_data: Optional[Dict[str, Any]] = Field(None, description="Cached recipes snapshot from Orchestrator")
|
||||
|
||||
|
||||
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 AutoGenerateProcurementResponse(ProcurementBase):
|
||||
"""Response from auto-generate procurement (called by Orchestrator)"""
|
||||
success: bool
|
||||
message: str
|
||||
plan_id: Optional[uuid.UUID] = None
|
||||
plan_number: Optional[str] = None
|
||||
requirements_created: int = 0
|
||||
purchase_orders_created: int = 0
|
||||
purchase_orders_auto_approved: int = 0
|
||||
total_estimated_cost: Decimal = Decimal("0")
|
||||
warnings: List[str] = []
|
||||
errors: List[str] = []
|
||||
created_pos: List[Dict[str, Any]] = []
|
||||
|
||||
|
||||
class PaginatedProcurementPlans(ProcurementBase):
|
||||
"""Paginated list of procurement plans"""
|
||||
plans: List[ProcurementPlanResponse]
|
||||
total: int
|
||||
page: int
|
||||
limit: int
|
||||
has_more: bool
|
||||
395
services/procurement/app/schemas/purchase_order_schemas.py
Normal file
395
services/procurement/app/schemas/purchase_order_schemas.py
Normal file
@@ -0,0 +1,395 @@
|
||||
# ================================================================
|
||||
# services/procurement/app/schemas/purchase_order_schemas.py
|
||||
# ================================================================
|
||||
"""
|
||||
Purchase Order Schemas - Request/response models for purchase orders
|
||||
Migrated from Suppliers Service with procurement-specific additions
|
||||
"""
|
||||
|
||||
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 PurchaseOrderBase(BaseModel):
|
||||
"""Base schema for purchase order entities"""
|
||||
model_config = ConfigDict(from_attributes=True, str_strip_whitespace=True)
|
||||
|
||||
|
||||
# ================================================================
|
||||
# PURCHASE ORDER ITEM SCHEMAS
|
||||
# ================================================================
|
||||
|
||||
class PurchaseOrderItemCreate(PurchaseOrderBase):
|
||||
"""Schema for creating purchase order items"""
|
||||
inventory_product_id: uuid.UUID # Changed from ingredient_id to match model
|
||||
ordered_quantity: Decimal = Field(..., gt=0)
|
||||
unit_price: Decimal = Field(..., gt=0)
|
||||
unit_of_measure: str = Field(..., max_length=50)
|
||||
quality_requirements: Optional[str] = None
|
||||
item_notes: Optional[str] = None
|
||||
|
||||
|
||||
class PurchaseOrderItemUpdate(PurchaseOrderBase):
|
||||
"""Schema for updating purchase order items"""
|
||||
ordered_quantity: Optional[Decimal] = Field(None, gt=0)
|
||||
unit_price: Optional[Decimal] = Field(None, gt=0)
|
||||
quality_requirements: Optional[str] = None
|
||||
item_notes: Optional[str] = None
|
||||
|
||||
|
||||
class PurchaseOrderItemResponse(PurchaseOrderBase):
|
||||
"""Schema for purchase order item responses"""
|
||||
id: uuid.UUID
|
||||
tenant_id: uuid.UUID
|
||||
purchase_order_id: uuid.UUID
|
||||
inventory_product_id: uuid.UUID # Changed from ingredient_id to match model
|
||||
product_name: Optional[str] = None
|
||||
ordered_quantity: Decimal
|
||||
received_quantity: Decimal
|
||||
unit_price: Decimal
|
||||
unit_of_measure: str
|
||||
line_total: Decimal
|
||||
quality_requirements: Optional[str] = None
|
||||
item_notes: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
# ================================================================
|
||||
# PURCHASE ORDER SCHEMAS
|
||||
# ================================================================
|
||||
|
||||
class PurchaseOrderCreate(PurchaseOrderBase):
|
||||
"""Schema for creating purchase orders"""
|
||||
supplier_id: uuid.UUID
|
||||
required_delivery_date: datetime # Use datetime with timezone
|
||||
priority: str = Field(default="normal", pattern="^(low|normal|high|critical)$")
|
||||
|
||||
# Financial information
|
||||
tax_amount: Decimal = Field(default=Decimal("0"), ge=0)
|
||||
shipping_cost: Decimal = Field(default=Decimal("0"), ge=0)
|
||||
discount_amount: Decimal = Field(default=Decimal("0"), ge=0)
|
||||
subtotal: Decimal = Field(..., ge=0)
|
||||
|
||||
# Additional information
|
||||
notes: Optional[str] = None
|
||||
|
||||
# NEW: Procurement-specific fields
|
||||
procurement_plan_id: Optional[uuid.UUID] = None
|
||||
|
||||
# Items
|
||||
items: List[PurchaseOrderItemCreate] = Field(..., min_length=1)
|
||||
|
||||
|
||||
class PurchaseOrderUpdate(PurchaseOrderBase):
|
||||
"""Schema for updating purchase orders"""
|
||||
required_delivery_date: Optional[datetime] = None # Use datetime with timezone
|
||||
priority: Optional[str] = Field(None, pattern="^(low|normal|high|critical)$")
|
||||
|
||||
# Financial information
|
||||
tax_amount: Optional[Decimal] = Field(None, ge=0)
|
||||
shipping_cost: Optional[Decimal] = Field(None, ge=0)
|
||||
discount_amount: Optional[Decimal] = Field(None, ge=0)
|
||||
|
||||
# Additional information
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class PurchaseOrderApproval(PurchaseOrderBase):
|
||||
"""Schema for purchase order approval/rejection"""
|
||||
action: str = Field(..., pattern="^(approve|reject)$")
|
||||
notes: Optional[str] = None
|
||||
approved_by: Optional[uuid.UUID] = None
|
||||
|
||||
|
||||
class SupplierSummary(PurchaseOrderBase):
|
||||
"""Schema for supplier summary - matches the structure returned by suppliers service"""
|
||||
id: str
|
||||
name: str
|
||||
supplier_code: Optional[str] = None
|
||||
email: Optional[str] = None
|
||||
phone: Optional[str] = None
|
||||
contact_person: Optional[str] = None
|
||||
address_line1: Optional[str] = None
|
||||
city: Optional[str] = None
|
||||
country: Optional[str] = None
|
||||
supplier_type: Optional[str] = None
|
||||
status: Optional[str] = None
|
||||
mobile: Optional[str] = None
|
||||
website: Optional[str] = None
|
||||
payment_terms: Optional[str] = None
|
||||
standard_lead_time: Optional[int] = None
|
||||
quality_rating: Optional[float] = None
|
||||
delivery_rating: Optional[float] = None
|
||||
total_orders: Optional[int] = None
|
||||
total_amount: Optional[float] = None
|
||||
|
||||
|
||||
class PurchaseOrderResponse(PurchaseOrderBase):
|
||||
"""Schema for purchase order responses"""
|
||||
id: uuid.UUID
|
||||
tenant_id: uuid.UUID
|
||||
supplier_id: uuid.UUID
|
||||
supplier_name: Optional[str] = None
|
||||
po_number: str
|
||||
status: str
|
||||
priority: str
|
||||
|
||||
order_date: datetime
|
||||
required_delivery_date: Optional[datetime] = None # Use datetime with timezone
|
||||
estimated_delivery_date: Optional[datetime] = None # Use datetime with timezone
|
||||
actual_delivery_date: Optional[datetime] = None # Use datetime with timezone
|
||||
|
||||
# Financial information
|
||||
subtotal: Decimal
|
||||
tax_amount: Decimal
|
||||
shipping_cost: Decimal
|
||||
discount_amount: Decimal
|
||||
total_amount: Decimal
|
||||
currency: str
|
||||
|
||||
# Approval workflow
|
||||
approved_by: Optional[uuid.UUID] = None
|
||||
approved_at: Optional[datetime] = None
|
||||
rejection_reason: Optional[str] = None
|
||||
|
||||
# NEW: Procurement-specific fields
|
||||
procurement_plan_id: Optional[uuid.UUID] = None
|
||||
auto_approved: bool = False
|
||||
auto_approval_rule_id: Optional[uuid.UUID] = None
|
||||
|
||||
# Additional information
|
||||
notes: Optional[str] = None
|
||||
|
||||
# AI/ML reasoning for procurement decisions (JTBD dashboard support)
|
||||
reasoning_data: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Audit fields
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
created_by: Optional[uuid.UUID] = None
|
||||
updated_by: Optional[uuid.UUID] = None
|
||||
|
||||
# Related data
|
||||
items: List[PurchaseOrderItemResponse] = []
|
||||
|
||||
|
||||
class PurchaseOrderWithSupplierResponse(PurchaseOrderResponse):
|
||||
"""Schema for purchase order responses with supplier information"""
|
||||
supplier: Optional[SupplierSummary] = None
|
||||
|
||||
|
||||
class PurchaseOrderSummary(PurchaseOrderBase):
|
||||
"""Schema for purchase order summary (list view)"""
|
||||
id: uuid.UUID
|
||||
po_number: str
|
||||
supplier_id: uuid.UUID
|
||||
supplier_name: Optional[str] = None
|
||||
status: str
|
||||
priority: str
|
||||
order_date: datetime
|
||||
required_delivery_date: datetime # Use datetime with timezone
|
||||
total_amount: Decimal
|
||||
currency: str
|
||||
auto_approved: bool = False
|
||||
created_at: datetime
|
||||
|
||||
|
||||
# ================================================================
|
||||
# DELIVERY SCHEMAS
|
||||
# ================================================================
|
||||
|
||||
class DeliveryItemCreate(PurchaseOrderBase):
|
||||
"""Schema for creating delivery items"""
|
||||
purchase_order_item_id: uuid.UUID
|
||||
inventory_product_id: uuid.UUID # Changed from ingredient_id to match model
|
||||
ordered_quantity: Decimal = Field(..., gt=0)
|
||||
delivered_quantity: Decimal = Field(..., ge=0)
|
||||
accepted_quantity: Decimal = Field(..., ge=0)
|
||||
rejected_quantity: Decimal = Field(default=Decimal("0"), ge=0)
|
||||
|
||||
# Quality information
|
||||
batch_lot_number: Optional[str] = Field(None, max_length=100)
|
||||
expiry_date: Optional[datetime] = None # Use datetime with timezone
|
||||
quality_grade: Optional[str] = Field(None, max_length=20)
|
||||
|
||||
# Issues and notes
|
||||
quality_issues: Optional[str] = None
|
||||
rejection_reason: Optional[str] = None
|
||||
item_notes: Optional[str] = None
|
||||
|
||||
|
||||
class DeliveryItemResponse(PurchaseOrderBase):
|
||||
"""Schema for delivery item responses"""
|
||||
id: uuid.UUID
|
||||
tenant_id: uuid.UUID
|
||||
delivery_id: uuid.UUID
|
||||
purchase_order_item_id: uuid.UUID
|
||||
inventory_product_id: uuid.UUID # Changed from ingredient_id to match model
|
||||
ingredient_name: Optional[str] = None
|
||||
ordered_quantity: Decimal
|
||||
delivered_quantity: Decimal
|
||||
accepted_quantity: Decimal
|
||||
rejected_quantity: Decimal
|
||||
batch_lot_number: Optional[str] = None
|
||||
expiry_date: Optional[datetime] = None # Use datetime with timezone
|
||||
quality_grade: Optional[str] = None
|
||||
quality_issues: Optional[str] = None
|
||||
rejection_reason: Optional[str] = None
|
||||
item_notes: Optional[str] = None
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
|
||||
|
||||
class DeliveryCreate(PurchaseOrderBase):
|
||||
"""Schema for creating deliveries"""
|
||||
purchase_order_id: uuid.UUID
|
||||
supplier_id: uuid.UUID
|
||||
supplier_delivery_note: Optional[str] = Field(None, max_length=100)
|
||||
scheduled_date: Optional[datetime] = None # Use datetime with timezone
|
||||
estimated_arrival: Optional[datetime] = None
|
||||
|
||||
# Delivery details
|
||||
carrier_name: Optional[str] = Field(None, max_length=200)
|
||||
tracking_number: Optional[str] = Field(None, max_length=100)
|
||||
|
||||
# Additional information
|
||||
notes: Optional[str] = None
|
||||
|
||||
# Items
|
||||
items: List[DeliveryItemCreate] = Field(..., min_length=1)
|
||||
|
||||
|
||||
class DeliveryUpdate(PurchaseOrderBase):
|
||||
"""Schema for updating deliveries"""
|
||||
supplier_delivery_note: Optional[str] = Field(None, max_length=100)
|
||||
scheduled_date: Optional[datetime] = None # Use datetime with timezone
|
||||
estimated_arrival: Optional[datetime] = None
|
||||
actual_arrival: Optional[datetime] = None
|
||||
|
||||
# Delivery details
|
||||
carrier_name: Optional[str] = Field(None, max_length=200)
|
||||
tracking_number: Optional[str] = Field(None, max_length=100)
|
||||
|
||||
# Quality inspection
|
||||
inspection_passed: Optional[bool] = None
|
||||
inspection_notes: Optional[str] = None
|
||||
quality_issues: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Additional information
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class DeliveryResponse(PurchaseOrderBase):
|
||||
"""Schema for delivery responses"""
|
||||
id: uuid.UUID
|
||||
tenant_id: uuid.UUID
|
||||
purchase_order_id: uuid.UUID
|
||||
supplier_id: uuid.UUID
|
||||
supplier_name: Optional[str] = None
|
||||
delivery_number: str
|
||||
supplier_delivery_note: Optional[str] = None
|
||||
status: str
|
||||
|
||||
# Timing
|
||||
scheduled_date: Optional[datetime] = None # Use datetime with timezone
|
||||
estimated_arrival: Optional[datetime] = None
|
||||
actual_arrival: Optional[datetime] = None
|
||||
completed_at: Optional[datetime] = None
|
||||
|
||||
# Delivery details
|
||||
carrier_name: Optional[str] = None
|
||||
tracking_number: Optional[str] = None
|
||||
|
||||
# Quality inspection
|
||||
inspection_passed: Optional[bool] = None
|
||||
inspection_notes: Optional[str] = None
|
||||
quality_issues: Optional[Dict[str, Any]] = None
|
||||
|
||||
# Receipt information
|
||||
received_by: Optional[uuid.UUID] = None
|
||||
received_at: Optional[datetime] = None
|
||||
|
||||
# Additional information
|
||||
notes: Optional[str] = None
|
||||
|
||||
# Audit fields
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
created_by: uuid.UUID
|
||||
|
||||
# Related data
|
||||
items: List[DeliveryItemResponse] = []
|
||||
|
||||
|
||||
# ================================================================
|
||||
# INVOICE SCHEMAS
|
||||
# ================================================================
|
||||
|
||||
class SupplierInvoiceCreate(PurchaseOrderBase):
|
||||
"""Schema for creating supplier invoices"""
|
||||
purchase_order_id: uuid.UUID
|
||||
supplier_id: uuid.UUID
|
||||
invoice_number: str = Field(..., max_length=100)
|
||||
invoice_date: datetime # Use datetime with timezone
|
||||
due_date: datetime # Use datetime with timezone
|
||||
|
||||
# Financial information
|
||||
subtotal: Decimal = Field(..., ge=0)
|
||||
tax_amount: Decimal = Field(default=Decimal("0"), ge=0)
|
||||
shipping_cost: Decimal = Field(default=Decimal("0"), ge=0)
|
||||
discount_amount: Decimal = Field(default=Decimal("0"), ge=0)
|
||||
|
||||
# Additional information
|
||||
notes: Optional[str] = None
|
||||
payment_reference: Optional[str] = Field(None, max_length=100)
|
||||
|
||||
|
||||
class SupplierInvoiceUpdate(PurchaseOrderBase):
|
||||
"""Schema for updating supplier invoices"""
|
||||
due_date: Optional[datetime] = None # Use datetime with timezone
|
||||
payment_reference: Optional[str] = Field(None, max_length=100)
|
||||
notes: Optional[str] = None
|
||||
|
||||
|
||||
class SupplierInvoiceResponse(PurchaseOrderBase):
|
||||
"""Schema for supplier invoice responses"""
|
||||
id: uuid.UUID
|
||||
tenant_id: uuid.UUID
|
||||
purchase_order_id: uuid.UUID
|
||||
supplier_id: uuid.UUID
|
||||
supplier_name: Optional[str] = None
|
||||
invoice_number: str
|
||||
status: str
|
||||
invoice_date: datetime # Use datetime with timezone
|
||||
due_date: datetime # Use datetime with timezone
|
||||
|
||||
# Financial information
|
||||
subtotal: Decimal
|
||||
tax_amount: Decimal
|
||||
shipping_cost: Decimal
|
||||
discount_amount: Decimal
|
||||
total_amount: Decimal
|
||||
currency: str
|
||||
|
||||
# Payment tracking
|
||||
paid_amount: Decimal
|
||||
remaining_amount: Decimal
|
||||
payment_date: Optional[datetime] = None # Use datetime with timezone
|
||||
payment_reference: Optional[str] = None
|
||||
|
||||
# Additional information
|
||||
notes: Optional[str] = None
|
||||
|
||||
# Audit fields
|
||||
created_at: datetime
|
||||
updated_at: datetime
|
||||
created_by: uuid.UUID
|
||||
updated_by: uuid.UUID
|
||||
440
services/procurement/app/schemas/replenishment.py
Normal file
440
services/procurement/app/schemas/replenishment.py
Normal file
@@ -0,0 +1,440 @@
|
||||
"""
|
||||
Pydantic schemas for replenishment planning.
|
||||
"""
|
||||
|
||||
from pydantic import BaseModel, Field, validator
|
||||
from typing import List, Optional, Dict, Any
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from uuid import UUID
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Replenishment Plan Schemas
|
||||
# ============================================================================
|
||||
|
||||
class ReplenishmentPlanItemBase(BaseModel):
|
||||
"""Base schema for replenishment plan item"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
unit_of_measure: str
|
||||
|
||||
base_quantity: Decimal
|
||||
safety_stock_quantity: Decimal
|
||||
shelf_life_adjusted_quantity: Decimal
|
||||
final_order_quantity: Decimal
|
||||
|
||||
order_date: date
|
||||
delivery_date: date
|
||||
required_by_date: date
|
||||
|
||||
lead_time_days: int
|
||||
is_urgent: bool
|
||||
urgency_reason: Optional[str] = None
|
||||
waste_risk: str
|
||||
stockout_risk: str
|
||||
|
||||
supplier_id: Optional[UUID] = None
|
||||
|
||||
safety_stock_calculation: Optional[Dict[str, Any]] = None
|
||||
shelf_life_adjustment: Optional[Dict[str, Any]] = None
|
||||
inventory_projection: Optional[Dict[str, Any]] = None
|
||||
|
||||
|
||||
class ReplenishmentPlanItemCreate(ReplenishmentPlanItemBase):
|
||||
"""Schema for creating replenishment plan item"""
|
||||
replenishment_plan_id: UUID
|
||||
|
||||
|
||||
class ReplenishmentPlanItemResponse(ReplenishmentPlanItemBase):
|
||||
"""Schema for replenishment plan item response"""
|
||||
id: UUID
|
||||
replenishment_plan_id: UUID
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ReplenishmentPlanBase(BaseModel):
|
||||
"""Base schema for replenishment plan"""
|
||||
planning_date: date
|
||||
projection_horizon_days: int = 7
|
||||
|
||||
forecast_id: Optional[UUID] = None
|
||||
production_schedule_id: Optional[UUID] = None
|
||||
|
||||
total_items: int
|
||||
urgent_items: int
|
||||
high_risk_items: int
|
||||
total_estimated_cost: Decimal
|
||||
|
||||
|
||||
class ReplenishmentPlanCreate(ReplenishmentPlanBase):
|
||||
"""Schema for creating replenishment plan"""
|
||||
tenant_id: UUID
|
||||
items: List[Dict[str, Any]] = []
|
||||
|
||||
|
||||
class ReplenishmentPlanResponse(ReplenishmentPlanBase):
|
||||
"""Schema for replenishment plan response"""
|
||||
id: UUID
|
||||
tenant_id: UUID
|
||||
status: str
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
executed_at: Optional[datetime] = None
|
||||
|
||||
items: List[ReplenishmentPlanItemResponse] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class ReplenishmentPlanSummary(BaseModel):
|
||||
"""Summary schema for list views"""
|
||||
id: UUID
|
||||
tenant_id: UUID
|
||||
planning_date: date
|
||||
total_items: int
|
||||
urgent_items: int
|
||||
high_risk_items: int
|
||||
total_estimated_cost: Decimal
|
||||
status: str
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Inventory Projection Schemas
|
||||
# ============================================================================
|
||||
|
||||
class InventoryProjectionBase(BaseModel):
|
||||
"""Base schema for inventory projection"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
projection_date: date
|
||||
|
||||
starting_stock: Decimal
|
||||
forecasted_consumption: Decimal
|
||||
scheduled_receipts: Decimal
|
||||
projected_ending_stock: Decimal
|
||||
|
||||
is_stockout: bool
|
||||
coverage_gap: Decimal
|
||||
|
||||
|
||||
class InventoryProjectionCreate(InventoryProjectionBase):
|
||||
"""Schema for creating inventory projection"""
|
||||
tenant_id: UUID
|
||||
replenishment_plan_id: Optional[UUID] = None
|
||||
|
||||
|
||||
class InventoryProjectionResponse(InventoryProjectionBase):
|
||||
"""Schema for inventory projection response"""
|
||||
id: UUID
|
||||
tenant_id: UUID
|
||||
replenishment_plan_id: Optional[UUID] = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class IngredientProjectionSummary(BaseModel):
|
||||
"""Summary of projections for one ingredient"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
current_stock: Decimal
|
||||
unit_of_measure: str
|
||||
projection_horizon_days: int
|
||||
total_consumption: Decimal
|
||||
total_receipts: Decimal
|
||||
stockout_days: int
|
||||
stockout_risk: str
|
||||
daily_projections: List[Dict[str, Any]]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Supplier Allocation Schemas
|
||||
# ============================================================================
|
||||
|
||||
class SupplierAllocationBase(BaseModel):
|
||||
"""Base schema for supplier allocation"""
|
||||
supplier_id: UUID
|
||||
supplier_name: str
|
||||
|
||||
allocation_type: str
|
||||
allocated_quantity: Decimal
|
||||
allocation_percentage: Decimal
|
||||
|
||||
unit_price: Decimal
|
||||
total_cost: Decimal
|
||||
lead_time_days: int
|
||||
|
||||
supplier_score: Decimal
|
||||
score_breakdown: Optional[Dict[str, float]] = None
|
||||
allocation_reason: Optional[str] = None
|
||||
|
||||
|
||||
class SupplierAllocationCreate(SupplierAllocationBase):
|
||||
"""Schema for creating supplier allocation"""
|
||||
replenishment_plan_item_id: Optional[UUID] = None
|
||||
requirement_id: Optional[UUID] = None
|
||||
|
||||
|
||||
class SupplierAllocationResponse(SupplierAllocationBase):
|
||||
"""Schema for supplier allocation response"""
|
||||
id: UUID
|
||||
replenishment_plan_item_id: Optional[UUID] = None
|
||||
requirement_id: Optional[UUID] = None
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Supplier Selection Schemas
|
||||
# ============================================================================
|
||||
|
||||
class SupplierSelectionRequest(BaseModel):
|
||||
"""Request to select suppliers for an ingredient"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
required_quantity: Decimal
|
||||
supplier_options: List[Dict[str, Any]]
|
||||
|
||||
|
||||
class SupplierSelectionResult(BaseModel):
|
||||
"""Result of supplier selection"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
required_quantity: Decimal
|
||||
allocations: List[Dict[str, Any]]
|
||||
total_cost: Decimal
|
||||
weighted_lead_time: float
|
||||
risk_score: float
|
||||
diversification_applied: bool
|
||||
selection_strategy: str
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Replenishment Planning Request Schemas
|
||||
# ============================================================================
|
||||
|
||||
class IngredientRequirementInput(BaseModel):
|
||||
"""Input for a single ingredient requirement"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
required_quantity: Decimal
|
||||
required_by_date: date
|
||||
|
||||
supplier_id: Optional[UUID] = None
|
||||
lead_time_days: int = 3
|
||||
shelf_life_days: Optional[int] = None
|
||||
is_perishable: bool = False
|
||||
category: str = 'dry'
|
||||
unit_of_measure: str = 'kg'
|
||||
|
||||
current_stock: Decimal = Decimal('0')
|
||||
daily_consumption_rate: float = 0.0
|
||||
demand_std_dev: float = 0.0
|
||||
|
||||
|
||||
class GenerateReplenishmentPlanRequest(BaseModel):
|
||||
"""Request to generate replenishment plan"""
|
||||
tenant_id: UUID
|
||||
requirements: List[IngredientRequirementInput]
|
||||
forecast_id: Optional[UUID] = None
|
||||
production_schedule_id: Optional[UUID] = None
|
||||
|
||||
projection_horizon_days: int = 7
|
||||
service_level: float = 0.95
|
||||
buffer_days: int = 1
|
||||
|
||||
|
||||
class GenerateReplenishmentPlanResponse(BaseModel):
|
||||
"""Response from generating replenishment plan"""
|
||||
plan_id: UUID
|
||||
tenant_id: UUID
|
||||
planning_date: date
|
||||
projection_horizon_days: int
|
||||
|
||||
total_items: int
|
||||
urgent_items: int
|
||||
high_risk_items: int
|
||||
total_estimated_cost: Decimal
|
||||
|
||||
created_at: datetime
|
||||
|
||||
items: List[Dict[str, Any]]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# MOQ Aggregation Schemas
|
||||
# ============================================================================
|
||||
|
||||
class MOQAggregationRequest(BaseModel):
|
||||
"""Request for MOQ aggregation"""
|
||||
requirements: List[Dict[str, Any]]
|
||||
supplier_constraints: Dict[str, Dict[str, Any]]
|
||||
|
||||
|
||||
class MOQAggregationResponse(BaseModel):
|
||||
"""Response from MOQ aggregation"""
|
||||
aggregated_orders: List[Dict[str, Any]]
|
||||
efficiency_metrics: Dict[str, Any]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Safety Stock Calculation Schemas
|
||||
# ============================================================================
|
||||
|
||||
class SafetyStockRequest(BaseModel):
|
||||
"""Request for safety stock calculation"""
|
||||
ingredient_id: UUID
|
||||
daily_demands: List[float]
|
||||
lead_time_days: int
|
||||
service_level: float = 0.95
|
||||
|
||||
|
||||
class SafetyStockResponse(BaseModel):
|
||||
"""Response from safety stock calculation"""
|
||||
safety_stock_quantity: Decimal
|
||||
service_level: float
|
||||
z_score: float
|
||||
demand_std_dev: float
|
||||
lead_time_days: int
|
||||
calculation_method: str
|
||||
confidence: str
|
||||
reasoning: str
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Inventory Projection Request Schemas
|
||||
# ============================================================================
|
||||
|
||||
class ProjectInventoryRequest(BaseModel):
|
||||
"""Request to project inventory"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
current_stock: Decimal
|
||||
unit_of_measure: str
|
||||
daily_demand: List[Dict[str, Any]]
|
||||
scheduled_receipts: List[Dict[str, Any]] = []
|
||||
projection_horizon_days: int = 7
|
||||
|
||||
|
||||
class ProjectInventoryResponse(BaseModel):
|
||||
"""Response from inventory projection"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
current_stock: Decimal
|
||||
unit_of_measure: str
|
||||
projection_horizon_days: int
|
||||
total_consumption: Decimal
|
||||
total_receipts: Decimal
|
||||
stockout_days: int
|
||||
stockout_risk: str
|
||||
daily_projections: List[Dict[str, Any]]
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Supplier Selection History Schemas
|
||||
# ============================================================================
|
||||
|
||||
class SupplierSelectionHistoryBase(BaseModel):
|
||||
"""Base schema for supplier selection history"""
|
||||
ingredient_id: UUID
|
||||
ingredient_name: str
|
||||
selected_supplier_id: UUID
|
||||
selected_supplier_name: str
|
||||
|
||||
selection_date: date
|
||||
quantity: Decimal
|
||||
unit_price: Decimal
|
||||
total_cost: Decimal
|
||||
|
||||
lead_time_days: int
|
||||
quality_score: Optional[Decimal] = None
|
||||
delivery_performance: Optional[Decimal] = None
|
||||
|
||||
selection_strategy: str
|
||||
was_primary_choice: bool = True
|
||||
|
||||
|
||||
class SupplierSelectionHistoryCreate(SupplierSelectionHistoryBase):
|
||||
"""Schema for creating supplier selection history"""
|
||||
tenant_id: UUID
|
||||
|
||||
|
||||
class SupplierSelectionHistoryResponse(SupplierSelectionHistoryBase):
|
||||
"""Schema for supplier selection history response"""
|
||||
id: UUID
|
||||
tenant_id: UUID
|
||||
created_at: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Analytics Schemas
|
||||
# ============================================================================
|
||||
|
||||
class ReplenishmentAnalytics(BaseModel):
|
||||
"""Analytics for replenishment planning"""
|
||||
total_plans: int
|
||||
total_items_planned: int
|
||||
total_estimated_value: Decimal
|
||||
|
||||
urgent_items_percentage: float
|
||||
high_risk_items_percentage: float
|
||||
|
||||
average_lead_time_days: float
|
||||
average_safety_stock_percentage: float
|
||||
|
||||
stockout_prevention_rate: float
|
||||
moq_optimization_savings: Decimal
|
||||
|
||||
supplier_diversification_rate: float
|
||||
average_suppliers_per_ingredient: float
|
||||
|
||||
|
||||
class InventoryProjectionAnalytics(BaseModel):
|
||||
"""Analytics for inventory projections"""
|
||||
total_ingredients: int
|
||||
stockout_ingredients: int
|
||||
stockout_percentage: float
|
||||
|
||||
risk_breakdown: Dict[str, int]
|
||||
|
||||
total_stockout_days: int
|
||||
total_consumption: Decimal
|
||||
total_receipts: Decimal
|
||||
|
||||
projection_horizon_days: int
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# Validators
|
||||
# ============================================================================
|
||||
|
||||
@validator('required_quantity', 'current_stock', 'allocated_quantity',
|
||||
'safety_stock_quantity', 'base_quantity', 'final_order_quantity')
|
||||
def validate_positive_quantity(cls, v):
|
||||
"""Validate that quantities are positive"""
|
||||
if v < 0:
|
||||
raise ValueError('Quantity must be non-negative')
|
||||
return v
|
||||
|
||||
|
||||
@validator('service_level')
|
||||
def validate_service_level(cls, v):
|
||||
"""Validate service level is between 0 and 1"""
|
||||
if not 0 <= v <= 1:
|
||||
raise ValueError('Service level must be between 0 and 1')
|
||||
return v
|
||||
Reference in New Issue
Block a user