GapFill Flowee
This commit is contained in:
+49
-13
@@ -39,7 +39,8 @@ from app.jobs.dispatch_notification import evaluate_and_enqueue_notifications
|
||||
from app.jobs.index_message import index_message
|
||||
from app.models import Attachment, Mailbox, Message
|
||||
from app.parsers.eml_parser import parse_eml
|
||||
from app.parsers.pec_parser import apply_outbound_transition, classify_pec_message
|
||||
from app.parsers.pec_parser import apply_outbound_transition, classify_pec_message, detect_protocol
|
||||
from app.parsers.rem_parser import classify_rem_message
|
||||
from app.storage.minio_client import upload_attachment, upload_eml
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -543,15 +544,47 @@ async def _save_message(
|
||||
logger.debug(f"[{mailbox.email_address}] UID {uid} in {imap_folder!r} già in DB, skip")
|
||||
return False
|
||||
|
||||
# ── Classificazione PEC da header (veloce, senza body) ───────────────────
|
||||
# ── Classificazione tipo messaggio da header (veloce, senza body) ────────
|
||||
# La classificazione avviene PRIMA del parsing completo perche' il parser
|
||||
# deve sapere se il messaggio e' una ricevuta per evitare di sovrascrivere
|
||||
# il body_text (testo della ricevuta) con il contenuto di postacert.eml.
|
||||
#
|
||||
# Strategia dual-protocol (Feature N8 – REM europea):
|
||||
# 1. Rileva automaticamente il protocollo dagli header del messaggio.
|
||||
# 2. Se header X-REM-* presenti: usa classify_rem_message (ETSI EN 319 532-4)
|
||||
# 3. Se header PEC italiani (X-Ricevuta/X-TipoRicevuta): usa classify_pec_message
|
||||
# 4. Default fallback: PEC italiana
|
||||
#
|
||||
# Il protocollo configurato sulla casella (mailbox.protocol_type) viene
|
||||
# usato come hint, ma il rilevamento automatico degli header ha priorita'.
|
||||
quick_msg = email.message_from_bytes(raw_eml)
|
||||
pec_class = classify_pec_message(quick_msg)
|
||||
|
||||
mailbox_protocol = getattr(mailbox, "protocol_type", "pec_it") or "pec_it"
|
||||
detected_protocol = detect_protocol(quick_msg)
|
||||
# Il rilevamento automatico ha priorita': una casella PEC-IT che riceve un
|
||||
# messaggio REM da partner europeo viene classificata correttamente.
|
||||
_protocol_type = detected_protocol if detected_protocol == "rem_eu" else mailbox_protocol
|
||||
|
||||
if _protocol_type == "rem_eu":
|
||||
rem_class = classify_rem_message(quick_msg)
|
||||
_pec_type = rem_class.pec_type
|
||||
_is_receipt = rem_class.is_receipt
|
||||
_riferimento_message_id = rem_class.riferimento_message_id
|
||||
_rem_evidence_type = rem_class.rem_evidence_type
|
||||
logger.debug(
|
||||
f"[{mailbox.email_address}] REM UID={uid}: "
|
||||
f"evidence={_rem_evidence_type!r} → pec_type={_pec_type!r}"
|
||||
)
|
||||
else:
|
||||
pec_class = classify_pec_message(quick_msg)
|
||||
_pec_type = pec_class.pec_type
|
||||
_is_receipt = pec_class.is_receipt
|
||||
_riferimento_message_id = pec_class.riferimento_message_id
|
||||
_rem_evidence_type = None
|
||||
_protocol_type = "pec_it"
|
||||
|
||||
# ── Parsing completo EML (con is_receipt per proteggere il body) ──────────
|
||||
parsed = parse_eml(raw_eml, is_receipt=pec_class.is_receipt)
|
||||
parsed = parse_eml(raw_eml, is_receipt=_is_receipt)
|
||||
received_at = datetime.now(UTC)
|
||||
|
||||
# ── Dedup outbound: upsert sul record send_pec invece di creare duplicato ─
|
||||
@@ -605,18 +638,18 @@ async def _save_message(
|
||||
# Solo per messaggi inbound che sono ricevute PEC (non per posta inviata)
|
||||
parent_message_id: uuid.UUID | None = None
|
||||
|
||||
if direction == "inbound" and pec_class.is_receipt and pec_class.riferimento_message_id:
|
||||
if direction == "inbound" and _is_receipt and _riferimento_message_id:
|
||||
try:
|
||||
parent_message_id = await _apply_outbound_state_machine(
|
||||
riferimento_message_id=pec_class.riferimento_message_id,
|
||||
pec_type=pec_class.pec_type,
|
||||
riferimento_message_id=_riferimento_message_id,
|
||||
pec_type=_pec_type,
|
||||
tenant_id=mailbox.tenant_id,
|
||||
db=db,
|
||||
)
|
||||
except Exception as bind_err:
|
||||
logger.error(
|
||||
f"[{mailbox.email_address}] [receipt-binding] Errore aggiornamento stato "
|
||||
f"outbound per ricevuta UID={uid} tipo={pec_class.pec_type!r}: {bind_err}",
|
||||
f"outbound per ricevuta UID={uid} tipo={_pec_type!r}: {bind_err}",
|
||||
exc_info=True,
|
||||
)
|
||||
# Non interrompere il salvataggio della ricevuta: il record viene
|
||||
@@ -648,7 +681,9 @@ async def _save_message(
|
||||
imap_folder=imap_folder,
|
||||
direction=direction,
|
||||
state=state,
|
||||
pec_type=pec_class.pec_type,
|
||||
pec_type=_pec_type,
|
||||
protocol_type=_protocol_type,
|
||||
rem_evidence_type=_rem_evidence_type,
|
||||
subject=parsed.subject,
|
||||
from_address=parsed.from_address,
|
||||
to_addresses=parsed.to_addresses if parsed.to_addresses else None,
|
||||
@@ -687,7 +722,7 @@ async def _save_message(
|
||||
"from_address": message.from_address or "",
|
||||
"pec_type": message.pec_type,
|
||||
"direction": direction,
|
||||
"is_receipt": pec_class.is_receipt,
|
||||
"is_receipt": _is_receipt,
|
||||
"received_at": received_at.isoformat(),
|
||||
}
|
||||
await redis_client.publish(f"ws:tenant:{mailbox.tenant_id}", json.dumps(event))
|
||||
@@ -696,7 +731,7 @@ async def _save_message(
|
||||
|
||||
logger.info(
|
||||
f"[{mailbox.email_address}] Nuovo messaggio: UID={uid} folder={imap_folder!r} "
|
||||
f"direction={direction!r} pec_type={pec_class.pec_type!r} "
|
||||
f"direction={direction!r} pec_type={_pec_type!r} protocol={_protocol_type!r} "
|
||||
f"subject={message.subject!r} allegati={len(parsed.attachments)}"
|
||||
)
|
||||
|
||||
@@ -719,7 +754,8 @@ async def _save_message(
|
||||
|
||||
# ── Regole di smistamento automatico (Feature 2) ──────────────────────────
|
||||
# Solo per messaggi inbound posta_certificata (non ricevute di sistema).
|
||||
if direction == "inbound" and pec_class.pec_type == "posta_certificata":
|
||||
# Valido anche per messaggi REM (REMDispatch e' equivalente a posta_certificata).
|
||||
if direction == "inbound" and _pec_type == "posta_certificata":
|
||||
try:
|
||||
await redis_client.enqueue_job("apply_routing_rules", str(message.id))
|
||||
except Exception as e:
|
||||
@@ -728,7 +764,7 @@ async def _save_message(
|
||||
# ── Auto-save mittente nella rubrica (Feature 6) ──────────────────────────
|
||||
# Per messaggi inbound di tipo posta_certificata, salva automaticamente
|
||||
# il mittente nella rubrica pec_contacts del tenant (upsert idempotente).
|
||||
if direction == "inbound" and pec_class.pec_type == "posta_certificata" and message.from_address:
|
||||
if direction == "inbound" and _pec_type == "posta_certificata" and message.from_address:
|
||||
try:
|
||||
from sqlalchemy import text as _text
|
||||
await db.execute(
|
||||
|
||||
Reference in New Issue
Block a user