# 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"" 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""