se agrega fallback OWTR y calculo de fecha de vencimiento por condicion de pago

- hana_manager.get_delivery ahora busca en ODLN y, si no encuentra, en OWTR
- sapb1_manager resuelve StockTransfers -> Orders (via U_IDC_NUMOV) para
  validar precios de flete cuando la guia es un traslado de inventario
- se corrige el orden de parametros tipo/serie/numero (sin cruces) en
  get_delivery, get_delivery_notes y _get_delivery_entries
- se agrega sapb1_manager.obtener_condicion_pago (BusinessPartners +
  PaymentTermsTypes) y pipeline._calcular_fecha_vencimiento para que
  DocDueDate del draft sea fecha de contabilizacion + dias de la condicion
  de pago del proveedor
- se agrega variante de regex para "VALOR REFER" truncado en sunat_schema.py
- se actualiza documentacion (docs/) reflejando estos cambios

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
georgeandClaude Sonnet 5 committed 2026-09-10 13:21:04 -05:00
1 parent 6b0ed6f519
commit 15e3b5079c
231 files changed
+21421 -475

No files matched your search

+17 -5
View File
@@ -11,15 +11,16 @@ warnings.filterwarnings("ignore")
# CONFIGURACIÓN
# ============================================================================
# Cargar variables de entorno desde .env
load_dotenv(dotenv_path=".env")
# Cargar variables de entorno desde .env (ruta absoluta: no depende del
# directorio de trabajo desde el que se lance el proceso)
load_dotenv(dotenv_path=os.path.join(os.path.dirname(__file__), ".env"))
# Configuración de la API
CLIENT_ID = os.getenv("CLIENT_ID", "")
CLIENT_SECRET = os.getenv("CLIENT_SECRET", "")
TENANT_ID = os.getenv("TENANT_ID", "")
DOWNLOAD_FOLDER = os.getenv("DOWNLOAD_FOLDER","")
VERIFY_SSL = os.getenv("VERIFI_SSL", "False").lower() == "true"
VERIFY_SSL = os.getenv("VERIFY_SSL", "False").lower() == "true"
# ============================================================================
def get_access_token() -> str:
@@ -149,16 +150,17 @@ def download_attachments(user_email: str, message_id: str, token: str, download_
return downloaded_files
# ============================================================================
def send_email(access_token: str, from_email: str, to_recipients: list[str], subject: str, html_body: str, attachment_paths: list[str] | None = None) -> bool:
def send_email(access_token: str, from_email: str, to_recipients: list[str], subject: str, html_body: str, attachment_paths: list[str] | None = None, cc_recipients: list[str] | None = None) -> bool:
"""
Envía un correo electrónico con cuerpo HTML y múltiples adjuntos PDF.
:param access_token: Token de acceso de Microsoft Graph API.
:param from_email: Correo electrónico del remitente (cuenta de Office 365).
:param to_recipients: Lista de correos electrónicos de los destinatarios.
:param subject: Asunto del correo.
:param html_body: Contenido del correo en formato HTML.
:param attachment_paths: Lista de rutas a los archivos PDF que se adjuntarán.
:param cc_recipients: Lista de correos electrónicos en copia (CC).
:return: True si se envió correctamente, False en caso contrario.
"""
url = f"https://graph.microsoft.com/v1.0/users/{from_email}/sendMail"
@@ -178,6 +180,15 @@ def send_email(access_token: str, from_email: str, to_recipients: list[str], sub
}
})
# Construir lista de destinatarios en copia (CC) para Graph API
cc_payload = []
for email in (cc_recipients or []):
cc_payload.append({
"emailAddress": {
"address": email
}
})
# Construir lista de adjuntos
attachments_payload = []
if attachment_paths:
@@ -214,6 +225,7 @@ def send_email(access_token: str, from_email: str, to_recipients: list[str], sub
"content": html_body
},
"toRecipients": recipients_payload,
"ccRecipients": cc_payload,
"attachments": attachments_payload
},
"saveToSentItems": "true"