Files
bakery-ia/services/orders/app/models/procurement.py
2025-10-01 11:24:06 +02:00

217 lines
11 KiB
Python

# ================================================================
# services/orders/app/models/procurement.py
# ================================================================
"""
Procurement planning database models for Orders Service
"""
import uuid
from datetime import datetime, date
from decimal import Decimal
from typing import Optional, List
from sqlalchemy import Column, String, Boolean, DateTime, Date, Numeric, Text, Integer, ForeignKey
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func
from shared.database.base import Base
class ProcurementPlan(Base):
"""Master procurement plan for coordinating supply needs across orders and production"""
__tablename__ = "procurement_plans"
# Primary identification
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
plan_number = Column(String(50), nullable=False, unique=True, index=True)
# Plan scope and timing
plan_date = Column(Date, nullable=False, index=True)
plan_period_start = Column(Date, nullable=False)
plan_period_end = Column(Date, nullable=False)
planning_horizon_days = Column(Integer, nullable=False, default=14)
# Plan status and lifecycle
status = Column(String(50), nullable=False, default="draft", index=True)
# Status values: draft, pending_approval, approved, in_execution, completed, cancelled
plan_type = Column(String(50), nullable=False, default="regular") # regular, emergency, seasonal
priority = Column(String(20), nullable=False, default="normal") # high, normal, low
# Business model context
business_model = Column(String(50), nullable=True) # individual_bakery, central_bakery
procurement_strategy = Column(String(50), nullable=False, default="just_in_time") # just_in_time, bulk, mixed
# Plan totals and summary
total_requirements = Column(Integer, nullable=False, default=0)
total_estimated_cost = Column(Numeric(12, 2), nullable=False, default=Decimal("0.00"))
total_approved_cost = Column(Numeric(12, 2), nullable=False, default=Decimal("0.00"))
cost_variance = Column(Numeric(12, 2), nullable=False, default=Decimal("0.00"))
# Demand analysis
total_demand_orders = Column(Integer, nullable=False, default=0)
total_demand_quantity = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
total_production_requirements = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
safety_stock_buffer = Column(Numeric(5, 2), nullable=False, default=Decimal("20.00")) # Percentage
# Supplier coordination
primary_suppliers_count = Column(Integer, nullable=False, default=0)
backup_suppliers_count = Column(Integer, nullable=False, default=0)
supplier_diversification_score = Column(Numeric(3, 1), nullable=True) # 1.0 to 10.0
# Risk assessment
supply_risk_level = Column(String(20), nullable=False, default="low") # low, medium, high, critical
demand_forecast_confidence = Column(Numeric(3, 1), nullable=True) # 1.0 to 10.0
seasonality_adjustment = Column(Numeric(5, 2), nullable=False, default=Decimal("0.00"))
# Execution tracking
approved_at = Column(DateTime(timezone=True), nullable=True)
approved_by = Column(UUID(as_uuid=True), nullable=True)
execution_started_at = Column(DateTime(timezone=True), nullable=True)
execution_completed_at = Column(DateTime(timezone=True), nullable=True)
# Performance metrics
fulfillment_rate = Column(Numeric(5, 2), nullable=True) # Percentage
on_time_delivery_rate = Column(Numeric(5, 2), nullable=True) # Percentage
cost_accuracy = Column(Numeric(5, 2), nullable=True) # Percentage
quality_score = Column(Numeric(3, 1), nullable=True) # 1.0 to 10.0
# Integration data
source_orders = Column(JSONB, nullable=True) # Orders that drove this plan
production_schedules = Column(JSONB, nullable=True) # Associated production schedules
inventory_snapshots = Column(JSONB, nullable=True) # Inventory levels at planning time
# Communication and collaboration
stakeholder_notifications = Column(JSONB, nullable=True) # Who was notified and when
approval_workflow = Column(JSONB, nullable=True) # Approval chain and status
# Special considerations
special_requirements = Column(Text, nullable=True)
seasonal_adjustments = Column(JSONB, nullable=True)
emergency_provisions = Column(JSONB, nullable=True)
# External references
erp_reference = Column(String(100), nullable=True)
supplier_portal_reference = Column(String(100), nullable=True)
# Audit fields
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
created_by = Column(UUID(as_uuid=True), nullable=True)
updated_by = Column(UUID(as_uuid=True), nullable=True)
# Additional metadata
plan_metadata = Column(JSONB, nullable=True)
# Relationships
requirements = relationship("ProcurementRequirement", back_populates="plan", cascade="all, delete-orphan")
class ProcurementRequirement(Base):
"""Individual procurement requirements within a procurement plan"""
__tablename__ = "procurement_requirements"
# Primary identification
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
plan_id = Column(UUID(as_uuid=True), ForeignKey("procurement_plans.id", ondelete="CASCADE"), nullable=False)
requirement_number = Column(String(50), nullable=False, index=True)
# Product/ingredient information
product_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Reference to products/ingredients
product_name = Column(String(200), nullable=False)
product_sku = Column(String(100), nullable=True)
product_category = Column(String(100), nullable=True)
product_type = Column(String(50), nullable=False, default="ingredient") # ingredient, packaging, supplies
# Requirement details
required_quantity = Column(Numeric(12, 3), nullable=False)
unit_of_measure = Column(String(50), nullable=False)
safety_stock_quantity = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
total_quantity_needed = Column(Numeric(12, 3), nullable=False)
# Current inventory situation
current_stock_level = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
reserved_stock = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
available_stock = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
net_requirement = Column(Numeric(12, 3), nullable=False)
# Demand breakdown
order_demand = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
production_demand = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
forecast_demand = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
buffer_demand = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
# Supplier information
preferred_supplier_id = Column(UUID(as_uuid=True), nullable=True)
backup_supplier_id = Column(UUID(as_uuid=True), nullable=True)
supplier_name = Column(String(200), nullable=True)
supplier_lead_time_days = Column(Integer, nullable=True)
minimum_order_quantity = Column(Numeric(12, 3), nullable=True)
# Pricing and cost
estimated_unit_cost = Column(Numeric(10, 4), nullable=True)
estimated_total_cost = Column(Numeric(12, 2), nullable=True)
last_purchase_cost = Column(Numeric(10, 4), nullable=True)
cost_variance = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00"))
# Timing requirements
required_by_date = Column(Date, nullable=False)
lead_time_buffer_days = Column(Integer, nullable=False, default=1)
suggested_order_date = Column(Date, nullable=False)
latest_order_date = Column(Date, nullable=False)
# Quality and specifications
quality_specifications = Column(JSONB, nullable=True)
special_requirements = Column(Text, nullable=True)
storage_requirements = Column(String(200), nullable=True)
shelf_life_days = Column(Integer, nullable=True)
# Requirement status
status = Column(String(50), nullable=False, default="pending")
# Status values: pending, approved, ordered, partially_received, received, cancelled
priority = Column(String(20), nullable=False, default="normal") # critical, high, normal, low
risk_level = Column(String(20), nullable=False, default="low") # low, medium, high, critical
# Purchase order tracking
purchase_order_id = Column(UUID(as_uuid=True), nullable=True)
purchase_order_number = Column(String(50), nullable=True)
ordered_quantity = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
ordered_at = Column(DateTime(timezone=True), nullable=True)
# Delivery tracking
expected_delivery_date = Column(Date, nullable=True)
actual_delivery_date = Column(Date, nullable=True)
received_quantity = Column(Numeric(12, 3), nullable=False, default=Decimal("0.000"))
delivery_status = Column(String(50), nullable=False, default="pending")
# Performance tracking
fulfillment_rate = Column(Numeric(5, 2), nullable=True) # Percentage
on_time_delivery = Column(Boolean, nullable=True)
quality_rating = Column(Numeric(3, 1), nullable=True) # 1.0 to 10.0
# Source traceability
source_orders = Column(JSONB, nullable=True) # Orders that contributed to this requirement
source_production_batches = Column(JSONB, nullable=True) # Production batches needing this
demand_analysis = Column(JSONB, nullable=True) # Detailed demand breakdown
# Approval and authorization
approved_quantity = Column(Numeric(12, 3), nullable=True)
approved_cost = Column(Numeric(12, 2), nullable=True)
approved_at = Column(DateTime(timezone=True), nullable=True)
approved_by = Column(UUID(as_uuid=True), nullable=True)
# Notes and communication
procurement_notes = Column(Text, nullable=True)
supplier_communication = Column(JSONB, nullable=True)
# Audit fields
created_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False)
# Additional metadata
requirement_metadata = Column(JSONB, nullable=True)
# Relationships
plan = relationship("ProcurementPlan", back_populates="requirements")