174 lines
7.5 KiB
Python
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})>" |