Files
bakery-ia/services/tenant/app/models/tenant_settings.py
2025-11-18 07:17:17 +01:00

371 lines
14 KiB
Python

# services/tenant/app/models/tenant_settings.py
"""
Tenant Settings Model
Centralized configuration storage for all tenant-specific operational settings
"""
from sqlalchemy import Column, String, Boolean, DateTime, ForeignKey, JSON
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from datetime import datetime, timezone
import uuid
from shared.database.base import Base
class TenantSettings(Base):
"""
Centralized tenant settings model
Stores all operational configurations for a tenant across all services
"""
__tablename__ = "tenant_settings"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
tenant_id = Column(UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False, unique=True, index=True)
# Procurement & Auto-Approval Settings (Orders Service)
procurement_settings = Column(JSON, nullable=False, default=lambda: {
"auto_approve_enabled": True,
"auto_approve_threshold_eur": 500.0,
"auto_approve_min_supplier_score": 0.80,
"require_approval_new_suppliers": True,
"require_approval_critical_items": True,
"procurement_lead_time_days": 3,
"demand_forecast_days": 14,
"safety_stock_percentage": 20.0,
"po_approval_reminder_hours": 24,
"po_critical_escalation_hours": 12,
"use_reorder_rules": True,
"economic_rounding": True,
"respect_storage_limits": True,
"use_supplier_minimums": True,
"optimize_price_tiers": True
})
# Inventory Management Settings (Inventory Service)
inventory_settings = Column(JSON, nullable=False, default=lambda: {
"low_stock_threshold": 10,
"reorder_point": 20,
"reorder_quantity": 50,
"expiring_soon_days": 7,
"expiration_warning_days": 3,
"quality_score_threshold": 8.0,
"temperature_monitoring_enabled": True,
"refrigeration_temp_min": 1.0,
"refrigeration_temp_max": 4.0,
"freezer_temp_min": -20.0,
"freezer_temp_max": -15.0,
"room_temp_min": 18.0,
"room_temp_max": 25.0,
"temp_deviation_alert_minutes": 15,
"critical_temp_deviation_minutes": 5
})
# Production Settings (Production Service)
production_settings = Column(JSON, nullable=False, default=lambda: {
"planning_horizon_days": 7,
"minimum_batch_size": 1.0,
"maximum_batch_size": 100.0,
"production_buffer_percentage": 10.0,
"working_hours_per_day": 12,
"max_overtime_hours": 4,
"capacity_utilization_target": 0.85,
"capacity_warning_threshold": 0.95,
"quality_check_enabled": True,
"minimum_yield_percentage": 85.0,
"quality_score_threshold": 8.0,
"schedule_optimization_enabled": True,
"prep_time_buffer_minutes": 30,
"cleanup_time_buffer_minutes": 15,
"labor_cost_per_hour_eur": 15.0,
"overhead_cost_percentage": 20.0
})
# Supplier Settings (Suppliers Service)
supplier_settings = Column(JSON, nullable=False, default=lambda: {
"default_payment_terms_days": 30,
"default_delivery_days": 3,
"excellent_delivery_rate": 95.0,
"good_delivery_rate": 90.0,
"excellent_quality_rate": 98.0,
"good_quality_rate": 95.0,
"critical_delivery_delay_hours": 24,
"critical_quality_rejection_rate": 10.0,
"high_cost_variance_percentage": 15.0,
# Supplier Approval Workflow Settings
"require_supplier_approval": True,
"auto_approve_for_admin_owner": True,
"approval_required_roles": ["member", "viewer"]
})
# POS Integration Settings (POS Service)
pos_settings = Column(JSON, nullable=False, default=lambda: {
"sync_interval_minutes": 5,
"auto_sync_products": True,
"auto_sync_transactions": True
})
# Order & Business Rules Settings (Orders Service)
order_settings = Column(JSON, nullable=False, default=lambda: {
"max_discount_percentage": 50.0,
"default_delivery_window_hours": 48,
"dynamic_pricing_enabled": False,
"discount_enabled": True,
"delivery_tracking_enabled": True
})
# Replenishment Planning Settings (Orchestrator Service)
replenishment_settings = Column(JSON, nullable=False, default=lambda: {
"projection_horizon_days": 7,
"service_level": 0.95,
"buffer_days": 1,
"enable_auto_replenishment": True,
"min_order_quantity": 1.0,
"max_order_quantity": 1000.0,
"demand_forecast_days": 14
})
# Safety Stock Settings (Orchestrator Service)
safety_stock_settings = Column(JSON, nullable=False, default=lambda: {
"service_level": 0.95,
"method": "statistical",
"min_safety_stock": 0.0,
"max_safety_stock": 100.0,
"reorder_point_calculation": "safety_stock_plus_lead_time_demand"
})
# MOQ Aggregation Settings (Orchestrator Service)
moq_settings = Column(JSON, nullable=False, default=lambda: {
"consolidation_window_days": 7,
"allow_early_ordering": True,
"enable_batch_optimization": True,
"min_batch_size": 1.0,
"max_batch_size": 1000.0
})
# Supplier Selection Settings (Orchestrator Service)
supplier_selection_settings = Column(JSON, nullable=False, default=lambda: {
"price_weight": 0.40,
"lead_time_weight": 0.20,
"quality_weight": 0.20,
"reliability_weight": 0.20,
"diversification_threshold": 1000,
"max_single_percentage": 0.70,
"enable_supplier_score_optimization": True
})
# ML Insights Settings (AI Insights Service)
ml_insights_settings = Column(JSON, nullable=False, default=lambda: {
# Inventory ML (Safety Stock Optimization)
"inventory_lookback_days": 90,
"inventory_min_history_days": 30,
# Production ML (Yield Prediction)
"production_lookback_days": 90,
"production_min_history_runs": 30,
# Procurement ML (Supplier Analysis & Price Forecasting)
"supplier_analysis_lookback_days": 180,
"supplier_analysis_min_orders": 10,
"price_forecast_lookback_days": 180,
"price_forecast_horizon_days": 30,
# Forecasting ML (Dynamic Rules)
"rules_generation_lookback_days": 90,
"rules_generation_min_samples": 10,
# Global ML Settings
"enable_ml_insights": True,
"ml_insights_auto_trigger": False,
"ml_confidence_threshold": 0.80
})
# Notification Settings (Notification Service)
notification_settings = Column(JSON, nullable=False, default=lambda: {
# WhatsApp Configuration (Shared Account Model)
"whatsapp_enabled": False,
"whatsapp_phone_number_id": "", # Meta WhatsApp Phone Number ID (from shared master account)
"whatsapp_display_phone_number": "", # Display format for UI (e.g., "+34 612 345 678")
"whatsapp_default_language": "es",
# Email Configuration
"email_enabled": True,
"email_from_address": "",
"email_from_name": "",
"email_reply_to": "",
# Notification Preferences
"enable_po_notifications": True,
"enable_inventory_alerts": True,
"enable_production_alerts": True,
"enable_forecast_alerts": True,
# Notification Channels
"po_notification_channels": ["email"], # ["email", "whatsapp"]
"inventory_alert_channels": ["email"],
"production_alert_channels": ["email"],
"forecast_alert_channels": ["email"]
})
# Timestamps
created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False)
updated_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False)
# Relationships
tenant = relationship("Tenant", backref="settings")
def __repr__(self):
return f"<TenantSettings(tenant_id={self.tenant_id})>"
@staticmethod
def get_default_settings() -> dict:
"""
Get default settings for all categories
Returns a dictionary with default values for all setting categories
"""
return {
"procurement_settings": {
"auto_approve_enabled": True,
"auto_approve_threshold_eur": 500.0,
"auto_approve_min_supplier_score": 0.80,
"require_approval_new_suppliers": True,
"require_approval_critical_items": True,
"procurement_lead_time_days": 3,
"demand_forecast_days": 14,
"safety_stock_percentage": 20.0,
"po_approval_reminder_hours": 24,
"po_critical_escalation_hours": 12,
"use_reorder_rules": True,
"economic_rounding": True,
"respect_storage_limits": True,
"use_supplier_minimums": True,
"optimize_price_tiers": True
},
"inventory_settings": {
"low_stock_threshold": 10,
"reorder_point": 20,
"reorder_quantity": 50,
"expiring_soon_days": 7,
"expiration_warning_days": 3,
"quality_score_threshold": 8.0,
"temperature_monitoring_enabled": True,
"refrigeration_temp_min": 1.0,
"refrigeration_temp_max": 4.0,
"freezer_temp_min": -20.0,
"freezer_temp_max": -15.0,
"room_temp_min": 18.0,
"room_temp_max": 25.0,
"temp_deviation_alert_minutes": 15,
"critical_temp_deviation_minutes": 5
},
"production_settings": {
"planning_horizon_days": 7,
"minimum_batch_size": 1.0,
"maximum_batch_size": 100.0,
"production_buffer_percentage": 10.0,
"working_hours_per_day": 12,
"max_overtime_hours": 4,
"capacity_utilization_target": 0.85,
"capacity_warning_threshold": 0.95,
"quality_check_enabled": True,
"minimum_yield_percentage": 85.0,
"quality_score_threshold": 8.0,
"schedule_optimization_enabled": True,
"prep_time_buffer_minutes": 30,
"cleanup_time_buffer_minutes": 15,
"labor_cost_per_hour_eur": 15.0,
"overhead_cost_percentage": 20.0
},
"supplier_settings": {
"default_payment_terms_days": 30,
"default_delivery_days": 3,
"excellent_delivery_rate": 95.0,
"good_delivery_rate": 90.0,
"excellent_quality_rate": 98.0,
"good_quality_rate": 95.0,
"critical_delivery_delay_hours": 24,
"critical_quality_rejection_rate": 10.0,
"high_cost_variance_percentage": 15.0,
"require_supplier_approval": True,
"auto_approve_for_admin_owner": True,
"approval_required_roles": ["member", "viewer"]
},
"pos_settings": {
"sync_interval_minutes": 5,
"auto_sync_products": True,
"auto_sync_transactions": True
},
"order_settings": {
"max_discount_percentage": 50.0,
"default_delivery_window_hours": 48,
"dynamic_pricing_enabled": False,
"discount_enabled": True,
"delivery_tracking_enabled": True
},
"replenishment_settings": {
"projection_horizon_days": 7,
"service_level": 0.95,
"buffer_days": 1,
"enable_auto_replenishment": True,
"min_order_quantity": 1.0,
"max_order_quantity": 1000.0,
"demand_forecast_days": 14
},
"safety_stock_settings": {
"service_level": 0.95,
"method": "statistical",
"min_safety_stock": 0.0,
"max_safety_stock": 100.0,
"reorder_point_calculation": "safety_stock_plus_lead_time_demand"
},
"moq_settings": {
"consolidation_window_days": 7,
"allow_early_ordering": True,
"enable_batch_optimization": True,
"min_batch_size": 1.0,
"max_batch_size": 1000.0
},
"supplier_selection_settings": {
"price_weight": 0.40,
"lead_time_weight": 0.20,
"quality_weight": 0.20,
"reliability_weight": 0.20,
"diversification_threshold": 1000,
"max_single_percentage": 0.70,
"enable_supplier_score_optimization": True
},
"ml_insights_settings": {
"inventory_lookback_days": 90,
"inventory_min_history_days": 30,
"production_lookback_days": 90,
"production_min_history_runs": 30,
"supplier_analysis_lookback_days": 180,
"supplier_analysis_min_orders": 10,
"price_forecast_lookback_days": 180,
"price_forecast_horizon_days": 30,
"rules_generation_lookback_days": 90,
"rules_generation_min_samples": 10,
"enable_ml_insights": True,
"ml_insights_auto_trigger": False,
"ml_confidence_threshold": 0.80
},
"notification_settings": {
"whatsapp_enabled": False,
"whatsapp_phone_number_id": "",
"whatsapp_display_phone_number": "",
"whatsapp_default_language": "es",
"email_enabled": True,
"email_from_address": "",
"email_from_name": "",
"email_reply_to": "",
"enable_po_notifications": True,
"enable_inventory_alerts": True,
"enable_production_alerts": True,
"enable_forecast_alerts": True,
"po_notification_channels": ["email"],
"inventory_alert_channels": ["email"],
"production_alert_channels": ["email"],
"forecast_alert_channels": ["email"]
}
}