Add whatsapp feature

This commit is contained in:
Urtzi Alfaro
2025-11-13 16:01:08 +01:00
parent d7df2b0853
commit 9bc048d360
74 changed files with 9765 additions and 533 deletions

View File

@@ -180,6 +180,35 @@ class TenantSettings(Base):
"ml_confidence_threshold": 0.80
})
# Notification Settings (Notification Service)
notification_settings = Column(JSON, nullable=False, default=lambda: {
# WhatsApp Configuration
"whatsapp_enabled": False,
"whatsapp_phone_number_id": "", # Meta WhatsApp Phone Number ID
"whatsapp_access_token": "", # Meta access token (should be encrypted)
"whatsapp_business_account_id": "", # Meta Business Account ID
"whatsapp_api_version": "v18.0",
"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)
@@ -321,5 +350,25 @@ class TenantSettings(Base):
"enable_ml_insights": True,
"ml_insights_auto_trigger": False,
"ml_confidence_threshold": 0.80
},
"notification_settings": {
"whatsapp_enabled": False,
"whatsapp_phone_number_id": "",
"whatsapp_access_token": "",
"whatsapp_business_account_id": "",
"whatsapp_api_version": "v18.0",
"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"]
}
}

View File

@@ -218,6 +218,58 @@ class MLInsightsSettings(BaseModel):
ml_confidence_threshold: float = Field(0.80, ge=0.0, le=1.0, description="Minimum confidence threshold for ML recommendations")
class NotificationSettings(BaseModel):
"""Notification and communication settings"""
# WhatsApp Configuration
whatsapp_enabled: bool = Field(False, description="Enable WhatsApp notifications for this tenant")
whatsapp_phone_number_id: str = Field("", description="Meta WhatsApp Phone Number ID")
whatsapp_access_token: str = Field("", description="Meta WhatsApp Access Token (encrypted)")
whatsapp_business_account_id: str = Field("", description="Meta WhatsApp Business Account ID")
whatsapp_api_version: str = Field("v18.0", description="WhatsApp Cloud API version")
whatsapp_default_language: str = Field("es", description="Default language for WhatsApp templates")
# Email Configuration
email_enabled: bool = Field(True, description="Enable email notifications for this tenant")
email_from_address: str = Field("", description="Custom from email address (optional)")
email_from_name: str = Field("", description="Custom from name (optional)")
email_reply_to: str = Field("", description="Reply-to email address (optional)")
# Notification Preferences
enable_po_notifications: bool = Field(True, description="Enable purchase order notifications")
enable_inventory_alerts: bool = Field(True, description="Enable inventory alerts")
enable_production_alerts: bool = Field(True, description="Enable production alerts")
enable_forecast_alerts: bool = Field(True, description="Enable forecast alerts")
# Notification Channels
po_notification_channels: list[str] = Field(["email"], description="Channels for PO notifications (email, whatsapp)")
inventory_alert_channels: list[str] = Field(["email"], description="Channels for inventory alerts")
production_alert_channels: list[str] = Field(["email"], description="Channels for production alerts")
forecast_alert_channels: list[str] = Field(["email"], description="Channels for forecast alerts")
@validator('po_notification_channels', 'inventory_alert_channels', 'production_alert_channels', 'forecast_alert_channels')
def validate_channels(cls, v):
"""Validate that channels are valid"""
valid_channels = ["email", "whatsapp", "sms", "push"]
for channel in v:
if channel not in valid_channels:
raise ValueError(f"Invalid channel: {channel}. Must be one of {valid_channels}")
return v
@validator('whatsapp_phone_number_id')
def validate_phone_number_id(cls, v, values):
"""Validate phone number ID is provided if WhatsApp is enabled"""
if values.get('whatsapp_enabled') and not v:
raise ValueError("whatsapp_phone_number_id is required when WhatsApp is enabled")
return v
@validator('whatsapp_access_token')
def validate_access_token(cls, v, values):
"""Validate access token is provided if WhatsApp is enabled"""
if values.get('whatsapp_enabled') and not v:
raise ValueError("whatsapp_access_token is required when WhatsApp is enabled")
return v
# ================================================================
# REQUEST/RESPONSE SCHEMAS
# ================================================================
@@ -237,6 +289,7 @@ class TenantSettingsResponse(BaseModel):
moq_settings: MOQSettings
supplier_selection_settings: SupplierSelectionSettings
ml_insights_settings: MLInsightsSettings
notification_settings: NotificationSettings
created_at: datetime
updated_at: datetime
@@ -257,6 +310,7 @@ class TenantSettingsUpdate(BaseModel):
moq_settings: Optional[MOQSettings] = None
supplier_selection_settings: Optional[SupplierSelectionSettings] = None
ml_insights_settings: Optional[MLInsightsSettings] = None
notification_settings: Optional[NotificationSettings] = None
class CategoryUpdateRequest(BaseModel):

View File

@@ -23,7 +23,8 @@ from ..schemas.tenant_settings import (
ReplenishmentSettings,
SafetyStockSettings,
MOQSettings,
SupplierSelectionSettings
SupplierSelectionSettings,
NotificationSettings
)
logger = structlog.get_logger()
@@ -46,7 +47,8 @@ class TenantSettingsService:
"replenishment": ReplenishmentSettings,
"safety_stock": SafetyStockSettings,
"moq": MOQSettings,
"supplier_selection": SupplierSelectionSettings
"supplier_selection": SupplierSelectionSettings,
"notification": NotificationSettings
}
# Map category names to database column names
@@ -60,7 +62,8 @@ class TenantSettingsService:
"replenishment": "replenishment_settings",
"safety_stock": "safety_stock_settings",
"moq": "moq_settings",
"supplier_selection": "supplier_selection_settings"
"supplier_selection": "supplier_selection_settings",
"notification": "notification_settings"
}
def __init__(self, db: AsyncSession):