- sapb1_manager.buscar_factura_existente busca la factura (proveedor, tipo, serie, número) en facturas de compra no anuladas y en drafts; si existe no se crea otro draft y se responde "ya está registrada" sin pisar el registro de una aceptación anterior. - SapNoDisponible (subclase de ConnectionError) distingue una caída (timeout, sin conexión, HTTP 5xx) de un rechazo normal de SAP (HTTP 4xx). Una caída de Service Layer con HANA sin resultados ya no se lee como "guía no existe". - Con SAP o HANA caídos se avisa una sola vez al proveedor con la plantilla "En cola" y el correo se reintenta en los ciclos siguientes (hasta 24), sin repetir avisos; al agotarse se avisa al equipo. - La lógica por factura pasa a _procesar_factura; lo ya resuelto dentro de un correo no se repite al reintentarlo. - seguimiento: columna intentos (con migración), registrar_intento y el estado de notificación en_espera. - database_utilities.DB_NAME pasa a ser absoluto (junto al código). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
4.9 KiB
seguimiento.py — Códigos de seguimiento y resultado por comprobante
Cada comprobante que llega por correo recibe un código de seguimiento (ABR-AAMMDD-NNNNN, por ejemplo ABR-261019-00042) que se le comunicará al proveedor y que permite reconocer el comprobante si lo reenvía. El registro también guarda el resultado del proceso y el estado de la notificación.
Los datos viven en la tabla comprobantes de emails.db. El esquema lo crea database_utilities.init_db() con CREATE TABLE IF NOT EXISTS, por lo que una base existente se actualiza sola sin tocar la tabla emails.
Identidad
- Un comprobante se identifica por (RUC emisor, tipo, serie, número). Si vuelve a llegar, en el mismo correo o en otro, conserva su código.
- Un correo que no trae ningún comprobante identificable (sin adjuntos, sin XML válido…) se registra aparte con
registrar_correo: una fila por correo, con RUC, serie y número enNULL. - El código sale del
idde la fila (ABR-+ fecha +idcon 5 dígitos), así que es único por construcción, sin reintentos ni contadores aparte.
Tabla comprobantes
| Columna | Contenido |
|---|---|
codigo_seguimiento |
Código único del comprobante |
email_id |
Último correo en el que llegó |
ruc_emisor, tipo, serie, numero |
Identidad del comprobante (NULL en las filas de correo sin comprobante) |
estado |
pendiente, aceptado, rechazado o error |
observaciones |
JSON con los motivos de rechazo o el detalle del error |
doc_entry |
DocEntry del draft creado en SAP |
veces_recibido |
Cuántos correos distintos trajeron este comprobante |
notificacion_estado, notificacion_error, notificacion_fecha |
pendiente, enviada, fallida, omitida o en_espera (se avisó que el servicio está fuera de línea), y su motivo |
intentos |
Veces que se intentó procesar el comprobante con SAP fuera de línea (tope: MAX_REINTENTOS) |
notificacion_destinatario |
Direcciones a las que se envió de verdad (en modo prueba no son las del remitente) |
creado_en, actualizado_en |
Marcas de tiempo |
Funciones
| Función | Qué hace |
|---|---|
registrar_comprobante(email_id, ruc_emisor, tipo, serie, numero) |
Crea el registro o recupera el existente. Devuelve la fila más reenvio (True si ya había llegado en otro correo). Reprocesar el mismo correo no cambia nada. |
registrar_correo(email_id) |
Igual, para un correo sin comprobante. Idempotente por correo. |
obtener(codigo) |
Devuelve el registro o None. |
actualizar_resultado(codigo, estado, observaciones=None, doc_entry=None) |
Guarda el resultado. ValueError si el estado no es válido, LookupError si no existe el código. |
registrar_notificacion(codigo, estado, error=None, destinatario=None) |
Guarda el estado de la notificación y a quién se envió. Mismas excepciones. |
registrar_intento(codigo) |
Suma un intento fallido por SAP fuera de línea y devuelve el total. |
contar_respuestas_correo(remitente, horas=24) |
Respuestas enviada a ese remitente en correos sin comprobante: el freno contra bucles de respuesta_proveedor. |
Uso en el pipeline
En pipeline._procesar_un_correo, cada factura se registra al empezar (_registrar_comprobante) y su resultado se guarda al terminar (_registrar_resultado):
| Resultado del proceso | estado |
Detalle guardado |
|---|---|---|
| Draft creado en SAP | aceptado |
doc_entry |
| Precios no coinciden con SAP | rechazado |
[{"codigo": "precios_no_coinciden"}] |
| Error al crear el draft | error |
[{"codigo": "error_sap", "detalle": "..."}] |
init_db agrega las columnas notificacion_destinatario e intentos a una tabla creada antes sin ellas, sin tocar los datos.
El seguimiento nunca impide procesar la factura: si falla la base de datos, se registra un WARNING y el flujo continúa. Si el comprobante ya había llegado antes, se registra un WARNING con las veces recibidas y el estado anterior, pero no se bloquea la creación de otro draft.
Observaciones guardadas
- Rechazo de una factura: la lista de observaciones completa (
codigo,titulo,filas,como_corregir; verobservaciones.py). - Error:
[{"codigo": "error_interno", "detalle": "..."}]. - SAP fuera de línea:
[{"codigo": "servicio_no_disponible", "detalle": "...", "intentos": n}]. - Correo sin comprobante: la observación del motivo (
sin_pdf_xml,falta_xml,sin_factura).
Las notificaciones las registra respuesta_proveedor (enviada, fallida u omitida con su motivo).
Notas
database_utilities.DB_NAMEesemails.dbjunto al código (ruta absoluta): no depende del directorio desde el que se lance el demonio, por ejemplo como servicio de Windows.- Las pruebas redirigen
DB_NAMEa una base temporal (fixture automático entests/conftest.py) para no escribir enemails.db.