""" Database models for replenishment planning. """ from sqlalchemy import Column, String, Integer, Numeric, Date, Boolean, ForeignKey, Text, TIMESTAMP, JSON from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.orm import relationship import uuid from datetime import datetime from shared.database import Base class ReplenishmentPlan(Base): """Replenishment plan master record""" __tablename__ = "replenishment_plans" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Planning metadata planning_date = Column(Date, nullable=False) projection_horizon_days = Column(Integer, nullable=False, default=7) # References forecast_id = Column(UUID(as_uuid=True), nullable=True) production_schedule_id = Column(UUID(as_uuid=True), nullable=True) # Summary statistics total_items = Column(Integer, nullable=False, default=0) urgent_items = Column(Integer, nullable=False, default=0) high_risk_items = Column(Integer, nullable=False, default=0) total_estimated_cost = Column(Numeric(12, 2), nullable=False, default=0) # Status status = Column(String(50), nullable=False, default='draft') # draft, approved, executed # Timestamps created_at = Column(TIMESTAMP(timezone=True), nullable=False, default=datetime.utcnow) updated_at = Column(TIMESTAMP(timezone=True), nullable=True, onupdate=datetime.utcnow) executed_at = Column(TIMESTAMP(timezone=True), nullable=True) # Relationships items = relationship("ReplenishmentPlanItem", back_populates="plan", cascade="all, delete-orphan") class ReplenishmentPlanItem(Base): """Individual item in a replenishment plan""" __tablename__ = "replenishment_plan_items" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) replenishment_plan_id = Column(UUID(as_uuid=True), ForeignKey("replenishment_plans.id"), nullable=False, index=True) # Ingredient info ingredient_id = Column(UUID(as_uuid=True), nullable=False, index=True) ingredient_name = Column(String(200), nullable=False) unit_of_measure = Column(String(20), nullable=False) # Quantities base_quantity = Column(Numeric(12, 3), nullable=False) safety_stock_quantity = Column(Numeric(12, 3), nullable=False, default=0) shelf_life_adjusted_quantity = Column(Numeric(12, 3), nullable=False) final_order_quantity = Column(Numeric(12, 3), nullable=False) # Dates order_date = Column(Date, nullable=False, index=True) delivery_date = Column(Date, nullable=False) required_by_date = Column(Date, nullable=False) # Planning metadata lead_time_days = Column(Integer, nullable=False) is_urgent = Column(Boolean, nullable=False, default=False, index=True) urgency_reason = Column(Text, nullable=True) waste_risk = Column(String(20), nullable=False, default='low') # low, medium, high stockout_risk = Column(String(20), nullable=False, default='low') # low, medium, high, critical # Supplier supplier_id = Column(UUID(as_uuid=True), nullable=True) # Calculation details (stored as JSONB) safety_stock_calculation = Column(JSONB, nullable=True) shelf_life_adjustment = Column(JSONB, nullable=True) inventory_projection = Column(JSONB, nullable=True) # Timestamps created_at = Column(TIMESTAMP(timezone=True), nullable=False, default=datetime.utcnow) # Relationships plan = relationship("ReplenishmentPlan", back_populates="items") class InventoryProjection(Base): """Daily inventory projection""" __tablename__ = "inventory_projections" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Ingredient ingredient_id = Column(UUID(as_uuid=True), nullable=False, index=True) ingredient_name = Column(String(200), nullable=False) # Projection date projection_date = Column(Date, nullable=False, index=True) # Stock levels starting_stock = Column(Numeric(12, 3), nullable=False) forecasted_consumption = Column(Numeric(12, 3), nullable=False, default=0) scheduled_receipts = Column(Numeric(12, 3), nullable=False, default=0) projected_ending_stock = Column(Numeric(12, 3), nullable=False) # Flags is_stockout = Column(Boolean, nullable=False, default=False, index=True) coverage_gap = Column(Numeric(12, 3), nullable=False, default=0) # Negative if stockout # Reference to replenishment plan replenishment_plan_id = Column(UUID(as_uuid=True), nullable=True) # Timestamps created_at = Column(TIMESTAMP(timezone=True), nullable=False, default=datetime.utcnow) __table_args__ = ( # Unique constraint: one projection per ingredient per date per tenant {'schema': None} ) class SupplierAllocation(Base): """Supplier allocation for a requirement""" __tablename__ = "supplier_allocations" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) # References replenishment_plan_item_id = Column(UUID(as_uuid=True), ForeignKey("replenishment_plan_items.id"), nullable=True, index=True) requirement_id = Column(UUID(as_uuid=True), nullable=True, index=True) # Reference to procurement_requirements # Supplier supplier_id = Column(UUID(as_uuid=True), nullable=False, index=True) supplier_name = Column(String(200), nullable=False) # Allocation allocation_type = Column(String(20), nullable=False) # primary, backup, diversification allocated_quantity = Column(Numeric(12, 3), nullable=False) allocation_percentage = Column(Numeric(5, 4), nullable=False) # 0.0000 - 1.0000 # Pricing unit_price = Column(Numeric(12, 2), nullable=False) total_cost = Column(Numeric(12, 2), nullable=False) # Lead time lead_time_days = Column(Integer, nullable=False) # Scoring supplier_score = Column(Numeric(5, 2), nullable=False) score_breakdown = Column(JSONB, nullable=True) # Reasoning allocation_reason = Column(Text, nullable=True) # Timestamps created_at = Column(TIMESTAMP(timezone=True), nullable=False, default=datetime.utcnow) class SupplierSelectionHistory(Base): """Historical record of supplier selections for analytics""" __tablename__ = "supplier_selection_history" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Selection details ingredient_id = Column(UUID(as_uuid=True), nullable=False, index=True) ingredient_name = Column(String(200), nullable=False) selected_supplier_id = Column(UUID(as_uuid=True), nullable=False, index=True) selected_supplier_name = Column(String(200), nullable=False) # Order details selection_date = Column(Date, nullable=False, index=True) quantity = Column(Numeric(12, 3), nullable=False) unit_price = Column(Numeric(12, 2), nullable=False) total_cost = Column(Numeric(12, 2), nullable=False) # Metrics lead_time_days = Column(Integer, nullable=False) quality_score = Column(Numeric(5, 2), nullable=True) delivery_performance = Column(Numeric(5, 2), nullable=True) # Selection strategy selection_strategy = Column(String(50), nullable=False) # single_source, dual_source, multi_source was_primary_choice = Column(Boolean, nullable=False, default=True) # Timestamps created_at = Column(TIMESTAMP(timezone=True), nullable=False, default=datetime.utcnow)