feat: paso 4 ventas en automation + fixes varios
- Automation: agrega Paso 4 (Ventas/Crear) con fetch _SQL_VENTAS, filtro por excluded_ventas_set, loop de envío y log de actividad - Automation: _guardar_envio ahora almacena idrecepcion y contrato para que /ventas reconozca registros enviados por automation - Contratos: corrige NameError en toggle_excluir (faltaba request: Request) - Ventas: agrega check if not q en send_one y send para evitar TypeError - Ventas HTML: agrega columna Contrato en la tabla de facturas - json_generator: normaliza etnia >2 chars a '99' (ej: 999 → 99) - json_generator: agrega campo ciudadExp usando el mismo cod_ciudad Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
21eb99d18d
commit
73062af8bc
+114
-9
@@ -11,10 +11,11 @@ from app.services.json_generator import (
|
||||
generar_tercero_api,
|
||||
generar_rda_paciente,
|
||||
agrupar_por_recepcion,
|
||||
generar_factura_venta,
|
||||
)
|
||||
from app.services.api_client import get_tns_token, TNS_BASE
|
||||
from app.services.whatsapp_sync import sync_paciente, sync_todos, guardar_sync_log
|
||||
from app.routes.contratos import load_contrato_map, load_excluded_set, load_sin_contrato_set
|
||||
from app.routes.contratos import load_contrato_map, load_excluded_set, load_sin_contrato_set, load_excluded_ventas_set
|
||||
from app.utils.activity import log_activity, get_ip
|
||||
|
||||
router = APIRouter(prefix="/automation", tags=["automation"])
|
||||
@@ -152,6 +153,31 @@ ORDER BY ps.ID_PS, rel.COD_EXAMEN
|
||||
"""
|
||||
|
||||
|
||||
_SQL_VENTAS = """
|
||||
SELECT
|
||||
r.IDRECEPCION,
|
||||
r.PREFIJO,
|
||||
r.NUM_FACTURA,
|
||||
r.FECHA_RECEPCION,
|
||||
r.COD_PACIENTE,
|
||||
r.NIT_EMPRESA,
|
||||
r.VALORTOTAL,
|
||||
r.VALORDESC,
|
||||
rel.COD_EXAMEN,
|
||||
COALESCE(NULLIF(TRIM(ex.NUM_ISS), ''), TRIM(rel.COD_EXAMEN)) AS CUPS,
|
||||
rel.PRECIO,
|
||||
COALESCE(t.VALOR, rel.PRECIO) AS PRECIO_TARIFA,
|
||||
TRIM(e.CODCONTRATO) AS CODCONTRATO
|
||||
FROM RECEPCION r
|
||||
JOIN RELACION rel ON rel.IDRECEPCION = r.IDRECEPCION
|
||||
LEFT JOIN EXAMEN ex ON TRIM(ex.CODIGO) = TRIM(rel.COD_EXAMEN)
|
||||
LEFT JOIN EMPRESA e ON e.NIT = r.NIT_EMPRESA
|
||||
LEFT JOIN TARIFA t ON TRIM(t.COD_EXAMEN) = TRIM(rel.COD_EXAMEN) AND t.TARIFA = e.TARIFA
|
||||
WHERE r.FECHA_RECEPCION BETWEEN :fecha_ini AND :fecha_fin
|
||||
ORDER BY r.IDRECEPCION
|
||||
"""
|
||||
|
||||
|
||||
def _agrupar_por_presserv(rows: list) -> dict:
|
||||
"""Agrupa filas de pre-servicios por ID_PS."""
|
||||
from collections import defaultdict
|
||||
@@ -267,11 +293,17 @@ async def preview_automation(
|
||||
return JSONResponse({"success": False, "message": f"Error BD RDA: {err2}"})
|
||||
|
||||
ok3, err3, rows_ps = fb.execute_query(_SQL_PRESERV, {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin})
|
||||
fb.disconnect()
|
||||
if not ok3:
|
||||
fb.disconnect()
|
||||
return JSONResponse({"success": False, "message": f"Error BD Pre-servicios: {err3}"})
|
||||
|
||||
ok4, err4, rows_vta = fb.execute_query(_SQL_VENTAS, {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin})
|
||||
fb.disconnect()
|
||||
if not ok4:
|
||||
return JSONResponse({"success": False, "message": f"Error BD Ventas: {err4}"})
|
||||
|
||||
excluded = load_excluded_set()
|
||||
excluded_ventas = load_excluded_ventas_set()
|
||||
|
||||
grupos_all = agrupar_por_recepcion(rows_rda)
|
||||
grupos = {k: v for k, v in grupos_all.items()
|
||||
@@ -345,6 +377,25 @@ async def preview_automation(
|
||||
if rda_por_pac.get(str(p.get("CODIGO", "")))
|
||||
]
|
||||
|
||||
grupos_vta_all = agrupar_por_recepcion(rows_vta)
|
||||
grupos_vta = {k: v for k, v in grupos_vta_all.items()
|
||||
if str(v[0].get("CODCONTRATO") or "").strip() not in excluded_ventas}
|
||||
|
||||
ventas_preview = []
|
||||
for id_rec, grupo_rows in grupos_vta.items():
|
||||
num_fac = str(grupo_rows[0].get("NUM_FACTURA") or "").strip()
|
||||
prefijo = str(grupo_rows[0].get("PREFIJO") or "").strip()
|
||||
venta_json = generar_factura_venta(grupo_rows, default_vendedor="00",
|
||||
default_prefijo=prefijo_def, numero_override=num_fac)
|
||||
ventas_preview.append({
|
||||
"factura": f"{prefijo}-{num_fac}",
|
||||
"paciente": str(grupo_rows[0].get("COD_PACIENTE", "")),
|
||||
"fecha": str(grupo_rows[0].get("FECHA_RECEPCION", ""))[:10],
|
||||
"contrato": str(grupo_rows[0].get("CODCONTRATO") or "").strip(),
|
||||
"examenes": len(grupo_rows),
|
||||
"json": venta_json,
|
||||
})
|
||||
|
||||
return JSONResponse({
|
||||
"success": True,
|
||||
"fecha": fecha,
|
||||
@@ -353,8 +404,10 @@ async def preview_automation(
|
||||
"examenes": total_examenes,
|
||||
"excluidos": excluidos_count,
|
||||
"preservicios": len(grupos_ps),
|
||||
"ventas": len(grupos_vta),
|
||||
"pacientes_preview": pacientes_preview,
|
||||
"preservicios_preview": preservicios_preview,
|
||||
"ventas_preview": ventas_preview,
|
||||
})
|
||||
|
||||
|
||||
@@ -401,15 +454,21 @@ async def run_automation(
|
||||
|
||||
# ── Pacientes EPS (para registrar terceros en paso 1) ─────────────────────
|
||||
ok4, err4, rows_pac_eps = fb.execute_query(_SQL_PACIENTES_EPS, {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin})
|
||||
fb.disconnect()
|
||||
if not ok4:
|
||||
fb.disconnect()
|
||||
return JSONResponse({"success": False, "message": f"Error Firebird pacientes EPS: {err4}"})
|
||||
|
||||
# ── Ventas (Factura Venta) ────────────────────────────────────────────────
|
||||
ok5, err5, rows_vta = fb.execute_query(_SQL_VENTAS, {"fecha_ini": fecha_ini, "fecha_fin": fecha_fin})
|
||||
fb.disconnect()
|
||||
if not ok5:
|
||||
return JSONResponse({"success": False, "message": f"Error Firebird Ventas: {err5}"})
|
||||
|
||||
# Fusionar pacientes: particulares + EPS, deduplicando por CODIGO
|
||||
codigos_vistos = {str(r.get("CODIGO")) for r in rows_pac}
|
||||
rows_pac_todos = list(rows_pac) + [r for r in rows_pac_eps if str(r.get("CODIGO")) not in codigos_vistos]
|
||||
|
||||
if not rows_pac_todos and not rows_rda and not rows_ps:
|
||||
if not rows_pac_todos and not rows_rda and not rows_ps and not rows_vta:
|
||||
return JSONResponse({"success": False, "message": f"No hay datos para la fecha {fecha}"})
|
||||
|
||||
# ── Login TNS ─────────────────────────────────────────────────────────────
|
||||
@@ -430,6 +489,7 @@ async def run_automation(
|
||||
"paso1_terceros": {"enviados": 0, "errores": 0, "detalle": []},
|
||||
"paso2_rda": {"enviados": 0, "errores": 0, "detalle": []},
|
||||
"paso3_preserv": {"enviados": 0, "errores": 0, "detalle": []},
|
||||
"paso4_ventas": {"enviados": 0, "errores": 0, "detalle": []},
|
||||
}
|
||||
|
||||
# ── PASO 1: Enviar Terceros (particulares + EPS) ─────────────────────────
|
||||
@@ -558,27 +618,72 @@ async def run_automation(
|
||||
fecha_inicio=fecha, fecha_fin=fecha,
|
||||
servicios=len(grupo_rows))
|
||||
|
||||
# ── PASO 4: Enviar Facturas Venta ─────────────────────────────────────────
|
||||
excluded_ventas = load_excluded_ventas_set()
|
||||
grupos_vta_all = agrupar_por_recepcion(rows_vta)
|
||||
grupos_vta = {k: v for k, v in grupos_vta_all.items()
|
||||
if str(v[0].get("CODCONTRATO") or "").strip() not in excluded_ventas}
|
||||
resultado["paso4_ventas"]["excluidos"] = len(grupos_vta_all) - len(grupos_vta)
|
||||
|
||||
endpoint_venta = f"{TNS_BASE}/v2/facturacion/Ventas/Crear"
|
||||
async with httpx.AsyncClient(timeout=timeout) as client:
|
||||
for id_rec, grupo_rows in grupos_vta.items():
|
||||
num_fac = str(grupo_rows[0].get("NUM_FACTURA") or "").strip()
|
||||
prefijo = str(grupo_rows[0].get("PREFIJO") or "").strip()
|
||||
venta_json = generar_factura_venta(grupo_rows, default_vendedor="00",
|
||||
default_prefijo=prefijo_def, numero_override=num_fac)
|
||||
factura_display = f"{prefijo}-{num_fac}"
|
||||
contrato_vta = str(grupo_rows[0].get("CODCONTRATO") or "").strip()
|
||||
try:
|
||||
resp = await client.post(endpoint_venta, json=venta_json, headers=headers)
|
||||
ok = resp.is_success
|
||||
msg = resp.json().get("message", "") if ok else resp.text[:200]
|
||||
except Exception as e:
|
||||
ok = False
|
||||
msg = str(e)
|
||||
|
||||
if ok:
|
||||
resultado["paso4_ventas"]["enviados"] += 1
|
||||
else:
|
||||
resultado["paso4_ventas"]["errores"] += 1
|
||||
|
||||
resultado["paso4_ventas"]["detalle"].append({
|
||||
"factura": factura_display,
|
||||
"paciente": str(grupo_rows[0].get("COD_PACIENTE", "")),
|
||||
"examenes": len(grupo_rows),
|
||||
"ok": ok, "msg": msg,
|
||||
})
|
||||
|
||||
_guardar_envio(user["user_id"], "ventas", factura_display, venta_json, msg, ok,
|
||||
fecha_inicio=fecha, fecha_fin=fecha, servicios=len(grupo_rows),
|
||||
idrecepcion=id_rec, contrato=contrato_vta)
|
||||
|
||||
r1 = resultado["paso1_terceros"]
|
||||
r2 = resultado["paso2_rda"]
|
||||
r3 = resultado["paso3_preserv"]
|
||||
r4 = resultado["paso4_ventas"]
|
||||
log_activity(user["user_id"], user["username"], "automation_run",
|
||||
f"Fecha {fecha} | Terceros: {r1['enviados']} OK/{r1['errores']} err | "
|
||||
f"RDA: {r2['enviados']} OK/{r2['errores']} err | "
|
||||
f"PreServ: {r3['enviados']} OK/{r3['errores']} err",
|
||||
f"PreServ: {r3['enviados']} OK/{r3['errores']} err | "
|
||||
f"Ventas: {r4['enviados']} OK/{r4['errores']} err",
|
||||
get_ip(request))
|
||||
return JSONResponse({"success": True, "resultado": resultado})
|
||||
|
||||
|
||||
def _guardar_envio(user_id, tipo, factura, json_data, respuesta, ok,
|
||||
fecha_inicio=None, fecha_fin=None, servicios=0):
|
||||
fecha_inicio=None, fecha_fin=None, servicios=0,
|
||||
idrecepcion=None, contrato=None):
|
||||
try:
|
||||
conn = get_connection()
|
||||
conn.execute("""
|
||||
INSERT INTO envios (user_id, tipo, factura, fecha_inicio, fecha_fin,
|
||||
INSERT INTO envios (user_id, tipo, factura, idrecepcion, contrato,
|
||||
fecha_inicio, fecha_fin,
|
||||
pacientes_count, servicios_count, status, json_enviado, respuesta_api, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
user_id, tipo, factura, fecha_inicio, fecha_fin,
|
||||
user_id, tipo, factura, idrecepcion, contrato,
|
||||
fecha_inicio, fecha_fin,
|
||||
1, servicios,
|
||||
"success" if ok else "error",
|
||||
json_lib.dumps(json_data, indent=2, ensure_ascii=False)[:10000],
|
||||
|
||||
Reference in New Issue
Block a user