""" Job arq: watch_receipt – attende la ricevuta di accettazione per una PEC inviata. Viene enqueued da send_pec dopo un invio riuscito con un defer di 24 ore. Se dopo 24h nessuna ricevuta (accettazione o avvenuta_consegna) è arrivata tramite IMAP sync, imposta lo stato del messaggio a 'anomaly' e pubblica un evento WebSocket all'admin del tenant. Flow: send_pec → invio OK → enqueue watch_receipt (defer 24h) IMAP sync → ricevuta arriva → aggiorna Message.state a 'accepted'/'delivered' watch_receipt (dopo 24h) → verifica se state == 'accepted'/'delivered' → no → state = 'anomaly' + WS event """ import json import logging import uuid as uuid_module from typing import Any from sqlalchemy import select from app.database import AsyncSessionLocal from app.models import Message logger = logging.getLogger(__name__) # Stati che indicano ricezione ricevuta (impostati da IMAP sync via pec_parser) _ACCEPTED_STATES = {"accepted", "delivered"} async def watch_receipt(ctx: dict[str, Any], message_id: str) -> dict: """ Job arq: verifica se il messaggio outbound ha ricevuto accettazione. Args: ctx: contesto arq (redis, ecc.) message_id: UUID del messaggio outbound da monitorare Returns: dict con esito del controllo """ redis_client = ctx.get("redis") async with AsyncSessionLocal() as db: msg = await db.get(Message, uuid_module.UUID(message_id)) if not msg: logger.warning(f"[watch_receipt] Messaggio {message_id} non trovato") return {"status": "error", "message": "Messaggio non trovato"} if msg.direction != "outbound": return {"status": "skipped", "message": "Non è un messaggio outbound"} if msg.state in _ACCEPTED_STATES: # Ricevuta già arrivata tramite IMAP sync: OK logger.info( f"[watch_receipt] Messaggio {message_id} ha ricevuto " f"accettazione (state={msg.state!r})" ) return {"status": "ok", "state": msg.state} # Nessuna ricevuta in 24h → anomalia logger.warning( f"[watch_receipt] Nessuna accettazione in 24h per {message_id} " f"(state={msg.state!r}, mailbox={msg.mailbox_id})" ) prev_state = msg.state msg.state = "anomaly" await db.commit() # Pubblica evento WebSocket al tenant if redis_client: event = { "type": "message:anomaly", "message_id": message_id, "mailbox_id": str(msg.mailbox_id), "subject": msg.subject, "reason": "Nessuna ricevuta di accettazione entro 24 ore", "previous_state": prev_state, } channel = f"ws:tenant:{msg.tenant_id}" try: await redis_client.publish(channel, json.dumps(event, default=str)) logger.debug(f"[watch_receipt] Evento anomalia pubblicato su {channel}") except Exception as e: logger.error(f"[watch_receipt] Errore pubblicazione Redis: {e}") return { "status": "anomaly", "message_id": message_id, "reason": "Nessuna ricevuta di accettazione entro 24 ore", }