fix parsing ricevute

This commit is contained in:
2026-06-18 16:31:29 +02:00
parent 4c90a7c1a3
commit 058c550cd2
10 changed files with 731 additions and 15 deletions
@@ -0,0 +1,49 @@
"""
Migrazione 0023: aggiunge colonna riferimento_message_id alla tabella messages.
La colonna memorizza il valore di X-Riferimento-Message-ID presente nelle
ricevute PEC inbound (accettazione, avvenuta_consegna, ecc.). Serve per:
1. Binding retroattivo: se il binding fallisce durante la sync (race condition
con send_pec che non ha ancora committato message_id_header), il job di
rebinding puo' usare questa colonna per ricollegare le ricevute orfane
al messaggio outbound originale senza dover ri-leggere l'EML da MinIO.
2. Diagnostica: permette di verificare rapidamente quali ricevute hanno un
X-Riferimento valorizzato ma non hanno trovato il corrispondente outbound.
Revision ID: 0023_add_riferimento_message_id
Revises: 0022_partial_unique_mailbox_email
"""
from alembic import op
import sqlalchemy as sa
# ── Identificatori migrazione ─────────────────────────────────────────────────
revision = "0023"
down_revision = "0022"
branch_labels = None
depends_on = None
def upgrade() -> None:
# Colonna nullable: solo le ricevute inbound la hanno valorizzata.
# I messaggi posta_certificata (inbound e outbound) la lasciano NULL.
op.add_column(
"messages",
sa.Column("riferimento_message_id", sa.Text(), nullable=True),
)
# Indice parziale per le query di binding retroattivo:
# cerca ricevute orfane (parent_message_id IS NULL) con riferimento valorizzato.
op.create_index(
"idx_messages_riferimento",
"messages",
["riferimento_message_id"],
postgresql_where=sa.text("riferimento_message_id IS NOT NULL"),
)
def downgrade() -> None:
op.drop_index("idx_messages_riferimento", table_name="messages")
op.drop_column("messages", "riferimento_message_id")
+18
View File
@@ -244,6 +244,24 @@ async def list_messages(
Message.parent_message_id.is_(None),
)
# ── Auto-filtro ricevute ───────────────────────────────────────────────────
# Esclude automaticamente i messaggi inbound di tipo ricevuta (accettazione,
# avvenuta_consegna, ecc.) quando pec_type non e' specificato esplicitamente.
# Le ricevute correttamente bindate hanno parent_message_id != NULL e sono
# gia' escluse dal filtro sopra. Quelle non bindato (race condition o invii
# da client esterni) verrebbero mostrate in inbox come messaggi normali senza
# questo filtro aggiuntivo.
# I messaggi outbound hanno sempre pec_type='posta_certificata' in questo
# sistema (non creiamo mai record outbound di tipo ricevuta) quindi il filtro
# e' trasparente per la vista posta inviata.
if pec_type is None:
q = q.where(
or_(
Message.direction == "outbound",
Message.pec_type == "posta_certificata",
)
)
if visible_mailbox_ids is not None:
if not visible_mailbox_ids:
return MessageListResponse(items=[], total=0, page=page, page_size=page_size)
+6
View File
@@ -92,6 +92,12 @@ class Message(Base):
parent_message_id: Mapped[uuid.UUID | None] = mapped_column(
UUID(as_uuid=True), ForeignKey("messages.id"), nullable=True
)
# X-Riferimento-Message-ID estratto dalle ricevute inbound PEC.
# Permette il binding retroattivo se la race condition ha impedito il binding
# live (send_pec non aveva ancora committato message_id_header quando la
# ricevuta e' stata processata dalla sync IMAP).
# Solo le ricevute (pec_type != posta_certificata) la hanno valorizzata.
riferimento_message_id: Mapped[str | None] = mapped_column(Text, nullable=True)
# Flag operativi
is_read: Mapped[bool] = mapped_column(Boolean, nullable=False, default=False)