Initial commit - production deployment

This commit is contained in:
2026-01-21 17:17:16 +01:00
commit c23d00dd92
2289 changed files with 638440 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
# services/suppliers/app/models/__init__.py
"""
Models package for the Supplier service
"""
# Import AuditLog model for this service
from shared.security import create_audit_log_model
from shared.database.base import Base
# Create audit log model for this service
AuditLog = create_audit_log_model(Base)
from .suppliers import (
Supplier, SupplierPriceList, SupplierQualityReview,
SupplierType, SupplierStatus, PaymentTerms, QualityRating,
# Deprecated stubs for backward compatibility
PurchaseOrder, PurchaseOrderItem, Delivery, DeliveryItem, SupplierInvoice,
PurchaseOrderStatus, DeliveryStatus, DeliveryRating, InvoiceStatus
)
from .performance import (
SupplierPerformanceMetric, SupplierAlert, SupplierScorecard,
SupplierBenchmark, AlertRule, AlertSeverity, AlertType, AlertStatus,
PerformanceMetricType, PerformancePeriod
)
__all__ = [
# Supplier Models
'Supplier',
'SupplierPriceList',
'SupplierQualityReview',
# Performance Models
'SupplierPerformanceMetric',
'SupplierAlert',
'SupplierScorecard',
'SupplierBenchmark',
'AlertRule',
# Supplier Enums
'SupplierType',
'SupplierStatus',
'PaymentTerms',
'QualityRating',
# Performance Enums
'AlertSeverity',
'AlertType',
'AlertStatus',
'PerformanceMetricType',
'PerformancePeriod',
"AuditLog",
# Deprecated stubs (backward compatibility only - DO NOT USE)
'PurchaseOrder',
'PurchaseOrderItem',
'Delivery',
'DeliveryItem',
'SupplierInvoice',
'PurchaseOrderStatus',
'DeliveryStatus',
'DeliveryRating',
'InvoiceStatus',
]

View File

