Files
bakery-ia/services/suppliers/app/models/suppliers.py
2025-10-30 21:08:07 +01:00

334 lines
13 KiB
Python

# services/suppliers/app/models/suppliers.py
"""
Supplier management models for Suppliers Service
Comprehensive supplier management and vendor relationships
NOTE: Purchase orders, deliveries, and invoices have been moved to Procurement Service
"""
from sqlalchemy import Column, String, DateTime, Float, Integer, Text, Index, Boolean, Numeric, ForeignKey, Enum as SQLEnum
from sqlalchemy.dialects.postgresql import UUID, JSONB
from sqlalchemy.orm import relationship
import uuid
import enum
from datetime import datetime, timezone
from typing import Dict, Any, Optional, List
from decimal import Decimal
from shared.database.base import Base
class SupplierType(enum.Enum):
"""Types of suppliers"""
ingredients = "ingredients" # Raw materials supplier
packaging = "packaging" # Packaging materials
equipment = "equipment" # Bakery equipment
services = "services" # Service providers
utilities = "utilities" # Utilities (gas, electricity)
multi = "multi" # Multi-category supplier
class SupplierStatus(enum.Enum):
"""Supplier lifecycle status"""
active = "active"
inactive = "inactive"
pending_approval = "pending_approval"
suspended = "suspended"
blacklisted = "blacklisted"
class PaymentTerms(enum.Enum):
"""Payment terms with suppliers"""
cod = "cod"
net_15 = "net_15"
net_30 = "net_30"
net_45 = "net_45"
net_60 = "net_60"
prepaid = "prepaid"
credit_terms = "credit_terms"
class QualityRating(enum.Enum):
"""Quality rating scale for supplier reviews"""
excellent = 5
good = 4
average = 3
poor = 2
very_poor = 1
# ============================================================================
# DEPRECATED ENUMS - Kept for backward compatibility only
# These enums are defined here to prevent import errors, but the actual
# tables and functionality have moved to the Procurement Service
# ============================================================================
class PurchaseOrderStatus(enum.Enum):
"""DEPRECATED: Moved to Procurement Service"""
draft = "draft"
pending_approval = "pending_approval"
approved = "approved"
sent_to_supplier = "sent_to_supplier"
confirmed = "confirmed"
partially_received = "partially_received"
completed = "completed"
cancelled = "cancelled"
disputed = "disputed"
class DeliveryStatus(enum.Enum):
"""DEPRECATED: Moved to Procurement Service"""
scheduled = "scheduled"
in_transit = "in_transit"
out_for_delivery = "out_for_delivery"
delivered = "delivered"
partially_delivered = "partially_delivered"
failed_delivery = "failed_delivery"
returned = "returned"
class DeliveryRating(enum.Enum):
"""DEPRECATED: Moved to Procurement Service"""
excellent = 5
good = 4
average = 3
poor = 2
very_poor = 1
class InvoiceStatus(enum.Enum):
"""DEPRECATED: Moved to Procurement Service"""
pending = "pending"
approved = "approved"
paid = "paid"
overdue = "overdue"
disputed = "disputed"
cancelled = "cancelled"
class Supplier(Base):
"""Master supplier information"""
__tablename__ = "suppliers"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
# Basic supplier information
name = Column(String(255), nullable=False, index=True)
supplier_code = Column(String(50), nullable=True, index=True) # Internal reference code
tax_id = Column(String(50), nullable=True) # VAT/Tax ID
registration_number = Column(String(100), nullable=True) # Business registration number
# Supplier classification
supplier_type = Column(SQLEnum(SupplierType), nullable=False, index=True)
status = Column(SQLEnum(SupplierStatus), nullable=False, default=SupplierStatus.pending_approval, index=True)
# Contact information
contact_person = Column(String(200), nullable=True)
email = Column(String(254), nullable=True)
phone = Column(String(30), nullable=True)
mobile = Column(String(30), nullable=True)
website = Column(String(255), nullable=True)
# Address information
address_line1 = Column(String(255), nullable=True)
address_line2 = Column(String(255), nullable=True)
city = Column(String(100), nullable=True)
state_province = Column(String(100), nullable=True)
postal_code = Column(String(20), nullable=True)
country = Column(String(100), nullable=True)
# Business terms
payment_terms = Column(SQLEnum(PaymentTerms), nullable=False, default=PaymentTerms.net_30)
credit_limit = Column(Numeric(12, 2), nullable=True)
currency = Column(String(3), nullable=False, default="EUR") # ISO currency code
# Lead times (in days)
standard_lead_time = Column(Integer, nullable=False, default=3)
minimum_order_amount = Column(Numeric(10, 2), nullable=True)
delivery_area = Column(String(255), nullable=True)
# Quality and performance metrics
quality_rating = Column(Float, nullable=True, default=0.0) # Average quality rating (1-5)
delivery_rating = Column(Float, nullable=True, default=0.0) # Average delivery rating (1-5)
total_orders = Column(Integer, nullable=False, default=0)
total_amount = Column(Numeric(12, 2), nullable=False, default=0.0)
# Trust and auto-approval metrics
trust_score = Column(Float, nullable=False, default=0.0) # Calculated trust score (0.0-1.0)
is_preferred_supplier = Column(Boolean, nullable=False, default=False) # Preferred supplier status
auto_approve_enabled = Column(Boolean, nullable=False, default=False) # Enable auto-approval for this supplier
total_pos_count = Column(Integer, nullable=False, default=0) # Total purchase orders created
approved_pos_count = Column(Integer, nullable=False, default=0) # Total POs approved
on_time_delivery_rate = Column(Float, nullable=False, default=0.0) # Percentage of on-time deliveries
fulfillment_rate = Column(Float, nullable=False, default=0.0) # Percentage of orders fully fulfilled
last_performance_update = Column(DateTime(timezone=True), nullable=True) # Last time metrics were calculated
# Onboarding and approval
approved_by = Column(UUID(as_uuid=True), nullable=True) # User who approved
approved_at = Column(DateTime(timezone=True), nullable=True)
rejection_reason = Column(Text, nullable=True)
# Additional information
notes = Column(Text, nullable=True)
certifications = Column(JSONB, nullable=True) # Quality certifications, licenses
business_hours = Column(JSONB, nullable=True) # Operating hours by day
specializations = Column(JSONB, nullable=True) # Product categories, special services
# 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))
created_by = Column(UUID(as_uuid=True), nullable=False)
updated_by = Column(UUID(as_uuid=True), nullable=False)
# Relationships
price_lists = relationship("SupplierPriceList", back_populates="supplier", cascade="all, delete-orphan")
quality_reviews = relationship("SupplierQualityReview", back_populates="supplier", cascade="all, delete-orphan")
# Indexes
__table_args__ = (
Index('ix_suppliers_tenant_name', 'tenant_id', 'name'),
Index('ix_suppliers_tenant_status', 'tenant_id', 'status'),
Index('ix_suppliers_tenant_type', 'tenant_id', 'supplier_type'),
Index('ix_suppliers_quality_rating', 'quality_rating'),
)
class SupplierPriceList(Base):
"""Product pricing from suppliers"""
__tablename__ = "supplier_price_lists"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
supplier_id = Column(UUID(as_uuid=True), ForeignKey('suppliers.id'), nullable=False, index=True)
# Product identification (references inventory service)
inventory_product_id = Column(UUID(as_uuid=True), nullable=False, index=True) # Reference to inventory products
product_code = Column(String(100), nullable=True) # Supplier's product code
# Pricing information
unit_price = Column(Numeric(10, 4), nullable=False)
unit_of_measure = Column(String(20), nullable=False) # kg, g, l, ml, units, etc.
minimum_order_quantity = Column(Integer, nullable=True, default=1)
price_per_unit = Column(Numeric(10, 4), nullable=False) # Calculated field
# Pricing tiers (volume discounts)
tier_pricing = Column(JSONB, nullable=True) # [{quantity: 100, price: 2.50}, ...]
# Validity and terms
effective_date = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc))
expiry_date = Column(DateTime(timezone=True), nullable=True)
is_active = Column(Boolean, nullable=False, default=True)
# Additional product details
brand = Column(String(100), nullable=True)
packaging_size = Column(String(50), nullable=True)
origin_country = Column(String(100), nullable=True)
shelf_life_days = Column(Integer, nullable=True)
storage_requirements = Column(Text, nullable=True)
# Quality specifications
quality_specs = Column(JSONB, nullable=True) # Quality parameters, certifications
allergens = Column(JSONB, nullable=True) # Allergen information
# 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))
created_by = Column(UUID(as_uuid=True), nullable=False)
updated_by = Column(UUID(as_uuid=True), nullable=False)
# Relationships
supplier = relationship("Supplier", back_populates="price_lists")
# Indexes
__table_args__ = (
Index('ix_price_lists_tenant_supplier', 'tenant_id', 'supplier_id'),
Index('ix_price_lists_inventory_product', 'inventory_product_id'),
Index('ix_price_lists_active', 'is_active'),
Index('ix_price_lists_effective_date', 'effective_date'),
)
class SupplierQualityReview(Base):
"""Quality and performance reviews for suppliers"""
__tablename__ = "supplier_quality_reviews"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
supplier_id = Column(UUID(as_uuid=True), ForeignKey('suppliers.id'), nullable=False, index=True)
# Review details
review_date = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc))
review_type = Column(String(50), nullable=False) # monthly, annual, incident
# Ratings (1-5 scale)
quality_rating = Column(SQLEnum(QualityRating), nullable=False)
delivery_rating = Column(Integer, nullable=False) # 1-5 scale
communication_rating = Column(Integer, nullable=False) # 1-5
overall_rating = Column(Float, nullable=False) # Calculated average
# Detailed feedback
quality_comments = Column(Text, nullable=True)
delivery_comments = Column(Text, nullable=True)
communication_comments = Column(Text, nullable=True)
improvement_suggestions = Column(Text, nullable=True)
# Issues and corrective actions
quality_issues = Column(JSONB, nullable=True) # Documented issues
corrective_actions = Column(Text, nullable=True)
follow_up_required = Column(Boolean, nullable=False, default=False)
follow_up_date = Column(DateTime(timezone=True), nullable=True)
# Review status
is_final = Column(Boolean, nullable=False, default=True)
approved_by = Column(UUID(as_uuid=True), nullable=True)
# Audit fields
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
reviewed_by = Column(UUID(as_uuid=True), nullable=False)
# Relationships
supplier = relationship("Supplier", back_populates="quality_reviews")
# Indexes
__table_args__ = (
Index('ix_quality_reviews_tenant_supplier', 'tenant_id', 'supplier_id'),
Index('ix_quality_reviews_date', 'review_date'),
Index('ix_quality_reviews_overall_rating', 'overall_rating'),
)
# ============================================================================
# DEPRECATED MODELS - Stub definitions for backward compatibility
# These models are defined here ONLY to prevent import errors
# The actual tables exist in the Procurement Service database, NOT here
# __table__ = None prevents SQLAlchemy from creating these tables
# ============================================================================
class PurchaseOrder:
"""DEPRECATED STUB: Actual implementation in Procurement Service"""
__table__ = None # Prevent table creation
pass
class PurchaseOrderItem:
"""DEPRECATED STUB: Actual implementation in Procurement Service"""
__table__ = None # Prevent table creation
pass
class Delivery:
"""DEPRECATED STUB: Actual implementation in Procurement Service"""
__table__ = None # Prevent table creation
pass
class DeliveryItem:
"""DEPRECATED STUB: Actual implementation in Procurement Service"""
__table__ = None # Prevent table creation
pass
class SupplierInvoice:
"""DEPRECATED STUB: Actual implementation in Procurement Service"""
__table__ = None # Prevent table creation
pass