Fascicoli+Tassonomia+permessi
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
"""
|
||||
Migrazione 0016: tabelle fascicoli e fascicolo_messages.
|
||||
Fascicolazione / Dossier — raggruppa piu' comunicazioni PEC in una pratica.
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision = "0016"
|
||||
down_revision = "0015"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ─── Tabella fascicoli ─────────────────────────────────────────────────────
|
||||
# L'ENUM fascicolo_stato viene creato automaticamente da SQLAlchemy
|
||||
# come parte di op.create_table (no op.execute separato)
|
||||
op.create_table(
|
||||
"fascicoli",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column(
|
||||
"tenant_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("tenants.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("titolo", sa.String(255), nullable=False),
|
||||
sa.Column("numero_pratica", sa.String(100), nullable=True),
|
||||
sa.Column(
|
||||
"stato",
|
||||
sa.Enum("aperto", "chiuso", "archiviato", name="fascicolo_stato"),
|
||||
nullable=False,
|
||||
server_default="aperto",
|
||||
),
|
||||
sa.Column("categoria", sa.String(100), nullable=True),
|
||||
sa.Column(
|
||||
"responsabile_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("users.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("scadenza", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("note", sa.Text, nullable=True),
|
||||
sa.Column(
|
||||
"created_by",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("users.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
)
|
||||
op.create_index("idx_fascicoli_tenant", "fascicoli", ["tenant_id"])
|
||||
op.create_index("idx_fascicoli_stato", "fascicoli", ["stato"])
|
||||
op.create_index("idx_fascicoli_responsabile", "fascicoli", ["responsabile_id"])
|
||||
|
||||
# ─── Tabella fascicolo_messages (N:M) ──────────────────────────────────────
|
||||
op.create_table(
|
||||
"fascicolo_messages",
|
||||
sa.Column(
|
||||
"fascicolo_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("fascicoli.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
),
|
||||
sa.Column(
|
||||
"message_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("messages.id", ondelete="CASCADE"),
|
||||
primary_key=True,
|
||||
),
|
||||
sa.Column(
|
||||
"added_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
sa.Column(
|
||||
"added_by",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("users.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
op.create_index(
|
||||
"idx_fascicolo_messages_fascicolo", "fascicolo_messages", ["fascicolo_id"]
|
||||
)
|
||||
op.create_index(
|
||||
"idx_fascicolo_messages_message", "fascicolo_messages", ["message_id"]
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("idx_fascicolo_messages_message", table_name="fascicolo_messages")
|
||||
op.drop_index("idx_fascicolo_messages_fascicolo", table_name="fascicolo_messages")
|
||||
op.drop_table("fascicolo_messages")
|
||||
|
||||
op.drop_index("idx_fascicoli_responsabile", table_name="fascicoli")
|
||||
op.drop_index("idx_fascicoli_stato", table_name="fascicoli")
|
||||
op.drop_index("idx_fascicoli_tenant", table_name="fascicoli")
|
||||
op.drop_table("fascicoli")
|
||||
|
||||
op.execute("DROP TYPE fascicolo_stato")
|
||||
@@ -0,0 +1,78 @@
|
||||
"""
|
||||
Migrazione 0017: Tassonomia di Classificazione Multi-livello (Feature N2).
|
||||
|
||||
Estende la tabella `labels` con:
|
||||
- parent_id: FK self-referenziale (nullable) per struttura ad albero
|
||||
- description: testo descrittivo opzionale
|
||||
|
||||
Struttura tassonomica:
|
||||
Livello 0 (radice) = Ambito (Area Aziendale) — parent_id IS NULL
|
||||
Livello 1 = Processo — parent_id = ID Ambito
|
||||
Livello 2 (foglia) = Classificazione — parent_id = ID Processo
|
||||
|
||||
I vincoli di unicità vengono sostituiti con indici parziali per supportare
|
||||
nomi identici a diversi livelli dell'albero.
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision = "0017"
|
||||
down_revision = "0016"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ── Aggiunge parent_id (FK self-referenziale nullable) ─────────────────────
|
||||
op.add_column(
|
||||
"labels",
|
||||
sa.Column(
|
||||
"parent_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_labels_parent",
|
||||
"labels",
|
||||
"labels",
|
||||
["parent_id"],
|
||||
["id"],
|
||||
ondelete="CASCADE",
|
||||
)
|
||||
|
||||
# ── Aggiunge description ────────────────────────────────────────────────────
|
||||
op.add_column(
|
||||
"labels",
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
)
|
||||
|
||||
# ── Indice su parent_id per query gerarchiche ───────────────────────────────
|
||||
op.create_index("idx_labels_parent", "labels", ["parent_id"])
|
||||
|
||||
# ── Sostituisce il vincolo di unicità con indici parziali ───────────────────
|
||||
# Il vecchio vincolo (tenant_id, name) non supporta nomi uguali a livelli diversi
|
||||
op.drop_constraint("uq_label_name_tenant", "labels")
|
||||
|
||||
# Nodi radice: nome unico per tenant (parent_id IS NULL)
|
||||
op.execute(
|
||||
"CREATE UNIQUE INDEX uq_label_name_root "
|
||||
"ON labels (tenant_id, name) WHERE parent_id IS NULL"
|
||||
)
|
||||
# Nodi non-radice: nome unico per (tenant, parent) — stesso nome ammesso sotto parent diversi
|
||||
op.execute(
|
||||
"CREATE UNIQUE INDEX uq_label_name_parent "
|
||||
"ON labels (tenant_id, name, parent_id) WHERE parent_id IS NOT NULL"
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP INDEX IF EXISTS uq_label_name_parent")
|
||||
op.execute("DROP INDEX IF EXISTS uq_label_name_root")
|
||||
op.create_unique_constraint("uq_label_name_tenant", "labels", ["tenant_id", "name"])
|
||||
op.drop_index("idx_labels_parent", table_name="labels")
|
||||
op.drop_constraint("fk_labels_parent", "labels", type_="foreignkey")
|
||||
op.drop_column("labels", "description")
|
||||
op.drop_column("labels", "parent_id")
|
||||
@@ -0,0 +1,80 @@
|
||||
"""
|
||||
Migrazione 0018: Livello di Rischio e Riservatezza per Comunicazione (Feature N3).
|
||||
|
||||
Aggiunge alla tabella `messages`:
|
||||
- risk_level ENUM(low, medium, high, critical) nullable
|
||||
- confidentiality ENUM(public, internal, confidential, secret) nullable
|
||||
|
||||
Entrambi i campi sono nullable: rimangono NULL finche' non vengono
|
||||
impostati manualmente o da una regola di smistamento (set_risk_level /
|
||||
set_confidentiality).
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision = "0018"
|
||||
down_revision = "0017"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ── Crea i nuovi ENUM types ─────────────────────────────────────────────────
|
||||
risk_level_enum = postgresql.ENUM(
|
||||
"low", "medium", "high", "critical",
|
||||
name="risk_level",
|
||||
create_type=True,
|
||||
)
|
||||
confidentiality_enum = postgresql.ENUM(
|
||||
"public", "internal", "confidential", "secret",
|
||||
name="confidentiality_level",
|
||||
create_type=True,
|
||||
)
|
||||
risk_level_enum.create(op.get_bind(), checkfirst=True)
|
||||
confidentiality_enum.create(op.get_bind(), checkfirst=True)
|
||||
|
||||
# ── Aggiunge le colonne a messages ──────────────────────────────────────────
|
||||
op.add_column(
|
||||
"messages",
|
||||
sa.Column(
|
||||
"risk_level",
|
||||
sa.Enum("low", "medium", "high", "critical", name="risk_level", create_type=False),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"messages",
|
||||
sa.Column(
|
||||
"confidentiality",
|
||||
sa.Enum("public", "internal", "confidential", "secret", name="confidentiality_level", create_type=False),
|
||||
nullable=True,
|
||||
),
|
||||
)
|
||||
|
||||
# ── Indici per filtrare rapidamente per rischio/riservatezza ────────────────
|
||||
op.create_index(
|
||||
"idx_messages_risk_level",
|
||||
"messages",
|
||||
["tenant_id", "risk_level"],
|
||||
postgresql_where=sa.text("risk_level IS NOT NULL"),
|
||||
)
|
||||
op.create_index(
|
||||
"idx_messages_confidentiality",
|
||||
"messages",
|
||||
["tenant_id", "confidentiality"],
|
||||
postgresql_where=sa.text("confidentiality IS NOT NULL"),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("idx_messages_confidentiality", table_name="messages")
|
||||
op.drop_index("idx_messages_risk_level", table_name="messages")
|
||||
op.drop_column("messages", "confidentiality")
|
||||
op.drop_column("messages", "risk_level")
|
||||
|
||||
risk_level_enum = postgresql.ENUM(name="risk_level")
|
||||
confidentiality_enum = postgresql.ENUM(name="confidentiality_level")
|
||||
risk_level_enum.drop(op.get_bind(), checkfirst=True)
|
||||
confidentiality_enum.drop(op.get_bind(), checkfirst=True)
|
||||
@@ -0,0 +1,77 @@
|
||||
"""
|
||||
Migrazione 0019: Tabella permission_presets (sottoruoli/template permessi).
|
||||
|
||||
Aggiunge la tabella `permission_presets` che consente ad admin e supervisor
|
||||
di definire template nominati di permessi riutilizzabili (es. "Operatore Archivio",
|
||||
"Operatore Invio", ecc.) da applicare agli operatori sulle caselle PEC.
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision = "0019"
|
||||
down_revision = "0018"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"permission_presets",
|
||||
sa.Column(
|
||||
"id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
primary_key=True,
|
||||
server_default=sa.text("gen_random_uuid()"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"tenant_id",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("tenants.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("name", sa.String(100), nullable=False),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("can_read", sa.Boolean(), nullable=False, server_default="true"),
|
||||
sa.Column("can_send", sa.Boolean(), nullable=False, server_default="false"),
|
||||
sa.Column("can_manage", sa.Boolean(), nullable=False, server_default="false"),
|
||||
sa.Column("can_conserve", sa.Boolean(), nullable=False, server_default="false"),
|
||||
sa.Column(
|
||||
"created_by",
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey("users.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.text("now()"),
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.text("now()"),
|
||||
),
|
||||
)
|
||||
|
||||
# Unicita' nome per tenant
|
||||
op.create_unique_constraint(
|
||||
"uq_preset_name_tenant",
|
||||
"permission_presets",
|
||||
["tenant_id", "name"],
|
||||
)
|
||||
|
||||
# Indici
|
||||
op.create_index("idx_preset_tenant", "permission_presets", ["tenant_id"])
|
||||
op.create_index("idx_preset_created_by", "permission_presets", ["created_by"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("idx_preset_created_by", table_name="permission_presets")
|
||||
op.drop_index("idx_preset_tenant", table_name="permission_presets")
|
||||
op.drop_constraint("uq_preset_name_tenant", "permission_presets", type_="unique")
|
||||
op.drop_table("permission_presets")
|
||||
Reference in New Issue
Block a user