Create new services: inventory, recipes, suppliers

This commit is contained in:
Urtzi Alfaro
2025-08-13 17:39:35 +02:00
parent fbe7470ad9
commit 16b8a9d50c
151 changed files with 35799 additions and 857 deletions

View File

@@ -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"]

View File

@@ -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):