Files
GeorgeandClaude Sonnet 5.5 6030320822 se actualiza la documentación: sapb1_manager, nombres de funciones y cifras
- Nueva página docs/sapb1_manager.md: sesión, funciones, clasificación de
  errores (SapNoDisponible frente a rechazo de SAP), búsqueda de facturas
  existentes y cómo se ubica una guía.
- procesar_pdfs.md y procesar_xmls.md usaban los nombres anteriores a la
  división de pipeline.py (_procesar_pdfs, _ruta_carpeta...) y números de
  línea que envejecen; ahora apuntan a documentos.py.
- inventario.md: árbol de dependencias con los módulos nuevos.
- pipeline.md: _procesar_factura, importes_coinciden, hana_disponible y
  buscar_factura_existente; validar_precios.md, hana_manager.md, main.md y
  procesar_xmls.md con las notas que faltaban; README sin la cifra de pruebas
  vieja.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 14:56:34 -05:00

4.6 KiB

sapb1_manager.py — SAP Business One Service Layer

Cliente del Service Layer de SAP B1: crea los drafts de factura de compra, ubica las guías de remisión para validar precios, busca si una factura ya existe y obtiene datos maestros. Si el Service Layer no encuentra una guía, recurre a HANA (hana_manager.py).

Configuración (.env)

Variable Para qué Por defecto
SAP_B1_URL URL del Service Layer (por ejemplo https://servidor:50000/b1s/v1) —
SAP_B1_COMPANY, SAP_B1_USER, SAP_B1_PASSWORD Base de datos y credenciales —
SAP_B1_TIMEOUT Segundos de espera por petición 15
SAP_B1_VERIFY_SSL Verificar el certificado (true) false

Sesión

La sesión se abre sola en la primera petición (login) y se guarda en un diccionario del módulo. Ante un HTTP 401 se vuelve a iniciar sesión una vez y se repite la petición. No hay cierre automático: logout() existe pero el demonio no lo llama. Al ser un estado global del módulo, no está pensado para varios hilos.

Funciones

Función Qué hace
forward_to_sapb1(method, endpoint_path, json_data, params, extra_headers) Punto único de salida hacia SAP. Registra cada petición y clasifica los errores (ver abajo).
create_draft(payload) POST /Drafts. Devuelve el draft creado (con DocEntry).
buscar_factura_existente(ruc_emisor, tipo, serie, numero) ¿La factura ya está en SAP? Ver más abajo.
get_delivery_notes(tipo, serie, numero) Documento completo de una guía, con las líneas FL* de las que se validan los precios.
obtener_condicion_pago(card_code) Condición de pago del proveedor (para el vencimiento).
get_zonas() Maestro U_IDC_ZONAS (activas), para resolver el nombre de la zona de destino.
get_purchase_invoice(doc_entry) GET /PurchaseInvoices(DocEntry).
login() / logout() Inicio y cierre de sesión.

Errores: caída o rechazo

Todo error del Service Layer se lanza como ConnectionError, pero se distinguen dos casos:

Excepción Cuándo Qué hace el pipeline
SapNoDisponible (subclase de ConnectionError) Timeout, sin conexión, HTTP 5xx, o login imposible por esos motivos Es una caída: aviso "En cola" al proveedor y reintento del correo
ConnectionError HTTP 4xx (SAP rechazó la petición), credenciales inválidas Es un error nuestro: se avisa solo al equipo

Así, una caída nunca se confunde con "la guía no existe" ni se le comunica al proveedor como rechazo. Detalle del flujo en respuesta_proveedor.md.

¿La factura ya existe en SAP?

buscar_factura_existente busca por proveedor (CardCode = 'PL' + RUC), tipo, serie y número (U_BPP_MDTD, U_BPP_MDSD, U_BPP_MDCD, los mismos campos que llena el draft) en dos entidades, en este orden:

  1. PurchaseInvoices, solo las no anuladas (Cancelled eq 'tNO').
  2. Drafts de factura de compra (DocObjectCode eq 'oPurchaseInvoices'): una factura cuyo draft ya se creó aunque nadie lo haya contabilizado.

Devuelve {"entidad", "DocEntry", "DocNum"} del primer documento hallado, o None. Los valores se escapan para el filtro OData (las comillas simples se duplican). Si SAP no responde lanza SapNoDisponible: no se puede afirmar que la factura no exista.

Cómo se ubica una guía (get_delivery_notes)

  1. Service Layer: GET /DeliveryNotes con Cancelled eq 'tNO', el tipo, la serie y contains(U_BPP_MDCD, número) (coincidencia parcial del número).
  2. HANA, si Service Layer no devolvió nada o falló: consulta ODLN y, si no hay resultados, OWTR.
  3. Por cada documento hallado se hace GET a su entidad: DeliveryNotes ya trae las líneas FL*; en StockTransfers (traslados, OWTR) se lee U_IDC_NUMOV y se obtiene la orden de venta (Orders) asociada, que es la que lleva precios.

Si Service Layer cayó y HANA no aporta nada, se lanza SapNoDisponible en vez de devolver una lista vacía; igual si la caída ocurre al leer el documento. Una lista vacía significa solo "la guía no existe".

Notas

  • obtener_condicion_pago devuelve None ante cualquier error (incluida una caída): en ese caso el vencimiento del draft queda igual a la fecha de contabilización.
  • get_zonas no captura errores: si Service Layer no responde, lanza la excepción (y validate_prices la deja pasar como SapNoDisponible).
  • SAP_B1_VERIFY_SSL es false por defecto; en producción conviene activarlo con el certificado de la CA.