Create new services: inventory, recipes, suppliers
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
# services/sales/app/models/__init__.py
|
||||
|
||||
from .sales import SalesData, Product, SalesImportJob
|
||||
from .sales import SalesData, SalesImportJob
|
||||
|
||||
__all__ = ["SalesData", "Product", "SalesImportJob"]
|
||||
__all__ = ["SalesData", "SalesImportJob"]
|
||||
@@ -4,7 +4,7 @@ Sales data models for Sales Service
|
||||
Enhanced with additional fields and relationships
|
||||
"""
|
||||
|
||||
from sqlalchemy import Column, String, DateTime, Float, Integer, Text, Index, Boolean, Numeric
|
||||
from sqlalchemy import Column, String, DateTime, Float, Integer, Text, Index, Boolean, Numeric, ForeignKey
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import relationship
|
||||
import uuid
|
||||
@@ -22,10 +22,8 @@ class SalesData(Base):
|
||||
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
date = Column(DateTime(timezone=True), nullable=False, index=True)
|
||||
|
||||
# Product information
|
||||
product_name = Column(String(255), nullable=False, index=True)
|
||||
product_category = Column(String(100), nullable=True, index=True)
|
||||
product_sku = Column(String(100), nullable=True, index=True)
|
||||
# Product reference to inventory service (REQUIRED)
|
||||
inventory_product_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Reference to inventory.ingredients.id
|
||||
|
||||
# Sales data
|
||||
quantity_sold = Column(Integer, nullable=False)
|
||||
@@ -60,18 +58,17 @@ class SalesData(Base):
|
||||
__table_args__ = (
|
||||
# Core query patterns
|
||||
Index('idx_sales_tenant_date', 'tenant_id', 'date'),
|
||||
Index('idx_sales_tenant_product', 'tenant_id', 'product_name'),
|
||||
Index('idx_sales_tenant_location', 'tenant_id', 'location_id'),
|
||||
Index('idx_sales_tenant_category', 'tenant_id', 'product_category'),
|
||||
|
||||
# Analytics queries
|
||||
Index('idx_sales_date_range', 'date', 'tenant_id'),
|
||||
Index('idx_sales_product_date', 'product_name', 'date', 'tenant_id'),
|
||||
Index('idx_sales_channel_date', 'sales_channel', 'date', 'tenant_id'),
|
||||
|
||||
# Data quality queries
|
||||
Index('idx_sales_source_validated', 'source', 'is_validated', 'tenant_id'),
|
||||
Index('idx_sales_sku_date', 'product_sku', 'date', 'tenant_id'),
|
||||
# Primary product reference index
|
||||
Index('idx_sales_inventory_product', 'inventory_product_id', 'tenant_id'),
|
||||
Index('idx_sales_product_date', 'inventory_product_id', 'date', 'tenant_id'),
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
@@ -80,9 +77,7 @@ class SalesData(Base):
|
||||
'id': str(self.id),
|
||||
'tenant_id': str(self.tenant_id),
|
||||
'date': self.date.isoformat() if self.date else None,
|
||||
'product_name': self.product_name,
|
||||
'product_category': self.product_category,
|
||||
'product_sku': self.product_sku,
|
||||
'inventory_product_id': str(self.inventory_product_id),
|
||||
'quantity_sold': self.quantity_sold,
|
||||
'unit_price': float(self.unit_price) if self.unit_price else None,
|
||||
'revenue': float(self.revenue) if self.revenue else None,
|
||||
@@ -110,70 +105,8 @@ class SalesData(Base):
|
||||
return None
|
||||
|
||||
|
||||
class Product(Base):
|
||||
"""Product catalog model - future expansion"""
|
||||
__tablename__ = "products"
|
||||
|
||||
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
|
||||
|
||||
# Product identification
|
||||
name = Column(String(255), nullable=False, index=True)
|
||||
sku = Column(String(100), nullable=True, index=True)
|
||||
category = Column(String(100), nullable=True, index=True)
|
||||
subcategory = Column(String(100), nullable=True)
|
||||
|
||||
# Product details
|
||||
description = Column(Text, nullable=True)
|
||||
unit_of_measure = Column(String(20), nullable=False, default="unit")
|
||||
weight = Column(Float, nullable=True) # in grams
|
||||
volume = Column(Float, nullable=True) # in ml
|
||||
|
||||
# Pricing
|
||||
base_price = Column(Numeric(10, 2), nullable=True)
|
||||
cost_price = Column(Numeric(10, 2), nullable=True)
|
||||
|
||||
# Status
|
||||
is_active = Column(Boolean, default=True)
|
||||
is_seasonal = Column(Boolean, default=False)
|
||||
seasonal_start = Column(DateTime(timezone=True), nullable=True)
|
||||
seasonal_end = Column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
# Audit fields
|
||||
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
|
||||
updated_at = Column(DateTime(timezone=True),
|
||||
default=lambda: datetime.now(timezone.utc),
|
||||
onupdate=lambda: datetime.now(timezone.utc))
|
||||
|
||||
__table_args__ = (
|
||||
Index('idx_products_tenant_name', 'tenant_id', 'name', unique=True),
|
||||
Index('idx_products_tenant_sku', 'tenant_id', 'sku'),
|
||||
Index('idx_products_category', 'tenant_id', 'category', 'is_active'),
|
||||
Index('idx_products_seasonal', 'is_seasonal', 'seasonal_start', 'seasonal_end'),
|
||||
)
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
"""Convert model to dictionary for API responses"""
|
||||
return {
|
||||
'id': str(self.id),
|
||||
'tenant_id': str(self.tenant_id),
|
||||
'name': self.name,
|
||||
'sku': self.sku,
|
||||
'category': self.category,
|
||||
'subcategory': self.subcategory,
|
||||
'description': self.description,
|
||||
'unit_of_measure': self.unit_of_measure,
|
||||
'weight': self.weight,
|
||||
'volume': self.volume,
|
||||
'base_price': float(self.base_price) if self.base_price else None,
|
||||
'cost_price': float(self.cost_price) if self.cost_price else None,
|
||||
'is_active': self.is_active,
|
||||
'is_seasonal': self.is_seasonal,
|
||||
'seasonal_start': self.seasonal_start.isoformat() if self.seasonal_start else None,
|
||||
'seasonal_end': self.seasonal_end.isoformat() if self.seasonal_end else None,
|
||||
'created_at': self.created_at.isoformat() if self.created_at else None,
|
||||
'updated_at': self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
# Product model removed - using inventory service as single source of truth
|
||||
# Product data is now referenced via inventory_product_id in SalesData model
|
||||
|
||||
|
||||
class SalesImportJob(Base):
|
||||
|
||||
Reference in New Issue
Block a user