Initial commit - production deployment

This commit is contained in:
2026-01-21 17:17:16 +01:00
commit c23d00dd92
2289 changed files with 638440 additions and 0 deletions

View File

@@ -0,0 +1,24 @@
"""
Database models for POS Integration Service
"""
# Import AuditLog model for this service
from shared.security import create_audit_log_model
from shared.database.base import Base
# Create audit log model for this service
AuditLog = create_audit_log_model(Base)
from .pos_config import POSConfiguration
from .pos_transaction import POSTransaction, POSTransactionItem
from .pos_webhook import POSWebhookLog
from .pos_sync import POSSyncLog
__all__ = [
"POSConfiguration",
"POSTransaction",
"POSTransactionItem",
"POSWebhookLog",
"POSSyncLog",
"AuditLog"
]

View File

@@ -0,0 +1,83 @@
# services/pos/app/models/pos_config.py
"""
POS Configuration Model
Stores POS system configurations for each tenant
"""
from sqlalchemy import Column, String, DateTime, Boolean, Text, JSON, Index
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
import uuid
from shared.database.base import Base
class POSConfiguration(Base):
"""
POS system configuration for tenants
Stores encrypted credentials and settings for each POS provider
"""
__tablename__ = "pos_configurations"
# 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 Provider Information
pos_system = Column(String(50), nullable=False) # square, toast, lightspeed
provider_name = Column(String(100), nullable=False) # Display name for the provider
# Configuration Status
is_active = Column(Boolean, default=True, nullable=False)
is_connected = Column(Boolean, default=False, nullable=False)
# Authentication & Credentials (encrypted)
encrypted_credentials = Column(Text, nullable=True) # JSON with encrypted API keys/tokens
webhook_url = Column(String(500), nullable=True)
webhook_secret = Column(String(255), nullable=True)
# Provider-specific Settings
environment = Column(String(20), default="sandbox", nullable=False) # sandbox, production
location_id = Column(String(100), nullable=True) # For multi-location setups
merchant_id = Column(String(100), nullable=True) # Provider merchant ID
# Sync Configuration
sync_enabled = Column(Boolean, default=True, nullable=False)
sync_interval_minutes = Column(String(10), default="5", nullable=False)
auto_sync_products = Column(Boolean, default=True, nullable=False)
auto_sync_transactions = Column(Boolean, default=True, nullable=False)
# Last Sync Information
last_sync_at = Column(DateTime(timezone=True), nullable=True)
last_successful_sync_at = Column(DateTime(timezone=True), nullable=True)
last_sync_status = Column(String(50), nullable=True) # success, failed, partial
last_sync_message = Column(Text, nullable=True)
# Provider-specific Configuration (JSON)
provider_settings = Column(JSON, nullable=True)
# Connection Health
last_health_check_at = Column(DateTime(timezone=True), nullable=True)
health_status = Column(String(50), default="unknown", nullable=False) # healthy, unhealthy, unknown
health_message = Column(Text, 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)
# Metadata
created_by = Column(UUID(as_uuid=True), nullable=True)
notes = Column(Text, nullable=True)
# Indexes for performance
__table_args__ = (
Index('idx_pos_config_tenant_pos_system', 'tenant_id', 'pos_system'),
Index('idx_pos_config_active', 'is_active'),
Index('idx_pos_config_connected', 'is_connected'),
Index('idx_pos_config_sync_enabled', 'sync_enabled'),
Index('idx_pos_config_health_status', 'health_status'),
Index('idx_pos_config_created_at', 'created_at'),
)
def __repr__(self):
return f"<POSConfiguration(id={self.id}, tenant_id={self.tenant_id}, pos_system='{self.pos_system}', is_active={self.is_active})>"

View File

