""" Distribution models for the bakery management platform """ import uuid import enum from datetime import datetime, timezone from decimal import Decimal from sqlalchemy import Column, String, DateTime, Float, Integer, Text, Index, Boolean, Numeric, ForeignKey, Enum as SQLEnum from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.orm import relationship from sqlalchemy.sql import func from shared.database.base import Base class DeliveryRouteStatus(enum.Enum): """Status of delivery routes""" planned = "planned" in_progress = "in_progress" completed = "completed" cancelled = "cancelled" class ShipmentStatus(enum.Enum): """Status of individual shipments""" pending = "pending" packed = "packed" in_transit = "in_transit" delivered = "delivered" failed = "failed" class DeliveryScheduleFrequency(enum.Enum): """Frequency of recurring delivery schedules""" daily = "daily" weekly = "weekly" biweekly = "biweekly" monthly = "monthly" class DeliveryRoute(Base): """Optimized multi-stop routes for distribution""" __tablename__ = "delivery_routes" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Route identification route_number = Column(String(50), nullable=False, unique=True, index=True) route_date = Column(DateTime(timezone=True), nullable=False, index=True) # Date when route is executed # Vehicle and driver assignment vehicle_id = Column(String(100), nullable=True) # Reference to fleet management driver_id = Column(UUID(as_uuid=True), nullable=True, index=True) # Reference to driver # Optimization metadata total_distance_km = Column(Float, nullable=True) estimated_duration_minutes = Column(Integer, nullable=True) # VRP Optimization metrics (Phase 2 enhancement) vrp_optimization_savings = Column(JSONB, nullable=True) # {"distance_saved_km": 12.5, "time_saved_minutes": 25, "fuel_saved_liters": 8.2, "co2_saved_kg": 15.4, "cost_saved_eur": 12.50} vrp_algorithm_version = Column(String(50), nullable=True) # Version of VRP algorithm used vrp_optimization_timestamp = Column(DateTime(timezone=True), nullable=True) # When optimization was performed vrp_constraints_satisfied = Column(Boolean, nullable=True) # Whether all constraints were satisfied vrp_objective_value = Column(Float, nullable=True) # Objective function value from optimization # Route details route_sequence = Column(JSONB, nullable=True) # Ordered array of stops with timing: [{"stop_number": 1, "location_id": "...", "estimated_arrival": "...", "actual_arrival": "..."}] notes = Column(Text, nullable=True) # Status status = Column(SQLEnum(DeliveryRouteStatus), nullable=False, default=DeliveryRouteStatus.planned, index=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=False) updated_by = Column(UUID(as_uuid=True), nullable=False) # Relationships shipments = relationship("Shipment", back_populates="route", cascade="all, delete-orphan") # Indexes __table_args__ = ( Index('ix_delivery_routes_tenant_date', 'tenant_id', 'route_date'), Index('ix_delivery_routes_status', 'status'), Index('ix_delivery_routes_date_tenant_status', 'route_date', 'tenant_id', 'status'), ) class Shipment(Base): """Individual deliveries to child tenants""" __tablename__ = "shipments" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Links to hierarchy and procurement parent_tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Source tenant (central production) child_tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Destination tenant (retail outlet) purchase_order_id = Column(UUID(as_uuid=True), nullable=True, index=True) # Associated internal purchase order delivery_route_id = Column(UUID(as_uuid=True), ForeignKey('delivery_routes.id', ondelete='SET NULL'), nullable=True, index=True) # Assigned route # Shipment details shipment_number = Column(String(50), nullable=False, unique=True, index=True) shipment_date = Column(DateTime(timezone=True), nullable=False, index=True) # Tracking information current_location_lat = Column(Float, nullable=True) current_location_lng = Column(Float, nullable=True) last_tracked_at = Column(DateTime(timezone=True), nullable=True) status = Column(SQLEnum(ShipmentStatus), nullable=False, default=ShipmentStatus.pending, index=True) actual_delivery_time = Column(DateTime(timezone=True), nullable=True) # Proof of delivery signature = Column(Text, nullable=True) # Digital signature base64 encoded photo_url = Column(String(500), nullable=True) # URL to delivery confirmation photo received_by_name = Column(String(200), nullable=True) delivery_notes = Column(Text, nullable=True) # Weight/volume tracking total_weight_kg = Column(Float, nullable=True) total_volume_m3 = Column(Float, 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=False) updated_by = Column(UUID(as_uuid=True), nullable=False) # Relationships route = relationship("DeliveryRoute", back_populates="shipments") # Indexes __table_args__ = ( Index('ix_shipments_tenant_status', 'tenant_id', 'status'), Index('ix_shipments_parent_child', 'parent_tenant_id', 'child_tenant_id'), Index('ix_shipments_date_tenant', 'shipment_date', 'tenant_id'), ) class DeliverySchedule(Base): """Recurring delivery patterns""" __tablename__ = "delivery_schedules" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Schedule identification name = Column(String(200), nullable=False) # Delivery pattern delivery_days = Column(String(200), nullable=False) # Format: "Mon,Wed,Fri" or "Mon-Fri" delivery_time = Column(String(20), nullable=False) # Format: "HH:MM" or "HH:MM-HH:MM" frequency = Column(SQLEnum(DeliveryScheduleFrequency), nullable=False, default=DeliveryScheduleFrequency.weekly) # Auto-generation settings auto_generate_orders = Column(Boolean, nullable=False, default=False) lead_time_days = Column(Integer, nullable=False, default=1) # How many days in advance to generate # Target tenants for this schedule target_parent_tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True) target_child_tenant_ids = Column(JSONB, nullable=False) # List of child tenant IDs involved in this route # Configuration is_active = Column(Boolean, nullable=False, default=True) notes = Column(Text, 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=False) updated_by = Column(UUID(as_uuid=True), nullable=False) # Indexes __table_args__ = ( Index('ix_delivery_schedules_tenant_active', 'tenant_id', 'is_active'), Index('ix_delivery_schedules_parent_tenant', 'target_parent_tenant_id'), )