Files
bakery-ia/services/pos/app/models/pos_transaction.py
2025-08-16 15:00:36 +02:00

174 lines
7.5 KiB
Python

# services/pos/app/models/pos_transaction.py
"""
POS Transaction Models
Stores transaction data from POS systems
"""
from sqlalchemy import Column, String, DateTime, Boolean, Numeric, Integer, Text, JSON, Index, ForeignKey
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
import uuid
from shared.database.base import Base
class POSTransaction(Base):
"""
Main transaction record from POS systems
"""
__tablename__ = "pos_transactions"
# Primary identifiers
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
pos_config_id = Column(UUID(as_uuid=True), ForeignKey("pos_configurations.id"), nullable=False, index=True)
# POS Provider Information
pos_system = Column(String(50), nullable=False, index=True) # square, toast, lightspeed
external_transaction_id = Column(String(255), nullable=False, index=True) # POS system's transaction ID
external_order_id = Column(String(255), nullable=True, index=True) # POS system's order ID
# Transaction Details
transaction_type = Column(String(50), nullable=False) # sale, refund, void, exchange
status = Column(String(50), nullable=False) # completed, pending, failed, refunded, voided
# Financial Information
subtotal = Column(Numeric(10, 2), nullable=False)
tax_amount = Column(Numeric(10, 2), default=0, nullable=False)
tip_amount = Column(Numeric(10, 2), default=0, nullable=False)
discount_amount = Column(Numeric(10, 2), default=0, nullable=False)
total_amount = Column(Numeric(10, 2), nullable=False)
currency = Column(String(3), default="EUR", nullable=False)
# Payment Information
payment_method = Column(String(50), nullable=True) # card, cash, digital_wallet, etc.
payment_status = Column(String(50), nullable=True) # paid, pending, failed
# Transaction Timing
transaction_date = Column(DateTime(timezone=True), nullable=False, index=True)
pos_created_at = Column(DateTime(timezone=True), nullable=False) # Original POS timestamp
pos_updated_at = Column(DateTime(timezone=True), nullable=True) # Last update in POS
# Location & Staff
location_id = Column(String(100), nullable=True)
location_name = Column(String(255), nullable=True)
staff_id = Column(String(100), nullable=True)
staff_name = Column(String(255), nullable=True)
# Customer Information
customer_id = Column(String(100), nullable=True)
customer_email = Column(String(255), nullable=True)
customer_phone = Column(String(50), nullable=True)
# Order Context
order_type = Column(String(50), nullable=True) # dine_in, takeout, delivery, pickup
table_number = Column(String(20), nullable=True)
receipt_number = Column(String(100), nullable=True)
# Sync Status
is_synced_to_sales = Column(Boolean, default=False, nullable=False, index=True)
sales_record_id = Column(UUID(as_uuid=True), nullable=True, index=True) # Reference to sales service
sync_attempted_at = Column(DateTime(timezone=True), nullable=True)
sync_completed_at = Column(DateTime(timezone=True), nullable=True)
sync_error = Column(Text, nullable=True)
sync_retry_count = Column(Integer, default=0, nullable=False)
# Raw Data
raw_data = Column(JSON, nullable=True) # Complete raw response from POS
# Processing Status
is_processed = Column(Boolean, default=False, nullable=False)
processing_error = Column(Text, nullable=True)
# Duplicate Detection
is_duplicate = Column(Boolean, default=False, nullable=False)
duplicate_of = Column(UUID(as_uuid=True), nullable=True)
# Timestamps
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)
# Relationships
items = relationship("POSTransactionItem", back_populates="transaction", cascade="all, delete-orphan")
# Indexes for performance
__table_args__ = (
Index('idx_pos_transaction_tenant_date', 'tenant_id', 'transaction_date'),
Index('idx_pos_transaction_external_id', 'pos_system', 'external_transaction_id'),
Index('idx_pos_transaction_sync_status', 'is_synced_to_sales'),
Index('idx_pos_transaction_status', 'status'),
Index('idx_pos_transaction_type', 'transaction_type'),
Index('idx_pos_transaction_processed', 'is_processed'),
Index('idx_pos_transaction_duplicate', 'is_duplicate'),
Index('idx_pos_transaction_location', 'location_id'),
Index('idx_pos_transaction_customer', 'customer_id'),
)
def __repr__(self):
return f"<POSTransaction(id={self.id}, external_id='{self.external_transaction_id}', pos_system='{self.pos_system}', total={self.total_amount})>"
class POSTransactionItem(Base):
"""
Individual items within a POS transaction
"""
__tablename__ = "pos_transaction_items"
# Primary identifiers
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
transaction_id = Column(UUID(as_uuid=True), ForeignKey("pos_transactions.id"), nullable=False, index=True)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
# POS Item Information
external_item_id = Column(String(255), nullable=True) # POS system's item ID
sku = Column(String(100), nullable=True, index=True)
# Product Details
product_name = Column(String(255), nullable=False)
product_category = Column(String(100), nullable=True, index=True)
product_subcategory = Column(String(100), nullable=True)
# Quantity & Pricing
quantity = Column(Numeric(10, 3), nullable=False)
unit_price = Column(Numeric(10, 2), nullable=False)
total_price = Column(Numeric(10, 2), nullable=False)
# Discounts & Modifiers
discount_amount = Column(Numeric(10, 2), default=0, nullable=False)
tax_amount = Column(Numeric(10, 2), default=0, nullable=False)
# Modifiers (e.g., extra shot, no foam for coffee)
modifiers = Column(JSON, nullable=True)
# Inventory Mapping
inventory_product_id = Column(UUID(as_uuid=True), nullable=True, index=True) # Mapped to inventory service
is_mapped_to_inventory = Column(Boolean, default=False, nullable=False)
# Sync Status
is_synced_to_sales = Column(Boolean, default=False, nullable=False)
sync_error = Column(Text, nullable=True)
# Raw Data
raw_data = Column(JSON, nullable=True)
# Timestamps
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)
# Relationships
transaction = relationship("POSTransaction", back_populates="items")
# Indexes for performance
__table_args__ = (
Index('idx_pos_item_transaction', 'transaction_id'),
Index('idx_pos_item_product', 'product_name'),
Index('idx_pos_item_category', 'product_category'),
Index('idx_pos_item_sku', 'sku'),
Index('idx_pos_item_inventory', 'inventory_product_id'),
Index('idx_pos_item_sync', 'is_synced_to_sales'),
Index('idx_pos_item_mapped', 'is_mapped_to_inventory'),
)
def __repr__(self):
return f"<POSTransactionItem(id={self.id}, product='{self.product_name}', quantity={self.quantity}, price={self.total_price})>"