# ================================================================ # services/orders/app/models/order.py # ================================================================ """ Order-related database models for Orders Service """ import uuid from datetime import datetime from decimal import Decimal from typing import Optional, List from sqlalchemy import Column, String, Boolean, DateTime, Numeric, Text, ForeignKey, Integer from sqlalchemy.dialects.postgresql import UUID, JSONB from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.core.database import Base class CustomerOrder(Base): """Customer order model for tracking orders throughout their lifecycle""" __tablename__ = "customer_orders" # 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) order_number = Column(String(50), nullable=False, unique=True, index=True) # Customer information customer_id = Column(UUID(as_uuid=True), ForeignKey("customers.id"), nullable=False, index=True) # Order status and lifecycle status = Column(String(50), nullable=False, default="pending", index=True) # Status values: pending, confirmed, in_production, ready, out_for_delivery, delivered, cancelled, failed order_type = Column(String(50), nullable=False, default="standard") # standard, rush, recurring, special priority = Column(String(20), nullable=False, default="normal") # high, normal, low # Order timing order_date = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) requested_delivery_date = Column(DateTime(timezone=True), nullable=False) confirmed_delivery_date = Column(DateTime(timezone=True), nullable=True) actual_delivery_date = Column(DateTime(timezone=True), nullable=True) # Delivery information delivery_method = Column(String(50), nullable=False, default="delivery") # delivery, pickup delivery_address = Column(JSONB, nullable=True) # Complete delivery address delivery_instructions = Column(Text, nullable=True) delivery_window_start = Column(DateTime(timezone=True), nullable=True) delivery_window_end = Column(DateTime(timezone=True), nullable=True) # Financial information subtotal = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00")) discount_amount = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00")) discount_percentage = Column(Numeric(5, 2), nullable=False, default=Decimal("0.00")) tax_amount = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00")) delivery_fee = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00")) total_amount = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00")) # Payment information payment_status = Column(String(50), nullable=False, default="pending") # pending, partial, paid, failed, refunded payment_method = Column(String(50), nullable=True) # cash, card, bank_transfer, account payment_terms = Column(String(50), nullable=False, default="immediate") payment_due_date = Column(DateTime(timezone=True), nullable=True) # Special requirements and customizations special_instructions = Column(Text, nullable=True) custom_requirements = Column(JSONB, nullable=True) # Special dietary requirements, decorations allergen_warnings = Column(JSONB, nullable=True) # Allergen information # Business model detection business_model = Column(String(50), nullable=True) # individual_bakery, central_bakery (auto-detected) estimated_business_model = Column(String(50), nullable=True) # Based on order patterns # Order source and channel order_source = Column(String(50), nullable=False, default="manual") # manual, online, phone, app, api sales_channel = Column(String(50), nullable=False, default="direct") # direct, wholesale, retail order_origin = Column(String(100), nullable=True) # Website, app, store location # Fulfillment tracking production_batch_id = Column(UUID(as_uuid=True), nullable=True) # Link to production batch fulfillment_location = Column(String(100), nullable=True) # Which location fulfills this order estimated_preparation_time = Column(Integer, nullable=True) # Minutes actual_preparation_time = Column(Integer, nullable=True) # Minutes # Customer communication customer_notified_confirmed = Column(Boolean, nullable=False, default=False) customer_notified_ready = Column(Boolean, nullable=False, default=False) customer_notified_delivered = Column(Boolean, nullable=False, default=False) communication_preferences = Column(JSONB, nullable=True) # Quality and feedback quality_score = Column(Numeric(3, 1), nullable=True) # 1.0 to 10.0 customer_rating = Column(Integer, nullable=True) # 1-5 stars customer_feedback = Column(Text, nullable=True) # Cancellation and refunds cancellation_reason = Column(String(200), nullable=True) cancelled_at = Column(DateTime(timezone=True), nullable=True) cancelled_by = Column(UUID(as_uuid=True), nullable=True) refund_amount = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00")) refund_processed_at = Column(DateTime(timezone=True), 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 order_metadata = Column(JSONB, nullable=True) # Flexible field for additional data # Relationships customer = relationship("Customer", back_populates="orders") items = relationship("OrderItem", back_populates="order", cascade="all, delete-orphan") status_history = relationship("OrderStatusHistory", back_populates="order", cascade="all, delete-orphan") class OrderItem(Base): """Individual items within a customer order""" __tablename__ = "order_items" # Primary identification id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) order_id = Column(UUID(as_uuid=True), ForeignKey("customer_orders.id", ondelete="CASCADE"), nullable=False) # Product information product_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Reference to products service product_name = Column(String(200), nullable=False) product_sku = Column(String(100), nullable=True) product_category = Column(String(100), nullable=True) # Quantity and units quantity = Column(Numeric(10, 3), nullable=False) unit_of_measure = Column(String(50), nullable=False, default="each") weight = Column(Numeric(10, 3), nullable=True) # For weight-based products # Pricing information unit_price = Column(Numeric(10, 2), nullable=False) line_discount = Column(Numeric(10, 2), nullable=False, default=Decimal("0.00")) line_total = Column(Numeric(10, 2), nullable=False) # Product specifications and customizations product_specifications = Column(JSONB, nullable=True) # Size, flavor, decorations customization_details = Column(Text, nullable=True) special_instructions = Column(Text, nullable=True) # Production requirements recipe_id = Column(UUID(as_uuid=True), nullable=True) # Reference to recipes service production_requirements = Column(JSONB, nullable=True) # Ingredients, equipment needed estimated_production_time = Column(Integer, nullable=True) # Minutes # Fulfillment tracking status = Column(String(50), nullable=False, default="pending") # pending, in_production, ready, delivered production_started_at = Column(DateTime(timezone=True), nullable=True) production_completed_at = Column(DateTime(timezone=True), nullable=True) quality_checked = Column(Boolean, nullable=False, default=False) quality_score = Column(Numeric(3, 1), nullable=True) # Cost tracking ingredient_cost = Column(Numeric(10, 2), nullable=True) labor_cost = Column(Numeric(10, 2), nullable=True) overhead_cost = Column(Numeric(10, 2), nullable=True) total_cost = Column(Numeric(10, 2), nullable=True) margin = Column(Numeric(10, 2), nullable=True) # Inventory impact reserved_inventory = Column(Boolean, nullable=False, default=False) inventory_allocated_at = Column(DateTime(timezone=True), 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 customer_metadata = Column(JSONB, nullable=True) # Relationships order = relationship("CustomerOrder", back_populates="items") class OrderStatusHistory(Base): """Track status changes and important events in order lifecycle""" __tablename__ = "order_status_history" # Primary identification id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) order_id = Column(UUID(as_uuid=True), ForeignKey("customer_orders.id", ondelete="CASCADE"), nullable=False) # Status change information from_status = Column(String(50), nullable=True) to_status = Column(String(50), nullable=False) change_reason = Column(String(200), nullable=True) # Event details event_type = Column(String(50), nullable=False, default="status_change") # Event types: status_change, payment_received, production_started, delivery_scheduled, etc. event_description = Column(Text, nullable=True) event_data = Column(JSONB, nullable=True) # Additional event-specific data # Who made the change changed_by = Column(UUID(as_uuid=True), nullable=True) change_source = Column(String(50), nullable=False, default="manual") # manual, automatic, system, api # Timing changed_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False) # Customer communication customer_notified = Column(Boolean, nullable=False, default=False) notification_method = Column(String(50), nullable=True) # email, sms, phone, app notification_sent_at = Column(DateTime(timezone=True), nullable=True) # Additional notes notes = Column(Text, nullable=True) # Relationships order = relationship("CustomerOrder", back_populates="status_history")