chore: initial commit del proyecto SCAII_Sync_Client
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
218
motor_sincronizacion.py
Normal file
218
motor_sincronizacion.py
Normal file
@@ -0,0 +1,218 @@
|
||||
import time
|
||||
import os
|
||||
from typing import List, Dict, Any, Tuple, Optional
|
||||
from registros.scaii.reg_501 import Generador501 as Gen501SCAII
|
||||
from registros.scaf.reg_501 import Generador501SCAF as Gen501SCAF
|
||||
from registros.scaii.reg_502 import Generador502SCAII as Gen502SCAII
|
||||
from registros.scaf.reg_502 import Generador502SCAF as Gen502SCAF
|
||||
from registros.scaii.reg_503 import Generador503SCAII as Gen503SCAII
|
||||
from registros.scaf.reg_503 import Generador503SCAF as Gen503SCAF
|
||||
from registros.scaii.reg_504 import Generador504SCAII as Gen504SCAII
|
||||
from registros.scaf.reg_504 import Generador504SCAF as Gen504SCAF
|
||||
from registros.scaii.reg_505 import Generador505SCAII as Gen505SCAII
|
||||
from registros.scaf.reg_505 import Generador505SCAF as Gen505SCAF
|
||||
from registros.scaii.reg_551 import Generador551SCAII as Gen551SCAII
|
||||
from registros.scaf.reg_551 import Generador551SCAF as Gen551SCAF
|
||||
from registros.scaii.reg_554 import Generador554SCAII as Gen554SCAII
|
||||
from registros.scaf.reg_554 import Generador554SCAF as Gen554SCAF
|
||||
from registros.scaii.reg_558 import Generador558 as Gen558SCAII
|
||||
from registros.scaf.reg_558 import Generador558SCAF as Gen558SCAF
|
||||
from logger_utils import log
|
||||
|
||||
class MotorSincronizacion:
|
||||
def __init__(self, gestor_db, cliente_api):
|
||||
self.db = gestor_db
|
||||
self.api = cliente_api
|
||||
self.tipo_moneda_global = "ME"
|
||||
self.callback_progreso = None
|
||||
self.callback_fila = None
|
||||
self.callback_estado_texto = None
|
||||
self.callback_actualizar_fila = None
|
||||
|
||||
def proceso_principal(self):
|
||||
"""Metodo de entrada robusto para la Interfaz"""
|
||||
config_datos = self.db.config
|
||||
pedimento = config_datos.get("pedimento")
|
||||
sistema = config_datos.get("sistema", "SCAII")
|
||||
log.info(f"Iniciando proceso principal para Pedimento: {pedimento}, Sistema: {sistema}")
|
||||
|
||||
if getattr(self, "callback_estado_texto", None):
|
||||
self.callback_estado_texto("Iniciando conexiones...")
|
||||
|
||||
# 1. Conectar a la Base de Datos
|
||||
if not self.db.conectar():
|
||||
if getattr(self, "callback_estado_texto", None):
|
||||
self.callback_estado_texto("Error: Fallo conexión SQL Server.")
|
||||
return False
|
||||
|
||||
# 1b. Leer credenciales RPA de GEmpresa (igual que WinDev en producción)
|
||||
# Si vienen por parámetro (/RPAUser), se usan esas. Si no, se leen de la BD.
|
||||
if not self.api.usuario or not self.api.password:
|
||||
rpa_user, rpa_pass = self.db.obtener_credenciales_rpa()
|
||||
if rpa_user and rpa_pass:
|
||||
self.api.usuario = rpa_user
|
||||
self.api.password = rpa_pass
|
||||
else:
|
||||
if getattr(self, "callback_estado_texto", None):
|
||||
self.callback_estado_texto("Error: No hay credenciales RPA en GEmpresa.")
|
||||
log.error("No se encontraron credenciales RPA en GEmpresa. Configura NOMBREBDINTER y CLAVEPREVALIDADOR.")
|
||||
return False
|
||||
|
||||
# 2. Obtener Token de la API
|
||||
if not self.api.obtener_token():
|
||||
if getattr(self, "callback_estado_texto", None):
|
||||
self.callback_estado_texto("Error: Fallo de autenticación API.")
|
||||
return False
|
||||
|
||||
if not pedimento: return False
|
||||
|
||||
return self.ejecutar_logica_generacion(pedimento, sistema)
|
||||
|
||||
def ejecutar_logica_generacion(self, pedimento: str, sistema: str) -> bool:
|
||||
"""Orquestador Gen_Regs"""
|
||||
# 1. Buscar pedimento (Intento 1: Tal cual viene)
|
||||
datos_pedi = self.db.obtener_datos_pedimento(pedimento, sistema)
|
||||
|
||||
# 2. Buscar pedimento (Intento 2: Sin guiones)
|
||||
if not datos_pedi and "-" in pedimento:
|
||||
log.info("Intento 2: Buscando pedimento sin guiones...")
|
||||
datos_pedi = self.db.obtener_datos_pedimento(pedimento.replace("-", ""), sistema)
|
||||
|
||||
# 3. Buscar pedimento (Intento 3: Solo los últimos 7 dígitos - El número consecutivo)
|
||||
if not datos_pedi and len(pedimento) >= 7:
|
||||
solo_consecutivo = pedimento.replace("-", "").replace(" ", "")[-7:]
|
||||
log.info(f"Intento 3: Buscando solo por consecutivo final: {solo_consecutivo}...")
|
||||
datos_pedi = self.db.obtener_datos_pedimento(solo_consecutivo, sistema)
|
||||
|
||||
if not datos_pedi:
|
||||
if getattr(self, "callback_estado_texto", None):
|
||||
self.callback_estado_texto(f"Error: Pedimento {pedimento} no encontrado.")
|
||||
return False
|
||||
|
||||
tipo_op = datos_pedi.get("Tipo")
|
||||
regimen = datos_pedi.get("Regimen", "")
|
||||
|
||||
if getattr(self, "callback_estado_texto", None):
|
||||
self.callback_estado_texto(f"Pedimento {tipo_op}/{regimen} encontrado. Buscando facturas...")
|
||||
log.info(f"Pedimento encontrado: {tipo_op}/{regimen}. Buscando facturas...")
|
||||
|
||||
datos_empresa = self.db.obtener_datos_empresa(sistema)
|
||||
facturas = self.db.obtener_detalle_facturas(pedimento, sistema, regimen, tipo_op)
|
||||
|
||||
if not facturas:
|
||||
if getattr(self, "callback_estado_texto", None):
|
||||
self.callback_estado_texto(f"Aviso: 0 facturas para {pedimento}")
|
||||
return False
|
||||
|
||||
self.facturas_cache = facturas
|
||||
totales = self.calcular_totales(facturas, self.tipo_moneda_global)
|
||||
|
||||
id_proveedor = facturas[0].get("IdProveedor")
|
||||
datos_cliente = self.db.obtener_datos_proveedor(id_proveedor, sistema)
|
||||
total_trailers = self.db.obtener_total_vehiculos(pedimento, sistema)
|
||||
|
||||
gen_501 = Gen501SCAII if sistema == "SCAII" else Gen501SCAF
|
||||
json_501 = gen_501.preparar_json(datos_pedi, totales, datos_cliente, total_trailers)
|
||||
|
||||
id_pedimento_api = self.api.enviar_registro_api("registro501", json_501)
|
||||
if not id_pedimento_api: return False
|
||||
|
||||
num_trailer = facturas[0].get("NumTrailer")
|
||||
datos_trailer = self.db.obtener_datos_trailer(num_trailer, sistema)
|
||||
gen_502 = Gen502SCAII if sistema == "SCAII" else Gen502SCAF
|
||||
self.api.enviar_registro_api("registro502", gen_502.preparar_json(id_pedimento_api, datos_trailer))
|
||||
|
||||
gen_503 = Gen503SCAII if sistema == "SCAII" else Gen503SCAF
|
||||
self.api.enviar_registro_api("registro503", gen_503.preparar_json(id_pedimento_api, facturas[0]))
|
||||
|
||||
log.info(f"Procesando {len(facturas)} facturas...")
|
||||
|
||||
for idx, fac in enumerate(facturas):
|
||||
self.idx_actual = idx
|
||||
if self.callback_fila:
|
||||
self.callback_fila(pedimento, str(fac.get("NumFactura")), str(fac.get("Remesa")), fac, "Procesando...")
|
||||
time.sleep(0.02)
|
||||
|
||||
gen_504 = Gen504SCAII if sistema == "SCAII" else Gen504SCAF
|
||||
for j504 in gen_504.preparar_listado_json(id_pedimento_api, fac.get("ContenedoresTipo")):
|
||||
self.api.enviar_registro_api("registro504", j504)
|
||||
|
||||
id_prov = fac.get("IdProveedor")
|
||||
prov_data = self.db.obtener_datos_proveedor(id_prov, sistema)
|
||||
trans_data = self.db.obtener_datos_transporte(fac.get("Transportista"), sistema)
|
||||
|
||||
gen_505 = Gen505SCAII if sistema == "SCAII" else Gen505SCAF
|
||||
json_505 = gen_505.preparar_json(fac, id_pedimento_api, datos_empresa, prov_data, trans_data, self.tipo_moneda_global, 1.0)
|
||||
json_505["tipo_operacion_505"] = tipo_op
|
||||
|
||||
id_factura_api = self.api.enviar_registro_api("registro505", json_505)
|
||||
|
||||
if id_factura_api:
|
||||
self.ejecutar_generacion_partidas(fac, id_factura_api, sistema, tipo_op, regimen, json_505)
|
||||
if getattr(self, "callback_actualizar_fila", None):
|
||||
self.callback_actualizar_fila(idx, "Enviada correctamente")
|
||||
else:
|
||||
if getattr(self, "callback_actualizar_fila", None):
|
||||
self.callback_actualizar_fila(idx, "Error enviando")
|
||||
|
||||
if getattr(self, "callback_progreso", None):
|
||||
self.callback_progreso((idx + 1) / len(facturas))
|
||||
|
||||
time.sleep(0.05)
|
||||
log.info("Sincronización finalizada con éxito.")
|
||||
return True
|
||||
|
||||
def calcular_totales(self, facturas: List[Dict[str, Any]], moneda_objetivo: str) -> Dict[str, float]:
|
||||
totales = {"peso_bruto": 0.0, "bultos": 0.0, "flete": 0.0, "seguros": 0.0, "embalajes": 0.0, "otros_incrementa": 0.0}
|
||||
for fac in facturas:
|
||||
totales["peso_bruto"] += float(fac.get("PesoBruto") or 0)
|
||||
totales["bultos"] += float(fac.get("CANTBULTOS") or 0)
|
||||
tc = float(fac.get("TipoCambio") or 1.0)
|
||||
m_fac = fac.get("ClaveMoneda")
|
||||
def conv(v):
|
||||
val = float(v or 0)
|
||||
if moneda_objetivo == "ME": return val if m_fac == "ME" else val / tc
|
||||
return val * tc if m_fac == "MN" else val
|
||||
totales["flete"] += conv(fac.get("Flete"))
|
||||
totales["seguros"] += conv(fac.get("Seguros"))
|
||||
totales["embalajes"] += conv(fac.get("Embalajes"))
|
||||
totales["otros_incrementa"] += conv(fac.get("OtrosIncrementa"))
|
||||
return totales
|
||||
|
||||
def realizar_conversion_tarifa(self, cant_c: float, um_c: str, frac: str, peso: float, sist: str) -> Tuple[float, str]:
|
||||
cl_um = self.db.obtener_um_tarifa_fraccion(frac, sist)
|
||||
if not cl_um: return cant_c, ""
|
||||
if str(cl_um) == "1": return peso, "1"
|
||||
um_s = self.db.obtener_unidad_aduana_scaii(clave_aduana=cl_um)
|
||||
if not um_s or um_s == um_c: return cant_c, str(cl_um)
|
||||
t, f = self.db.obtener_conversion_unidades(um_c, um_s)
|
||||
cant_t = cant_c * f if t == "M" else (cant_c / f if f != 0 else cant_c)
|
||||
return cant_t, str(cl_um)
|
||||
|
||||
def ejecutar_generacion_partidas(self, fac: Dict[str, Any], id_f: int, sist: str, t_op: str, reg: str, j505: Dict[str, Any]):
|
||||
partidas = self.db.obtener_partidas_factura(fac.get("Consecutivo"), sist, t_op, reg)
|
||||
if not partidas: return
|
||||
for i, par in enumerate(partidas, start=1):
|
||||
if getattr(self, "callback_estado_texto", None):
|
||||
self.callback_estado_texto(f"Enviando Partida {i}/{len(partidas)}...")
|
||||
|
||||
# Obtener datos de la unidad de medida para el mapeo numérico
|
||||
um_raw = par.get("UnidadMedida", "")
|
||||
um_data = self.db.obtener_datos_unidad_medida(um_raw, sist) if um_raw else None
|
||||
|
||||
ct, ut = self.realizar_conversion_tarifa(float(par.get("Cantidad") or 0), par.get("UnidadMedida", ""), par.get("Fraccion", ""), float(par.get("PesoNeto") or 0), sist)
|
||||
if sist == "SCAII":
|
||||
j551 = Gen551SCAII.preparar_json(par, id_f, {}, um_data, str(fac.get("MetodoValoracion") or ""), j505["moneda_facturacion_505"], fac.get("Remesa"), i, fac.get("TipoMoneda", "ME"))
|
||||
else:
|
||||
j551 = Gen551SCAF.preparar_json(par, id_f, {}, um_data, str(fac.get("MetodoValoracion") or ""), j505["moneda_facturacion_505"], fac.get("Remesa"), i)
|
||||
j551["cantidad_tarifa_551"] = ct
|
||||
if ut: j551["unidad_medida_tarifa_551"] = ut
|
||||
j551["numero_partida_remesa_551"] = str(par.get("Linea", i))
|
||||
id_p = self.api.enviar_registro_api("registro551", j551)
|
||||
if id_p:
|
||||
mod = ("SCAII" if sist == "SCAII" else "SCAF") + ("-IT" if t_op == "I" and reg != "IMD" else ("-ID" if t_op == "I" else ("-E" if sist == "SCAII" else "-ER")))
|
||||
for iden in self.db.obtener_identificadores_partida(fac.get("Consecutivo"), par.get("Linea"), mod):
|
||||
self.api.enviar_registro_api("registro554", (Gen554SCAII if sist == "SCAII" else Gen554SCAF).preparar_json(iden, id_p))
|
||||
if par.get("InfoAdic", "").strip():
|
||||
self.api.enviar_registro_api("registro558", (Gen558SCAII if sist == "SCAII" else Gen558SCAF).preparar_json(id_p, par.get("InfoAdic")))
|
||||
time.sleep(0.01)
|
||||
Reference in New Issue
Block a user