""" Modelli RoutingRule, RoutingRuleCondition, RoutingRuleAction. Le regole di smistamento automatico vengono valutate a ogni messaggio in arrivo: 1. Si caricano tutte le regole attive del tenant, ordinate per priority ASC 2. Per ogni regola si valutano le condizioni (AND tra le condizioni della stessa regola) 3. Se tutte le condizioni sono soddisfatte, si eseguono le azioni 4. Se stop_processing=True, si ferma l'elaborazione (non vengono valutate regole successive) """ import uuid from datetime import datetime from sqlalchemy import Boolean, DateTime, ForeignKey, Index, Integer, String, Text, func from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column, relationship from app.database import Base class RoutingRule(Base): __tablename__ = "routing_rules" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) tenant_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False ) name: Mapped[str] = mapped_column(String(255), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) priority: Mapped[int] = mapped_column(Integer, nullable=False, default=100) # Se True, le regole successive non vengono valutate una volta che questa fa match stop_processing: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True) created_by: Mapped[uuid.UUID | None] = mapped_column( UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True ) created_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now() ) updated_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, server_default=func.now(), onupdate=func.now() ) # Relazioni conditions: Mapped[list["RoutingRuleCondition"]] = relationship( "RoutingRuleCondition", back_populates="rule", cascade="all, delete-orphan" ) actions: Mapped[list["RoutingRuleAction"]] = relationship( "RoutingRuleAction", back_populates="rule", cascade="all, delete-orphan" ) __table_args__ = ( Index("idx_routing_rules_tenant", "tenant_id"), Index("idx_routing_rules_active", "tenant_id", "priority"), ) def __repr__(self) -> str: return f"" class RoutingRuleCondition(Base): """ Singola condizione di una regola. field : from_address | to_address | subject | mailbox_id | pec_type operator : contains | equals | starts_with | ends_with | regex | not_contains value : valore da confrontare (stringa) """ __tablename__ = "routing_rule_conditions" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) rule_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("routing_rules.id", ondelete="CASCADE"), nullable=False ) field: Mapped[str] = mapped_column(String(50), nullable=False) operator: Mapped[str] = mapped_column(String(30), nullable=False, default="contains") value: Mapped[str] = mapped_column(Text, nullable=False) rule: Mapped["RoutingRule"] = relationship("RoutingRule", back_populates="conditions") __table_args__ = ( Index("idx_routing_conditions_rule", "rule_id"), ) class RoutingRuleAction(Base): """ Singola azione di una regola. action_type : apply_label | assign_vbox | mark_read | mark_starred | notify_webhook action_value : UUID della label/vbox, o URL del webhook """ __tablename__ = "routing_rule_actions" id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), primary_key=True, default=uuid.uuid4 ) rule_id: Mapped[uuid.UUID] = mapped_column( UUID(as_uuid=True), ForeignKey("routing_rules.id", ondelete="CASCADE"), nullable=False ) action_type: Mapped[str] = mapped_column(String(50), nullable=False) action_value: Mapped[str | None] = mapped_column(Text, nullable=True) rule: Mapped["RoutingRule"] = relationship("RoutingRule", back_populates="actions") __table_args__ = ( Index("idx_routing_actions_rule", "rule_id"), )