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

218 lines
10 KiB
Python

# ================================================================
# 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 shared.database.base 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")