@@ -0,0 +1,392 @@
# ================================================================
# services/suppliers/app/models/performance.py
# ================================================================
"""
Supplier Performance Tracking and Alert Models for Suppliers Service
Comprehensive supplier performance metrics, KPIs, and alert management
"""
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 AlertSeverity(enum.Enum):
"""Alert severity levels"""
CRITICAL = "critical"
HIGH = "high"
MEDIUM = "medium"
LOW = "low"
INFO = "info"
class AlertType(enum.Enum):
"""Types of supplier alerts"""
POOR_QUALITY = "poor_quality"
LATE_DELIVERY = "late_delivery"
PRICE_INCREASE = "price_increase"
LOW_PERFORMANCE = "low_performance"
CONTRACT_EXPIRY = "contract_expiry"
COMPLIANCE_ISSUE = "compliance_issue"
FINANCIAL_RISK = "financial_risk"
COMMUNICATION_ISSUE = "communication_issue"
CAPACITY_CONSTRAINT = "capacity_constraint"
CERTIFICATION_EXPIRY = "certification_expiry"
class AlertStatus(enum.Enum):
"""Alert processing status"""
ACTIVE = "active"
ACKNOWLEDGED = "acknowledged"
IN_PROGRESS = "in_progress"
RESOLVED = "resolved"
DISMISSED = "dismissed"
class PerformanceMetricType(enum.Enum):
"""Types of performance metrics"""
DELIVERY_PERFORMANCE = "delivery_performance"
QUALITY_SCORE = "quality_score"
PRICE_COMPETITIVENESS = "price_competitiveness"
COMMUNICATION_RATING = "communication_rating"
ORDER_ACCURACY = "order_accuracy"
RESPONSE_TIME = "response_time"
COMPLIANCE_SCORE = "compliance_score"
FINANCIAL_STABILITY = "financial_stability"
class PerformancePeriod(enum.Enum):
"""Performance measurement periods"""
DAILY = "daily"
WEEKLY = "weekly"
MONTHLY = "monthly"
QUARTERLY = "quarterly"
YEARLY = "yearly"
class SupplierPerformanceMetric(Base):
"""Supplier performance metrics tracking"""
__tablename__ = "supplier_performance_metrics"
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)
# Metric details
metric_type = Column(SQLEnum(PerformanceMetricType), nullable=False, index=True)
period = Column(SQLEnum(PerformancePeriod), nullable=False, index=True)
period_start = Column(DateTime(timezone=True), nullable=False, index=True)
period_end = Column(DateTime(timezone=True), nullable=False, index=True)
# Performance values
metric_value = Column(Float, nullable=False) # Main metric value (0-100 scale)
target_value = Column(Float, nullable=True) # Target/benchmark value
previous_value = Column(Float, nullable=True) # Previous period value for comparison
# Supporting data
total_orders = Column(Integer, nullable=False, default=0)
total_deliveries = Column(Integer, nullable=False, default=0)
on_time_deliveries = Column(Integer, nullable=False, default=0)
late_deliveries = Column(Integer, nullable=False, default=0)
quality_issues = Column(Integer, nullable=False, default=0)
total_amount = Column(Numeric(12, 2), nullable=False, default=0.0)
# Detailed metrics breakdown
metrics_data = Column(JSONB, nullable=True) # Detailed breakdown of calculations
# Performance trends
trend_direction = Column(String(20), nullable=True) # improving, declining, stable
trend_percentage = Column(Float, nullable=True) # % change from previous period
# Contextual information
notes = Column(Text, nullable=True)
external_factors = Column(JSONB, nullable=True) # External factors affecting performance
# Audit fields
calculated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
calculated_by = Column(UUID(as_uuid=True), nullable=True) # System or user ID
# Relationships
supplier = relationship("Supplier")
# Indexes
__table_args__ = (
Index('ix_performance_metrics_tenant_supplier', 'tenant_id', 'supplier_id'),
Index('ix_performance_metrics_type_period', 'metric_type', 'period'),
Index('ix_performance_metrics_period_dates', 'period_start', 'period_end'),
Index('ix_performance_metrics_value', 'metric_value'),
)
class SupplierAlert(Base):
"""Supplier-related alerts and notifications"""
__tablename__ = "supplier_alerts"
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)
# Alert classification
alert_type = Column(SQLEnum(AlertType), nullable=False, index=True)
severity = Column(SQLEnum(AlertSeverity), nullable=False, index=True)
status = Column(SQLEnum(AlertStatus), nullable=False, default=AlertStatus.ACTIVE, index=True)
# Alert content
title = Column(String(255), nullable=False)
message = Column(Text, nullable=False)
description = Column(Text, nullable=True)
# Alert triggers and context
trigger_value = Column(Float, nullable=True) # The value that triggered the alert
threshold_value = Column(Float, nullable=True) # The threshold that was exceeded
metric_type = Column(SQLEnum(PerformanceMetricType), nullable=True, index=True)
# Related entities
purchase_order_id = Column(UUID(as_uuid=True), nullable=True, index=True)
delivery_id = Column(UUID(as_uuid=True), nullable=True, index=True)
performance_metric_id = Column(UUID(as_uuid=True), ForeignKey('supplier_performance_metrics.id'), nullable=True)
# Alert lifecycle
triggered_at = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc))
acknowledged_at = Column(DateTime(timezone=True), nullable=True)
acknowledged_by = Column(UUID(as_uuid=True), nullable=True)
resolved_at = Column(DateTime(timezone=True), nullable=True)
resolved_by = Column(UUID(as_uuid=True), nullable=True)
# Actions and resolution
recommended_actions = Column(JSONB, nullable=True) # Suggested actions
actions_taken = Column(JSONB, nullable=True) # Actions that were taken
resolution_notes = Column(Text, nullable=True)
# Auto-resolution
auto_resolve = Column(Boolean, nullable=False, default=False)
auto_resolve_condition = Column(JSONB, nullable=True) # Conditions for auto-resolution
# Escalation
escalated = Column(Boolean, nullable=False, default=False)
escalated_at = Column(DateTime(timezone=True), nullable=True)
escalated_to = Column(UUID(as_uuid=True), nullable=True) # User/role escalated to
# Notification tracking
notification_sent = Column(Boolean, nullable=False, default=False)
notification_sent_at = Column(DateTime(timezone=True), nullable=True)
notification_recipients = Column(JSONB, nullable=True) # List of recipients
# Additional metadata
priority_score = Column(Integer, nullable=False, default=50) # 1-100 priority scoring
business_impact = Column(String(50), nullable=True) # high, medium, low impact
tags = Column(JSONB, nullable=True) # Categorization tags
# 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=True)
# Relationships
supplier = relationship("Supplier")
performance_metric = relationship("SupplierPerformanceMetric")
# Indexes
__table_args__ = (
Index('ix_supplier_alerts_tenant_supplier', 'tenant_id', 'supplier_id'),
Index('ix_supplier_alerts_type_severity', 'alert_type', 'severity'),
Index('ix_supplier_alerts_status_triggered', 'status', 'triggered_at'),
Index('ix_supplier_alerts_metric_type', 'metric_type'),
Index('ix_supplier_alerts_priority', 'priority_score'),
)
class SupplierScorecard(Base):
"""Comprehensive supplier scorecards for performance evaluation"""
__tablename__ = "supplier_scorecards"
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)
# Scorecard details
scorecard_name = Column(String(255), nullable=False)
period = Column(SQLEnum(PerformancePeriod), nullable=False, index=True)
period_start = Column(DateTime(timezone=True), nullable=False, index=True)
period_end = Column(DateTime(timezone=True), nullable=False, index=True)
# Overall performance scores
overall_score = Column(Float, nullable=False) # Weighted overall score (0-100)
quality_score = Column(Float, nullable=False) # Quality performance (0-100)
delivery_score = Column(Float, nullable=False) # Delivery performance (0-100)
cost_score = Column(Float, nullable=False) # Cost competitiveness (0-100)
service_score = Column(Float, nullable=False) # Service quality (0-100)
# Performance rankings
overall_rank = Column(Integer, nullable=True) # Rank among all suppliers
category_rank = Column(Integer, nullable=True) # Rank within supplier category
total_suppliers_evaluated = Column(Integer, nullable=True)
# Detailed performance breakdown
on_time_delivery_rate = Column(Float, nullable=False) # % of on-time deliveries
quality_rejection_rate = Column(Float, nullable=False) # % of quality rejections
order_accuracy_rate = Column(Float, nullable=False) # % of accurate orders
response_time_hours = Column(Float, nullable=False) # Average response time
cost_variance_percentage = Column(Float, nullable=False) # Cost variance from budget
# Business metrics
total_orders_processed = Column(Integer, nullable=False, default=0)
total_amount_processed = Column(Numeric(12, 2), nullable=False, default=0.0)
average_order_value = Column(Numeric(10, 2), nullable=False, default=0.0)
cost_savings_achieved = Column(Numeric(10, 2), nullable=False, default=0.0)
# Performance trends
score_trend = Column(String(20), nullable=True) # improving, declining, stable
score_change_percentage = Column(Float, nullable=True) # % change from previous period
# Recommendations and actions
strengths = Column(JSONB, nullable=True) # List of strengths
improvement_areas = Column(JSONB, nullable=True) # Areas for improvement
recommended_actions = Column(JSONB, nullable=True) # Recommended actions
# Scorecard status
is_final = Column(Boolean, nullable=False, default=False)
approved_by = Column(UUID(as_uuid=True), nullable=True)
approved_at = Column(DateTime(timezone=True), nullable=True)
# Additional information
notes = Column(Text, nullable=True)
attachments = Column(JSONB, nullable=True) # Supporting documents
# Audit fields
generated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc))
generated_by = Column(UUID(as_uuid=True), nullable=False)
# Relationships
supplier = relationship("Supplier")
# Indexes
__table_args__ = (
Index('ix_scorecards_tenant_supplier', 'tenant_id', 'supplier_id'),
Index('ix_scorecards_period_dates', 'period_start', 'period_end'),
Index('ix_scorecards_overall_score', 'overall_score'),
Index('ix_scorecards_period', 'period'),
Index('ix_scorecards_final', 'is_final'),
)
class SupplierBenchmark(Base):
"""Supplier performance benchmarks and industry standards"""
__tablename__ = "supplier_benchmarks"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
# Benchmark details
benchmark_name = Column(String(255), nullable=False)
benchmark_type = Column(String(50), nullable=False, index=True) # industry, internal, custom
supplier_category = Column(String(100), nullable=True, index=True) # Target supplier category
# Metric thresholds
metric_type = Column(SQLEnum(PerformanceMetricType), nullable=False, index=True)
excellent_threshold = Column(Float, nullable=False) # Excellent performance threshold
good_threshold = Column(Float, nullable=False) # Good performance threshold
acceptable_threshold = Column(Float, nullable=False) # Acceptable performance threshold
poor_threshold = Column(Float, nullable=False) # Poor performance threshold
# Benchmark context
data_source = Column(String(255), nullable=True) # Source of benchmark data
sample_size = Column(Integer, nullable=True) # Sample size for benchmark
confidence_level = Column(Float, nullable=True) # Statistical confidence level
# Validity and updates
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 information
description = Column(Text, nullable=True)
methodology = Column(Text, nullable=True)
notes = Column(Text, 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))
created_by = Column(UUID(as_uuid=True), nullable=False)
# Indexes
__table_args__ = (
Index('ix_benchmarks_tenant_type', 'tenant_id', 'benchmark_type'),
Index('ix_benchmarks_metric_type', 'metric_type'),
Index('ix_benchmarks_category', 'supplier_category'),
Index('ix_benchmarks_active', 'is_active'),
)
class AlertRule(Base):
"""Configurable alert rules for supplier performance monitoring"""
__tablename__ = "alert_rules"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), nullable=False, index=True)
# Rule identification
rule_name = Column(String(255), nullable=False)
rule_description = Column(Text, nullable=True)
is_active = Column(Boolean, nullable=False, default=True)
# Alert configuration
alert_type = Column(SQLEnum(AlertType), nullable=False, index=True)
severity = Column(SQLEnum(AlertSeverity), nullable=False)
metric_type = Column(SQLEnum(PerformanceMetricType), nullable=True, index=True)
# Trigger conditions
trigger_condition = Column(String(50), nullable=False) # greater_than, less_than, equals, etc.
threshold_value = Column(Float, nullable=False)
consecutive_violations = Column(Integer, nullable=False, default=1) # How many consecutive violations before alert
# Scope and filters
supplier_categories = Column(JSONB, nullable=True) # Which supplier categories this applies to
supplier_ids = Column(JSONB, nullable=True) # Specific suppliers (if applicable)
exclude_suppliers = Column(JSONB, nullable=True) # Suppliers to exclude
# Time constraints
evaluation_period = Column(SQLEnum(PerformancePeriod), nullable=False)
time_window_hours = Column(Integer, nullable=True) # Time window for evaluation
business_hours_only = Column(Boolean, nullable=False, default=False)
# Auto-resolution
auto_resolve = Column(Boolean, nullable=False, default=False)
auto_resolve_threshold = Column(Float, nullable=True) # Value at which alert auto-resolves
auto_resolve_duration_hours = Column(Integer, nullable=True) # How long condition must be met
# Notification settings
notification_enabled = Column(Boolean, nullable=False, default=True)
notification_recipients = Column(JSONB, nullable=True) # List of recipients
escalation_minutes = Column(Integer, nullable=True) # Minutes before escalation
escalation_recipients = Column(JSONB, nullable=True) # Escalation recipients
# Action triggers
recommended_actions = Column(JSONB, nullable=True) # Actions to recommend
auto_actions = Column(JSONB, nullable=True) # Actions to automatically trigger
# Rule metadata
priority = Column(Integer, nullable=False, default=50) # Rule priority (1-100)
tags = Column(JSONB, nullable=True) # Classification tags
# 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)
last_triggered = Column(DateTime(timezone=True), nullable=True)
trigger_count = Column(Integer, nullable=False, default=0)
# Indexes
__table_args__ = (
Index('ix_alert_rules_tenant_active', 'tenant_id', 'is_active'),
Index('ix_alert_rules_type_severity', 'alert_type', 'severity'),
Index('ix_alert_rules_metric_type', 'metric_type'),
Index('ix_alert_rules_priority', 'priority'),
)

View File

@@ -0,0 +1,333 @@
# 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