Files
bakery-ia/services/pos/app/schemas/pos_transaction.py
2025-10-23 07:44:54 +02:00

249 lines
8.1 KiB
Python

"""
Pydantic schemas for POS transaction API requests and responses
"""
from typing import Optional, List, Dict, Any
from datetime import datetime
from decimal import Decimal
from pydantic import BaseModel, Field
from enum import Enum
class TransactionType(str, Enum):
"""Transaction type enumeration"""
SALE = "sale"
REFUND = "refund"
VOID = "void"
EXCHANGE = "exchange"
class TransactionStatus(str, Enum):
"""Transaction status enumeration"""
COMPLETED = "completed"
PENDING = "pending"
FAILED = "failed"
REFUNDED = "refunded"
VOIDED = "voided"
class PaymentMethod(str, Enum):
"""Payment method enumeration"""
CARD = "card"
CASH = "cash"
DIGITAL_WALLET = "digital_wallet"
OTHER = "other"
class OrderType(str, Enum):
"""Order type enumeration"""
DINE_IN = "dine_in"
TAKEOUT = "takeout"
DELIVERY = "delivery"
PICKUP = "pickup"
class POSTransactionItemResponse(BaseModel):
"""Schema for POS transaction item response"""
id: str
transaction_id: str
tenant_id: str
external_item_id: Optional[str] = None
sku: Optional[str] = None
product_name: str
product_category: Optional[str] = None
product_subcategory: Optional[str] = None
quantity: Decimal
unit_price: Decimal
total_price: Decimal
discount_amount: Decimal = Decimal("0")
tax_amount: Decimal = Decimal("0")
modifiers: Optional[Dict[str, Any]] = None
inventory_product_id: Optional[str] = None
is_mapped_to_inventory: bool = False
is_synced_to_sales: bool = False
created_at: datetime
updated_at: datetime
class Config:
from_attributes = True
use_enum_values = True
json_encoders = {
datetime: lambda v: v.isoformat() if v else None,
Decimal: lambda v: float(v) if v else 0.0
}
@classmethod
def from_orm(cls, obj):
"""Convert ORM object to schema with proper UUID and Decimal handling"""
return cls(
id=str(obj.id),
transaction_id=str(obj.transaction_id),
tenant_id=str(obj.tenant_id),
external_item_id=obj.external_item_id,
sku=obj.sku,
product_name=obj.product_name,
product_category=obj.product_category,
product_subcategory=obj.product_subcategory,
quantity=obj.quantity,
unit_price=obj.unit_price,
total_price=obj.total_price,
discount_amount=obj.discount_amount,
tax_amount=obj.tax_amount,
modifiers=obj.modifiers,
inventory_product_id=str(obj.inventory_product_id) if obj.inventory_product_id else None,
is_mapped_to_inventory=obj.is_mapped_to_inventory,
is_synced_to_sales=obj.is_synced_to_sales,
created_at=obj.created_at,
updated_at=obj.updated_at
)
class POSTransactionResponse(BaseModel):
"""Schema for POS transaction response"""
id: str
tenant_id: str
pos_config_id: str
pos_system: str
external_transaction_id: str
external_order_id: Optional[str] = None
transaction_type: TransactionType
status: TransactionStatus
subtotal: Decimal
tax_amount: Decimal
tip_amount: Decimal
discount_amount: Decimal
total_amount: Decimal
currency: str = "EUR"
payment_method: Optional[PaymentMethod] = None
payment_status: Optional[str] = None
transaction_date: datetime
pos_created_at: datetime
pos_updated_at: Optional[datetime] = None
location_id: Optional[str] = None
location_name: Optional[str] = None
staff_id: Optional[str] = None
staff_name: Optional[str] = None
customer_id: Optional[str] = None
customer_email: Optional[str] = None
customer_phone: Optional[str] = None
order_type: Optional[OrderType] = None
table_number: Optional[str] = None
receipt_number: Optional[str] = None
is_synced_to_sales: bool = False
sales_record_id: Optional[str] = None
sync_attempted_at: Optional[datetime] = None
sync_completed_at: Optional[datetime] = None
sync_error: Optional[str] = None
sync_retry_count: int = 0
is_processed: bool = False
is_duplicate: bool = False
created_at: datetime
updated_at: datetime
items: List[POSTransactionItemResponse] = []
class Config:
from_attributes = True
use_enum_values = True
json_encoders = {
datetime: lambda v: v.isoformat() if v else None,
Decimal: lambda v: float(v) if v else 0.0
}
@classmethod
def from_orm(cls, obj):
"""Convert ORM object to schema with proper UUID and Decimal handling"""
return cls(
id=str(obj.id),
tenant_id=str(obj.tenant_id),
pos_config_id=str(obj.pos_config_id),
pos_system=obj.pos_system,
external_transaction_id=obj.external_transaction_id,
external_order_id=obj.external_order_id,
transaction_type=obj.transaction_type,
status=obj.status,
subtotal=obj.subtotal,
tax_amount=obj.tax_amount,
tip_amount=obj.tip_amount,
discount_amount=obj.discount_amount,
total_amount=obj.total_amount,
currency=obj.currency,
payment_method=obj.payment_method,
payment_status=obj.payment_status,
transaction_date=obj.transaction_date,
pos_created_at=obj.pos_created_at,
pos_updated_at=obj.pos_updated_at,
location_id=obj.location_id,
location_name=obj.location_name,
staff_id=obj.staff_id,
staff_name=obj.staff_name,
customer_id=obj.customer_id,
customer_email=obj.customer_email,
customer_phone=obj.customer_phone,
order_type=obj.order_type,
table_number=obj.table_number,
receipt_number=obj.receipt_number,
is_synced_to_sales=obj.is_synced_to_sales,
sales_record_id=str(obj.sales_record_id) if obj.sales_record_id else None,
sync_attempted_at=obj.sync_attempted_at,
sync_completed_at=obj.sync_completed_at,
sync_error=obj.sync_error,
sync_retry_count=obj.sync_retry_count,
is_processed=obj.is_processed,
is_duplicate=obj.is_duplicate,
created_at=obj.created_at,
updated_at=obj.updated_at,
items=[POSTransactionItemResponse.from_orm(item) for item in obj.items] if hasattr(obj, 'items') and obj.items else []
)
class POSTransactionSummary(BaseModel):
"""Summary information for a transaction (lightweight)"""
id: str
external_transaction_id: str
transaction_date: datetime
total_amount: Decimal
status: TransactionStatus
payment_method: Optional[PaymentMethod] = None
is_synced_to_sales: bool
item_count: int = 0
class Config:
from_attributes = True
use_enum_values = True
json_encoders = {
datetime: lambda v: v.isoformat() if v else None,
Decimal: lambda v: float(v) if v else 0.0
}
class POSTransactionListResponse(BaseModel):
"""Schema for paginated transaction list response"""
transactions: List[POSTransactionResponse]
total: int
has_more: bool = False
summary: Optional[Dict[str, Any]] = None
class Config:
from_attributes = True
class POSTransactionDashboardSummary(BaseModel):
"""Dashboard summary for POS transactions"""
total_transactions_today: int = 0
total_transactions_this_week: int = 0
total_transactions_this_month: int = 0
revenue_today: Decimal = Decimal("0")
revenue_this_week: Decimal = Decimal("0")
revenue_this_month: Decimal = Decimal("0")
average_transaction_value: Decimal = Decimal("0")
status_breakdown: Dict[str, int] = {}
payment_method_breakdown: Dict[str, int] = {}
sync_status: Dict[str, Any] = {}
class Config:
from_attributes = True
json_encoders = {
Decimal: lambda v: float(v) if v else 0.0,
datetime: lambda v: v.isoformat() if v else None
}