# 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