@@ -0,0 +1,126 @@
# services/pos/app/models/pos_sync.py
"""
POS Sync Log Model
Tracks synchronization operations with POS systems
"""
from sqlalchemy import Column, String, DateTime, Boolean, Integer, Text, JSON, Index, Numeric
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
import uuid
from shared.database.base import Base
class POSSyncLog(Base):
"""
Log of synchronization operations with POS systems
"""
__tablename__ = "pos_sync_logs"
# 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), nullable=False, index=True)
# Sync Operation Details
sync_type = Column(String(50), nullable=False, index=True) # full, incremental, manual, webhook_triggered
sync_direction = Column(String(20), nullable=False) # inbound, outbound, bidirectional
data_type = Column(String(50), nullable=False, index=True) # transactions, products, customers, orders
# POS Provider Information
pos_system = Column(String(50), nullable=False, index=True) # square, toast, lightspeed
# Sync Status
status = Column(String(50), nullable=False, default="started", index=True) # started, in_progress, completed, failed, cancelled
# Timing Information
started_at = Column(DateTime(timezone=True), nullable=False, index=True)
completed_at = Column(DateTime(timezone=True), nullable=True)
duration_seconds = Column(Numeric(10, 3), nullable=True)
# Date Range for Sync
sync_from_date = Column(DateTime(timezone=True), nullable=True)
sync_to_date = Column(DateTime(timezone=True), nullable=True)
# Statistics
records_requested = Column(Integer, default=0, nullable=False)
records_processed = Column(Integer, default=0, nullable=False)
records_created = Column(Integer, default=0, nullable=False)
records_updated = Column(Integer, default=0, nullable=False)
records_skipped = Column(Integer, default=0, nullable=False)
records_failed = Column(Integer, default=0, nullable=False)
# API Usage Statistics
api_calls_made = Column(Integer, default=0, nullable=False)
api_rate_limit_hits = Column(Integer, default=0, nullable=False)
total_api_time_ms = Column(Integer, default=0, nullable=False)
# Error Information
error_message = Column(Text, nullable=True)
error_code = Column(String(100), nullable=True)
error_details = Column(JSON, nullable=True)
# Retry Information
retry_attempt = Column(Integer, default=0, nullable=False)
max_retries = Column(Integer, default=3, nullable=False)
parent_sync_id = Column(UUID(as_uuid=True), nullable=True) # Reference to original sync for retries
# Configuration Snapshot
sync_configuration = Column(JSON, nullable=True) # Settings used for this sync
# Progress Tracking
current_page = Column(Integer, nullable=True)
total_pages = Column(Integer, nullable=True)
current_batch = Column(Integer, nullable=True)
total_batches = Column(Integer, nullable=True)
progress_percentage = Column(Numeric(5, 2), nullable=True)
# Data Quality
validation_errors = Column(JSON, nullable=True) # Array of validation issues
data_quality_score = Column(Numeric(5, 2), nullable=True) # 0-100 score
# Performance Metrics
memory_usage_mb = Column(Numeric(10, 2), nullable=True)
cpu_usage_percentage = Column(Numeric(5, 2), nullable=True)
network_bytes_received = Column(Integer, nullable=True)
network_bytes_sent = Column(Integer, nullable=True)
# Business Impact
revenue_synced = Column(Numeric(12, 2), nullable=True) # Total monetary value synced
transactions_synced = Column(Integer, default=0, nullable=False)
# Trigger Information
triggered_by = Column(String(50), nullable=True) # system, user, webhook, schedule
triggered_by_user_id = Column(UUID(as_uuid=True), nullable=True)
trigger_details = Column(JSON, nullable=True)
# External References
external_batch_id = Column(String(255), nullable=True) # POS system's batch/job ID
webhook_log_id = Column(UUID(as_uuid=True), nullable=True) # If triggered by webhook
# 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)
# Metadata
notes = Column(Text, nullable=True)
tags = Column(JSON, nullable=True) # Array of tags for categorization
# Indexes for performance
__table_args__ = (
Index('idx_sync_log_tenant_started', 'tenant_id', 'started_at'),
Index('idx_sync_log_pos_system_type', 'pos_system', 'sync_type'),
Index('idx_sync_log_status', 'status'),
Index('idx_sync_log_data_type', 'data_type'),
Index('idx_sync_log_trigger', 'triggered_by'),
Index('idx_sync_log_completed', 'completed_at'),
Index('idx_sync_log_duration', 'duration_seconds'),
Index('idx_sync_log_retry', 'retry_attempt'),
Index('idx_sync_log_parent', 'parent_sync_id'),
Index('idx_sync_log_webhook', 'webhook_log_id'),
Index('idx_sync_log_external_batch', 'external_batch_id'),
)
def __repr__(self):
return f"<POSSyncLog(id={self.id}, pos_system='{self.pos_system}', type='{self.sync_type}', status='{self.status}')>"

View File

@@ -0,0 +1,174 @@
# 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})>"

View File

