218 lines
10 KiB
Python
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 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")
|