173 lines
7.2 KiB
Python
173 lines
7.2 KiB
Python
"""
|
|
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)
|
|
|
|
# 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'),
|
|
) |