Fascicoli+Tassonomia+permessi
This commit is contained in:
@@ -14,3 +14,4 @@ from app.models.template import MessageTemplate # noqa: F401
|
||||
from app.models.routing_rule import RoutingRule, RoutingRuleCondition, RoutingRuleAction # noqa: F401
|
||||
from app.models.pec_contact import PecContact # noqa: F401
|
||||
from app.models.signature import Signature, SignatureAssignment # noqa: F401
|
||||
from app.models.fascicolo import Fascicolo, FascicoloMessage # noqa: F401
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
"""
|
||||
Modelli Fascicolo e FascicoloMessage — fascicolazione pratiche.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, Enum, ForeignKey, Index, String, Text, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
FascicoloStato = Enum(
|
||||
"aperto", "chiuso", "archiviato",
|
||||
name="fascicolo_stato",
|
||||
create_type=False,
|
||||
)
|
||||
|
||||
|
||||
class Fascicolo(Base):
|
||||
__tablename__ = "fascicoli"
|
||||
|
||||
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
|
||||
)
|
||||
titolo: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
numero_pratica: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
stato: Mapped[str] = mapped_column(FascicoloStato, nullable=False, default="aperto")
|
||||
categoria: Mapped[str | None] = mapped_column(String(100), nullable=True)
|
||||
responsabile_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
scadenza: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
note: Mapped[str | None] = mapped_column(Text, nullable=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
|
||||
fascicolo_messages: Mapped[list["FascicoloMessage"]] = relationship(
|
||||
"FascicoloMessage", back_populates="fascicolo", cascade="all, delete-orphan"
|
||||
)
|
||||
messages: Mapped[list] = relationship(
|
||||
"Message",
|
||||
secondary="fascicolo_messages",
|
||||
lazy="select",
|
||||
viewonly=True,
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_fascicoli_tenant", "tenant_id"),
|
||||
Index("idx_fascicoli_stato", "stato"),
|
||||
Index("idx_fascicoli_responsabile", "responsabile_id"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Fascicolo {self.id} {self.titolo!r} {self.stato!r}>"
|
||||
|
||||
|
||||
class FascicoloMessage(Base):
|
||||
__tablename__ = "fascicolo_messages"
|
||||
|
||||
fascicolo_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("fascicoli.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
)
|
||||
message_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("messages.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
)
|
||||
added_at: Mapped[datetime] = mapped_column(
|
||||
DateTime(timezone=True), nullable=False, server_default=func.now()
|
||||
)
|
||||
added_by: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("users.id", ondelete="SET NULL"), nullable=True
|
||||
)
|
||||
|
||||
# Relazioni
|
||||
fascicolo: Mapped["Fascicolo"] = relationship("Fascicolo", back_populates="fascicolo_messages")
|
||||
|
||||
__table_args__ = (
|
||||
Index("idx_fascicolo_messages_fascicolo", "fascicolo_id"),
|
||||
Index("idx_fascicolo_messages_message", "message_id"),
|
||||
)
|
||||
@@ -1,10 +1,17 @@
|
||||
"""
|
||||
Modelli Label e MessageLabel – tagging messaggi.
|
||||
Modelli Label e MessageLabel – tagging messaggi con supporto tassonomia gerarchica.
|
||||
|
||||
Struttura ad albero (Feature N2 – Tassonomia di Classificazione Multi-livello):
|
||||
parent_id = NULL → Livello 0: Ambito (Area Aziendale)
|
||||
parent_id = ID ambito → Livello 1: Processo
|
||||
parent_id = ID processo → Livello 2: Classificazione (foglia)
|
||||
|
||||
Le label senza parent_id sono label "piatte" classiche (comportamento pre-esistente).
|
||||
"""
|
||||
|
||||
import uuid
|
||||
|
||||
from sqlalchemy import CHAR, ForeignKey, Index, String, UniqueConstraint
|
||||
from sqlalchemy import CHAR, ForeignKey, Index, String, Text
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
@@ -20,15 +27,25 @@ class Label(Base):
|
||||
tenant_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("tenants.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
# Tassonomia: se parent_id è None è un nodo radice (Ambito) o label piatta
|
||||
parent_id: Mapped[uuid.UUID | None] = mapped_column(
|
||||
UUID(as_uuid=True),
|
||||
ForeignKey("labels.id", ondelete="CASCADE"),
|
||||
nullable=True,
|
||||
)
|
||||
name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
color: Mapped[str | None] = mapped_column(CHAR(7), nullable=True) # hex #RRGGBB
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
# Nota: i vincoli di unicità sono gestiti da indici parziali nel DB:
|
||||
# uq_label_name_root – UNIQUE (tenant_id, name) WHERE parent_id IS NULL
|
||||
# uq_label_name_parent – UNIQUE (tenant_id, name, parent_id) WHERE parent_id IS NOT NULL
|
||||
__table_args__ = (
|
||||
UniqueConstraint("tenant_id", "name", name="uq_label_name_tenant"),
|
||||
Index("idx_labels_parent", "parent_id"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return f"<Label {self.name!r}>"
|
||||
return f"<Label {self.name!r} parent={self.parent_id}>"
|
||||
|
||||
|
||||
class MessageLabel(Base):
|
||||
|
||||
@@ -24,6 +24,12 @@ from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
RiskLevel = Enum("low", "medium", "high", "critical", name="risk_level", create_type=False)
|
||||
ConfidentialityLevel = Enum(
|
||||
"public", "internal", "confidential", "secret",
|
||||
name="confidentiality_level",
|
||||
create_type=False,
|
||||
)
|
||||
PecDirection = Enum("inbound", "outbound", name="pec_direction", create_type=False)
|
||||
PecState = Enum(
|
||||
"draft", "queued", "sent", "accepted", "delivered", "anomaly", "failed", "received",
|
||||
@@ -105,6 +111,10 @@ class Message(Base):
|
||||
is_conserved: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
conserved_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True)
|
||||
|
||||
# Rischio e Riservatezza (Feature N3)
|
||||
risk_level: Mapped[str | None] = mapped_column(RiskLevel, nullable=True)
|
||||
confidentiality: Mapped[str | None] = mapped_column(ConfidentialityLevel, nullable=True)
|
||||
|
||||
raw_eml_path: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
# Full-text search vector (aggiornato da trigger DB + worker per allegati)
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
"""
|
||||
Modello PermissionPreset – template riutilizzabili di permessi per casella.
|
||||
|
||||
Permette ad admin e supervisor di creare sottoruoli nominati (es. "Operatore Archivio")
|
||||
con combinazioni predefinite di can_read/can_send/can_manage/can_conserve.
|
||||
I preset sono per-tenant e vengono applicati opzionalmente al momento
|
||||
dell'assegnazione di un operatore a una casella.
|
||||
"""
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import (
|
||||
Boolean,
|
||||
DateTime,
|
||||
ForeignKey,
|
||||
Index,
|
||||
String,
|
||||
Text,
|
||||
UniqueConstraint,
|
||||
func,
|
||||
)
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column, relationship
|
||||
|
||||
from app.database import Base
|
||||
|
||||
|
||||
class PermissionPreset(Base):
|
||||
__tablename__ = "permission_presets"
|
||||
|
||||
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(100), nullable=False)
|
||||
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
||||
|
||||
can_read: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True)
|
||||
can_send: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
can_manage: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
can_conserve: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)
|
||||
|
||||
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
|
||||
creator: Mapped["User"] = relationship( # noqa: F821
|
||||
"User", foreign_keys=[created_by]
|
||||
)
|
||||
|
||||
__table_args__ = (
|
||||
UniqueConstraint("tenant_id", "name", name="uq_preset_name_tenant"),
|
||||
Index("idx_preset_tenant", "tenant_id"),
|
||||
Index("idx_preset_created_by", "created_by"),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"<PermissionPreset name={self.name!r} tenant={self.tenant_id} "
|
||||
f"read={self.can_read} send={self.can_send} "
|
||||
f"manage={self.can_manage} conserve={self.can_conserve}>"
|
||||
)
|
||||
Reference in New Issue
Block a user