@@ -0,0 +1,109 @@
# services/pos/app/models/pos_webhook.py
"""
POS Webhook Log Model
Tracks webhook events from POS systems
"""
from sqlalchemy import Column, String, DateTime, Boolean, Integer, Text, JSON, Index
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.sql import func
import uuid
from shared.database.base import Base
class POSWebhookLog(Base):
"""
Log of webhook events received from POS systems
"""
__tablename__ = "pos_webhook_logs"
# Primary identifiers
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, index=True)
tenant_id = Column(UUID(as_uuid=True), nullable=True, index=True) # May be null until parsed
# POS Provider Information
pos_system = Column(String(50), nullable=False, index=True) # square, toast, lightspeed
webhook_type = Column(String(100), nullable=False, index=True) # payment.created, order.updated, etc.
# Request Information
method = Column(String(10), nullable=False) # POST, PUT, etc.
url_path = Column(String(500), nullable=False)
query_params = Column(JSON, nullable=True)
headers = Column(JSON, nullable=True)
# Payload
raw_payload = Column(Text, nullable=False) # Raw webhook payload
payload_size = Column(Integer, nullable=False, default=0)
content_type = Column(String(100), nullable=True)
# Security
signature = Column(String(500), nullable=True) # Webhook signature for verification
is_signature_valid = Column(Boolean, nullable=True) # null = not checked, true/false = verified
source_ip = Column(String(45), nullable=True) # IPv4 or IPv6
# Processing Status
status = Column(String(50), nullable=False, default="received", index=True) # received, processing, processed, failed
processing_started_at = Column(DateTime(timezone=True), nullable=True)
processing_completed_at = Column(DateTime(timezone=True), nullable=True)
processing_duration_ms = Column(Integer, nullable=True)
# Error Handling
error_message = Column(Text, nullable=True)
error_code = Column(String(50), nullable=True)
retry_count = Column(Integer, default=0, nullable=False)
max_retries = Column(Integer, default=3, nullable=False)
# Response Information
response_status_code = Column(Integer, nullable=True)
response_body = Column(Text, nullable=True)
response_sent_at = Column(DateTime(timezone=True), nullable=True)
# Event Metadata
event_id = Column(String(255), nullable=True, index=True) # POS system's event ID
event_timestamp = Column(DateTime(timezone=True), nullable=True) # When event occurred in POS
sequence_number = Column(Integer, nullable=True) # For ordered events
# Business Data References
transaction_id = Column(String(255), nullable=True, index=True) # Referenced transaction
order_id = Column(String(255), nullable=True, index=True) # Referenced order
customer_id = Column(String(255), nullable=True) # Referenced customer
# Internal References
created_transaction_id = Column(UUID(as_uuid=True), nullable=True) # Created POSTransaction record
updated_transaction_id = Column(UUID(as_uuid=True), nullable=True) # Updated POSTransaction record
# Duplicate Detection
is_duplicate = Column(Boolean, default=False, nullable=False, index=True)
duplicate_of = Column(UUID(as_uuid=True), nullable=True)
# Processing Priority
priority = Column(String(20), default="normal", nullable=False) # low, normal, high, urgent
# Debugging Information
user_agent = Column(String(500), nullable=True)
forwarded_for = Column(String(200), nullable=True) # X-Forwarded-For header
request_id = Column(String(100), nullable=True) # For request tracing
# Timestamps
received_at = Column(DateTime(timezone=True), server_default=func.now(), nullable=False, index=True)
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)
# Indexes for performance
__table_args__ = (
Index('idx_webhook_pos_system_type', 'pos_system', 'webhook_type'),
Index('idx_webhook_status', 'status'),
Index('idx_webhook_event_id', 'event_id'),
Index('idx_webhook_received_at', 'received_at'),
Index('idx_webhook_tenant_received', 'tenant_id', 'received_at'),
Index('idx_webhook_transaction_id', 'transaction_id'),
Index('idx_webhook_order_id', 'order_id'),
Index('idx_webhook_duplicate', 'is_duplicate'),
Index('idx_webhook_priority', 'priority'),
Index('idx_webhook_retry', 'retry_count'),
Index('idx_webhook_signature_valid', 'is_signature_valid'),
)
def __repr__(self):
return f"<POSWebhookLog(id={self.id}, pos_system='{self.pos_system}', type='{self.webhook_type}', status='{self.status}')>"