proyecto en producción
This commit is contained in:
commit
457ca296fa
9 files changed
+1009
No files matched your search
@@ -0,0 +1,259 @@
|
||||
import json
|
||||
import time
|
||||
from datetime import datetime
|
||||
from dotenv import load_dotenv
|
||||
|
||||
import logger_utilities
|
||||
import sunat_manager
|
||||
import hana_manager
|
||||
import sapb1_manager
|
||||
from constants import ESTADOS_COMPROBANTE
|
||||
from hana_manager import actualizar_documento_sap
|
||||
|
||||
# ============================================================================
|
||||
def es_hora_pico(dt: datetime) -> bool:
|
||||
minutos = dt.hour * 60 + dt.minute
|
||||
return (360 <= minutos < 795) or (840 <= minutos < 1050)
|
||||
|
||||
# ============================================================================
|
||||
def cargar_empresas():
|
||||
with open("empresas.json", "r", encoding="utf-8") as f:
|
||||
return json.load(f)
|
||||
|
||||
# ============================================================================
|
||||
def enviar_mensaje_sl(usercode, doc, resultado, schema):
|
||||
estado_cp = resultado.get("data", {}).get("estadoCp", "")
|
||||
estado_texto = ESTADOS_COMPROBANTE.get(estado_cp, "DESCONOCIDO")
|
||||
|
||||
subject = f"❌ SUNAT - {doc['Indicator']}-{doc['FolioPref']}-{doc['FolioNum']}"
|
||||
observaciones = resultado.get("data", {}).get("observaciones", [""])[0] or ""
|
||||
|
||||
text = (
|
||||
f"El documento {doc['Indicator']}-{doc['FolioPref']}-{doc['FolioNum']} "
|
||||
f"de {schema} entrego el siguiente estado por SUNAT.\n"
|
||||
f"Estado: {estado_texto} (código: {estado_cp})\n"
|
||||
f"RUC Proveedor: {doc.get('LicTradNum', 'N/A')}\n"
|
||||
f"Razón Social: {doc.get('CardName', 'N/A')}\n"
|
||||
f"Mensaje SUNAT: {resultado.get('message', 'N/A')}\n"
|
||||
)
|
||||
|
||||
if observaciones:
|
||||
text += f"Observaciones SUNAT: {observaciones}\n"
|
||||
|
||||
payload = {
|
||||
"MessageDataColumns": [
|
||||
{
|
||||
"ColumnName": "Numero",
|
||||
"Link": "tNO",
|
||||
"MessageDataLines": [
|
||||
{
|
||||
"Object":"",
|
||||
"ObjectKey":"",
|
||||
"Value":f"{doc['Indicator']}-{doc['FolioPref']}-{doc['FolioNum']} "
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ColumnName": "Documento",
|
||||
"Link": "tYES",
|
||||
"MessageDataLines": [
|
||||
{
|
||||
"Object": str(doc.get("ObjType", "18")),
|
||||
"ObjectKey": str(doc["DocEntry"]),
|
||||
"Value": str(doc["DocNum"])
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"ColumnName": "Razón Social / Nombre",
|
||||
"Link": "tNO",
|
||||
"MessageDataLines": [
|
||||
{
|
||||
"Object": "",
|
||||
"ObjectKey": "",
|
||||
"Value": f"{doc.get('CardName', 'N/A')}"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"RecipientCollection": [
|
||||
{
|
||||
"SendInternal": "tYES",
|
||||
"UserCode": usercode
|
||||
}
|
||||
],
|
||||
"Subject": subject,
|
||||
"Text": text
|
||||
}
|
||||
|
||||
|
||||
|
||||
print(f" ┌─ 📩 Enviando Messages a {usercode} ───────────")
|
||||
# for line in json.dumps(payload, indent=2).splitlines():
|
||||
# print(f" │ {line}")
|
||||
sapb1_manager.send_message(payload)
|
||||
print(f" │ ✅ Mensaje enviado a {usercode}")
|
||||
print(f" └───────────────────────────────────────────────")
|
||||
|
||||
# ============================================================================
|
||||
def procesar_empresa(schema, datos_empresa, modo):
|
||||
print(f"\n{'='*60}")
|
||||
print(f"🏢 Procesando empresa: {schema}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
if modo == "rapido":
|
||||
documentos = hana_manager.obtener_documentos_creados_actualizados_hace_x_minutos(schema, 60)
|
||||
else:
|
||||
documentos = hana_manager.obtener_documentos_fecha_emision_ultimos_8_dias(schema)
|
||||
if not documentos:
|
||||
print(f"⚠️ No se encontraron documentos para {schema}")
|
||||
return 0, 0
|
||||
|
||||
## documentos = hana_manager.obtener_documentos_creados_hace_x_dias(schema, 8)
|
||||
|
||||
print(f"📄 Documentos encontrados: {len(documentos)}")
|
||||
|
||||
print(f"🔑 Solicitando token SUNAT...")
|
||||
token = sunat_manager.token(datos_empresa)
|
||||
if not token:
|
||||
print(f"❌ No se obtuvo token para {schema}")
|
||||
return 0, len(documentos)
|
||||
|
||||
print(f"✅ Token obtenido correctamente")
|
||||
|
||||
sapb1_manager.login()
|
||||
print(f"✅ Sesión SL establecida para {schema}")
|
||||
|
||||
ruc_empresa = datos_empresa["Ruc"]
|
||||
procesados = 0
|
||||
errores = 0
|
||||
|
||||
try:
|
||||
for idx, doc in enumerate(documentos, start=1):
|
||||
print(f"\n--- [{idx}/{len(documentos)}] {doc['FolioPref']}-{doc['FolioNum']} ---")
|
||||
|
||||
try:
|
||||
payload = {
|
||||
"numRuc": doc["LicTradNum"],
|
||||
"codComp": "R1" if doc["Indicator"] == "02" else doc["Indicator"],
|
||||
"numeroSerie": doc["FolioPref"],
|
||||
"numero": doc["FolioNum"],
|
||||
"fechaEmision": doc["TaxDate"],
|
||||
}
|
||||
|
||||
if doc["FolioPref"] and doc["FolioPref"][0].isalpha():
|
||||
payload["monto"] = str(doc["DocTotal"])
|
||||
|
||||
print(f" ┌─ 📤 Enviando a SUNAT {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}─────────────────────")
|
||||
for line in json.dumps(payload, indent=2).splitlines():
|
||||
print(f" │ {line}")
|
||||
print(f" └────────────────────────────────────────────")
|
||||
resultado = sunat_manager.validar_comprobante(token, ruc_empresa, payload)
|
||||
print(f" ┌─ 📥 Respuesta de SUNAT {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}───────────────────")
|
||||
for line in json.dumps(resultado, indent=2).splitlines():
|
||||
print(f" │ {line}")
|
||||
print(f" └────────────────────────────────────────────")
|
||||
|
||||
registro = {
|
||||
"objtype": str(doc["ObjType"]),
|
||||
"companydb": schema,
|
||||
"fieldvalue": doc["DocEntry"],
|
||||
}
|
||||
|
||||
actualizar_documento_sap(schema, registro, resultado)
|
||||
|
||||
if resultado.get("success"):
|
||||
estado_cp = resultado.get("data", {}).get("estadoCp", "")
|
||||
print(f"✅ OK - Estado CP: {estado_cp}")
|
||||
|
||||
if estado_cp not in ["1","3"] and doc.get("UserCode"):
|
||||
enviar_mensaje_sl(doc["UserCode"], doc, resultado, schema)
|
||||
|
||||
else:
|
||||
print(f"⚠️ Falló - {resultado.get('message', 'Error desconocido')}")
|
||||
|
||||
procesados += 1
|
||||
|
||||
if idx < len(documentos):
|
||||
time.sleep(2)
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ Error procesando documento: {e}")
|
||||
errores += 1
|
||||
finally:
|
||||
sapb1_manager.logout()
|
||||
print(f"🔒 Sesión SL cerrada para {schema}")
|
||||
|
||||
return procesados, errores
|
||||
|
||||
# ============================================================================
|
||||
def main():
|
||||
log_path = logger_utilities.setup_logger()
|
||||
logger_utilities.limpiar_logs_antiguos(dias=7)
|
||||
|
||||
load_dotenv(dotenv_path=".env.dev")
|
||||
|
||||
empresas = cargar_empresas()
|
||||
print(f"📋 Empresas cargadas: {len(empresas)}")
|
||||
|
||||
print("=" * 60)
|
||||
print("🚀 Demonio iniciado")
|
||||
print(f"📄 Log guardado en: {log_path}")
|
||||
print(" Horario pico (cada 10 seg): 06:00 - 13:15 | 14:00 - 17:30")
|
||||
print(" Horario valle (cada 30 min): resto del día")
|
||||
print("=" * 60)
|
||||
|
||||
while True:
|
||||
inicio_ciclo = datetime.now()
|
||||
|
||||
logger_utilities.rotar_log()
|
||||
logger_utilities.limpiar_logs_antiguos(dias=7)
|
||||
|
||||
modo = "rapido" if es_hora_pico(inicio_ciclo) else "lento"
|
||||
|
||||
print(f"\n{'='*60}")
|
||||
print(f"🔄 Ciclo [{modo.upper()}] {inicio_ciclo.strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
total_procesados = 0
|
||||
total_errores = 0
|
||||
|
||||
for empresa in empresas:
|
||||
schema = empresa["CompanyDB"]
|
||||
|
||||
if empresa.get("Locked") == "Y":
|
||||
print(f"\n⏭️ Empresa {schema} está bloqueada (LOCKED=Y), saltando...")
|
||||
continue
|
||||
|
||||
sapb1_manager._sap_credentials = {
|
||||
"company": empresa["CompanyDB"],
|
||||
"username": empresa["UserName"],
|
||||
"password": empresa["Password"],
|
||||
}
|
||||
|
||||
try:
|
||||
p, e = procesar_empresa(schema, empresa, modo)
|
||||
total_procesados += p
|
||||
total_errores += e
|
||||
except Exception as ex:
|
||||
print(f"❌ Error procesando empresa {schema}: {ex}")
|
||||
total_errores += 1
|
||||
|
||||
duracion_ciclo = datetime.now() - inicio_ciclo
|
||||
print(f"\n📊 CICLO COMPLETADO en {str(duracion_ciclo).split('.')[0]}")
|
||||
print(f" ✅ {total_procesados} procesados | ❌ {total_errores} errores")
|
||||
|
||||
transcurrido = duracion_ciclo.total_seconds()
|
||||
modo_ahora = "rapido" if es_hora_pico(datetime.now()) else "lento"
|
||||
intervalo = 10 if modo_ahora == "rapido" else 1800
|
||||
dormir = max(0, intervalo - transcurrido)
|
||||
|
||||
print(f"💤 Próximo ciclo [{modo_ahora}] en {dormir:.0f}s...")
|
||||
time.sleep(dormir)
|
||||
|
||||
# ============================================================================
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print("\n\n🛑 Demonio detenido por el usuario.")
|
||||
Reference in new issue
Block a user