Initial commit - production deployment
This commit is contained in:
64
services/suppliers/app/models/__init__.py
Normal file
64
services/suppliers/app/models/__init__.py
Normal 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',
|
||||
]
|
||||
392
services/suppliers/app/models/performance.py
Normal file
392
services/suppliers/app/models/performance.py
Normal 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'),
|
||||
)
|
||||
333
services/suppliers/app/models/suppliers.py
Normal file
333
services/suppliers/app/models/suppliers.py
Normal 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
|
||||
Reference in New Issue
Block a user