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],
|
||||
|
||||
+46
-45
@@ -7,52 +7,53 @@ from app.utils.activity import log_activity, get_ip
|
||||
router = APIRouter(prefix="/contratos", tags=["contratos"])
|
||||
|
||||
# (numero_contrato, nit_empresa, tipo_usuario, descripcion, excluir_rda, sin_contrato, excluir_ventas)
|
||||
# excluir_ventas=0 → activo para Ventas/Crear | excluir_ventas=1 → bloqueado para ventas
|
||||
_SEED = [
|
||||
("001", "860078828", "11", "EPS SANITAS", 0, 0, 0),
|
||||
("002", "830054904", "11", "EPS COMPENSAR", 0, 0, 0),
|
||||
("003", "900278729", "12", "PARTICULAR", 0, 0, 0),
|
||||
("004", "800106339", "11", "EPS NUEVA EPS", 0, 0, 0),
|
||||
("005", "800153424", "11", "EPS SURA", 0, 0, 0),
|
||||
("006", "805009741", "11", "EPS COOMEVA", 0, 0, 0),
|
||||
("007", "860002183", "11", "EPS FAMISANAR", 0, 0, 0),
|
||||
("008", "860002503", "11", "EPS CRUZ BLANCA", 0, 0, 0),
|
||||
("009", "860027404", "11", "EPS COLSANITAS", 0, 0, 0),
|
||||
("010", "860039988", "11", "EPS SALUD TOTAL", 0, 0, 0),
|
||||
("011", "890903790", "11", "EPS SAVIA SALUD", 0, 0, 0),
|
||||
("012", "900178724", "11", "EPS MUTUAL SER", 0, 0, 0),
|
||||
("034", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 0),
|
||||
("035", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 0),
|
||||
("036", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 0),
|
||||
("20062026", "800182856", "01", "SUBSIDIADO", 0, 0, 0),
|
||||
("CW225489", "899999068", "07", "POLIZA / SEGURO", 0, 0, 0),
|
||||
# Contratos excluidos del envío TNS
|
||||
("013", "", "11", "MEDILAVORO S.A.S", 0, 0, 0),
|
||||
("014", "", "11", "LABORATORIO UROCLINICO",0, 0, 0),
|
||||
("015", "", "11", "ANDRES AFANADOR VILLAMIZAR", 1, 0, 0),
|
||||
("016", "", "11", "OMAR FERNANDO RIBERO GOMEZ", 1, 0, 0),
|
||||
("017", "", "11", "CLAUDIA BELEN JULIO SEPULVEDA", 1, 0, 0),
|
||||
("018", "", "11", "LABORATORIO MICROBIOLOGICO - MARGIE OJEDA", 1, 0, 0),
|
||||
("019", "", "11", "LABORATORIO TOXICOLOGICO - MARTHA MORALES", 1, 0, 0),
|
||||
("020", "", "11", "MARTHA LUCIA GALLARDO", 1, 0, 0),
|
||||
("021", "", "11", "LABORATORIO VILMA OROZCO AYALA", 1, 0, 0),
|
||||
("022", "", "11", "CLINICA SAN JOSE DE CUCUTA", 1, 0, 0),
|
||||
("023", "", "11", "URONORTE S.A", 1, 0, 0),
|
||||
("024", "", "11", "LABORATORIO CLINICO BIOLAB S.A.S", 1, 0, 0),
|
||||
("025", "", "11", "JOEL LEONARDO CARRILLO CORREDOR", 1, 0, 0),
|
||||
("026", "", "11", "ROLANDO IVAN PENARANDA DEVIA", 1, 0, 0),
|
||||
("027", "", "11", "JOSE WILMER GARCIA CALDERON", 1, 0, 0),
|
||||
("028", "", "11", "ONCOMEDICAL IPS S.A.S", 1, 0, 0),
|
||||
("029", "", "11", "GENETIX S.A.S", 1, 0, 0),
|
||||
("030", "", "11", "NORFETUS S.A.S", 1, 0, 0),
|
||||
("031", "", "11", "TBTB GLOBAL LAB S.A.S", 1, 0, 0),
|
||||
("032", "", "11", "COLGENES S.A.S", 1, 0, 0),
|
||||
("033", "", "11", "IPS FIGURAS SPA CUCUTA S.A.S", 1, 0, 0),
|
||||
("037", "", "11", "CLINICA URGENCIAS LA MERCED", 1, 0, 0),
|
||||
("038", "", "11", "CLINICA COLSANITAS S.A.", 1, 0, 0),
|
||||
("039", "", "11", "GOMEZ GIL JOSE JESUS", 1, 0, 0),
|
||||
("001", "860078828", "11", "EPS SANITAS", 0, 0, 1),
|
||||
("002", "830054904", "11", "EPS COMPENSAR", 0, 0, 1),
|
||||
("003", "900278729", "12", "PARTICULAR", 0, 0, 1),
|
||||
("004", "800106339", "11", "EPS NUEVA EPS", 0, 0, 1),
|
||||
("005", "800153424", "11", "EPS SURA", 0, 0, 1),
|
||||
("006", "805009741", "11", "EPS COOMEVA", 0, 0, 1),
|
||||
("007", "860002183", "11", "EPS FAMISANAR", 0, 0, 1),
|
||||
("008", "860002503", "11", "EPS CRUZ BLANCA", 0, 0, 1),
|
||||
("009", "860027404", "11", "EPS COLSANITAS", 0, 0, 1),
|
||||
("010", "860039988", "11", "EPS SALUD TOTAL", 0, 0, 1),
|
||||
("011", "890903790", "11", "EPS SAVIA SALUD", 0, 0, 1),
|
||||
("012", "900178724", "11", "EPS MUTUAL SER", 0, 0, 1),
|
||||
("034", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 1),
|
||||
("035", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 1),
|
||||
("036", "860078828", "11", "EPS SANITAS (alt)", 0, 0, 1),
|
||||
("20062026", "800182856", "01", "SUBSIDIADO", 0, 0, 1),
|
||||
("CW225489", "899999068", "07", "POLIZA / SEGURO", 0, 0, 1),
|
||||
# Contratos excluidos del envío RDA
|
||||
("013", "", "11", "MEDILAVORO S.A.S", 0, 0, 1),
|
||||
("014", "", "11", "LABORATORIO UROCLINICO",0, 0, 1),
|
||||
("015", "", "11", "ANDRES AFANADOR VILLAMIZAR", 1, 0, 1),
|
||||
("016", "", "11", "OMAR FERNANDO RIBERO GOMEZ", 1, 0, 1),
|
||||
("017", "", "11", "CLAUDIA BELEN JULIO SEPULVEDA", 1, 0, 1),
|
||||
("018", "", "11", "LABORATORIO MICROBIOLOGICO - MARGIE OJEDA", 1, 0, 1),
|
||||
("019", "", "11", "LABORATORIO TOXICOLOGICO - MARTHA MORALES", 1, 0, 1),
|
||||
("020", "", "11", "MARTHA LUCIA GALLARDO", 1, 0, 1),
|
||||
("021", "", "11", "LABORATORIO VILMA OROZCO AYALA", 1, 0, 1),
|
||||
("022", "", "11", "CLINICA SAN JOSE DE CUCUTA", 1, 0, 1),
|
||||
("023", "", "11", "URONORTE S.A", 1, 0, 1),
|
||||
("024", "", "11", "LABORATORIO CLINICO BIOLAB S.A.S", 1, 0, 1),
|
||||
("025", "", "11", "JOEL LEONARDO CARRILLO CORREDOR", 1, 0, 1),
|
||||
("026", "", "11", "ROLANDO IVAN PENARANDA DEVIA", 1, 0, 1),
|
||||
("027", "", "11", "JOSE WILMER GARCIA CALDERON", 1, 0, 1),
|
||||
("028", "", "11", "ONCOMEDICAL IPS S.A.S", 1, 0, 1),
|
||||
("029", "", "11", "GENETIX S.A.S", 1, 0, 1),
|
||||
("030", "", "11", "NORFETUS S.A.S", 1, 0, 1),
|
||||
("031", "", "11", "TBTB GLOBAL LAB S.A.S", 1, 0, 1),
|
||||
("032", "", "11", "COLGENES S.A.S", 1, 0, 1),
|
||||
("033", "", "11", "IPS FIGURAS SPA CUCUTA S.A.S", 1, 0, 1),
|
||||
("037", "", "11", "CLINICA URGENCIAS LA MERCED", 1, 0, 1),
|
||||
("038", "", "11", "CLINICA COLSANITAS S.A.", 1, 0, 1),
|
||||
("039", "", "11", "GOMEZ GIL JOSE JESUS", 1, 0, 1),
|
||||
("040", "", "11", "MARTHA LILIANA SALGAR GALLEGO", 1, 1, 0),
|
||||
("041", "", "11", "GENCELL PHARMA S.A.S", 1, 0, 0),
|
||||
("042", "", "07", "AXA COLPATRIA HYC", 1, 0, 0),
|
||||
("041", "", "11", "GENCELL PHARMA S.A.S", 1, 0, 1),
|
||||
("042", "", "07", "AXA COLPATRIA HYC", 1, 0, 1),
|
||||
]
|
||||
|
||||
|
||||
@@ -200,7 +201,7 @@ async def contrato_update(
|
||||
|
||||
|
||||
@router.post("/toggle-excluir/{contrato_id}")
|
||||
async def toggle_excluir(contrato_id: int, user: dict = Depends(get_current_user)):
|
||||
async def toggle_excluir(contrato_id: int, request: Request, user: dict = Depends(get_current_user)):
|
||||
conn = get_connection()
|
||||
conn.execute(
|
||||
"UPDATE contratos SET excluir = CASE WHEN excluir=1 THEN 0 ELSE 1 END WHERE id=?",
|
||||
|
||||
@@ -188,7 +188,7 @@ ORDER BY ps.ID_PS, rel.COD_EXAMEN""",
|
||||
"description": "Pre-servicio por número PS (buscar RCXC05291 → escribe solo el número)"
|
||||
},
|
||||
{
|
||||
"name": "Factura Venta 040 por fecha",
|
||||
"name": "Factura Venta por fecha",
|
||||
"query_type": "ventas",
|
||||
"query_text": """SELECT
|
||||
r.IDRECEPCION,
|
||||
@@ -203,19 +203,18 @@ ORDER BY ps.ID_PS, 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,
|
||||
e.CODCONTRATO
|
||||
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
|
||||
AND TRIM(e.CODCONTRATO) = '040'
|
||||
ORDER BY r.IDRECEPCION""",
|
||||
"description": "Facturas de venta contrato 040 en un rango de fechas"
|
||||
"description": "Facturas de venta en un rango de fechas (filtra por excluir_ventas en /contratos)"
|
||||
},
|
||||
{
|
||||
"name": "Factura Venta 040 por número",
|
||||
"name": "Factura Venta por número",
|
||||
"query_type": "ventas",
|
||||
"query_text": """SELECT
|
||||
r.IDRECEPCION,
|
||||
@@ -230,15 +229,14 @@ ORDER BY r.IDRECEPCION""",
|
||||
COALESCE(NULLIF(TRIM(ex.NUM_ISS), ''), TRIM(rel.COD_EXAMEN)) AS CUPS,
|
||||
rel.PRECIO,
|
||||
COALESCE(t.VALOR, rel.PRECIO) AS PRECIO_TARIFA,
|
||||
e.CODCONTRATO
|
||||
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.NUM_FACTURA = :num_factura
|
||||
AND TRIM(e.CODCONTRATO) = '040'""",
|
||||
"description": "Factura de venta contrato 040 por número de factura"
|
||||
WHERE r.NUM_FACTURA = :num_factura""",
|
||||
"description": "Factura de venta por número (filtra por excluir_ventas en /contratos)"
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
@@ -188,6 +188,8 @@ async def send_one(
|
||||
q = conn.execute("SELECT * FROM queries WHERE id = ?", (query_id,)).fetchone()
|
||||
cfg = {r["key"]: r["value"] for r in conn.execute("SELECT * FROM config").fetchall()}
|
||||
conn.close()
|
||||
if not q:
|
||||
return JSONResponse({"success": False, "message": "Consulta no encontrada"})
|
||||
|
||||
rows, err = _query_rows(cfg, q["query_text"], factura, fecha_inicio, fecha_fin)
|
||||
if rows is None:
|
||||
@@ -243,6 +245,8 @@ async def send_ventas(
|
||||
q = conn.execute("SELECT * FROM queries WHERE id = ?", (query_id,)).fetchone()
|
||||
cfg = {r["key"]: r["value"] for r in conn.execute("SELECT * FROM config").fetchall()}
|
||||
conn.close()
|
||||
if not q:
|
||||
return JSONResponse({"success": False, "message": "Consulta no encontrada"})
|
||||
|
||||
rows, err = _query_rows(cfg, q["query_text"], factura, fecha_inicio, fecha_fin)
|
||||
if rows is None:
|
||||
|
||||
@@ -106,7 +106,8 @@ def generar_tercero_api(row: dict) -> dict:
|
||||
"sexo": sexo,
|
||||
"identidadGenero": identidad_genero,
|
||||
"zona": zona,
|
||||
"etnia": str(row.get("CODETNIA") or "99").strip(),
|
||||
"etnia": (lambda v: v if v and len(v) <= 2 else "99")(str(row.get("CODETNIA") or "").strip()),
|
||||
"ciudadExp": cod_ciudad,
|
||||
"antecedentes": "",
|
||||
"cliente": "S",
|
||||
"enfermedadCronica": False,
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
<span id="preview-fecha" class="text-sm text-gray-500"></span>
|
||||
</div>
|
||||
<div class="p-6 space-y-4">
|
||||
<div class="grid grid-cols-4 gap-4">
|
||||
<div class="grid grid-cols-5 gap-4">
|
||||
<div class="bg-blue-50 rounded-lg p-4 text-center">
|
||||
<div id="cnt-pacientes" class="text-3xl font-bold text-blue-600">0</div>
|
||||
<div class="text-sm text-blue-700 mt-1">Pacientes</div>
|
||||
@@ -66,6 +66,11 @@
|
||||
<div class="text-sm text-orange-700 mt-1">Pre-servicios</div>
|
||||
<div class="text-xs text-orange-400">RCXC / SC</div>
|
||||
</div>
|
||||
<div class="bg-emerald-50 rounded-lg p-4 text-center">
|
||||
<div id="cnt-ventas" class="text-3xl font-bold text-emerald-600">0</div>
|
||||
<div class="text-sm text-emerald-700 mt-1">Ventas</div>
|
||||
<div class="text-xs text-emerald-400">Ventas/Crear</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<p class="text-xs font-medium text-gray-600 mb-2">Pacientes — clic para ver sus recepciones:</p>
|
||||
@@ -75,6 +80,10 @@
|
||||
<p class="text-xs font-medium text-orange-600 mb-2 mt-2">Pre-servicios RCXC / SC — clic para ver JSON:</p>
|
||||
<div id="preview-ps-table" class="border border-orange-100 rounded-lg overflow-hidden text-sm"></div>
|
||||
</div>
|
||||
<div id="ventas-section" class="hidden">
|
||||
<p class="text-xs font-medium text-emerald-600 mb-2 mt-2">Facturas Venta — clic para ver JSON:</p>
|
||||
<div id="preview-vta-table" class="border border-emerald-100 rounded-lg overflow-hidden text-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -110,6 +119,16 @@
|
||||
</div>
|
||||
<div id="p3-detalle" class="divide-y divide-gray-100 hidden"></div>
|
||||
</div>
|
||||
<div class="bg-white rounded-xl shadow-sm border border-gray-200">
|
||||
<div class="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div class="w-8 h-8 bg-emerald-500 rounded-full flex items-center justify-center text-white text-sm font-bold">4</div>
|
||||
<span class="font-semibold text-gray-800">Facturas Venta</span>
|
||||
</div>
|
||||
<span id="p4-badge" class="text-sm text-gray-400">Esperando...</span>
|
||||
</div>
|
||||
<div id="p4-detalle" class="divide-y divide-gray-100 hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -133,7 +152,7 @@
|
||||
<strong id="modal-fecha" class="text-blue-600"></strong>:
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-4 gap-3">
|
||||
<div class="grid grid-cols-5 gap-3">
|
||||
<div class="bg-blue-50 rounded-lg p-3 text-center">
|
||||
<div id="modal-cnt-pac" class="text-2xl font-bold text-blue-600">0</div>
|
||||
<div class="text-xs text-blue-700">Terceros</div>
|
||||
@@ -154,6 +173,11 @@
|
||||
<div class="text-xs text-orange-700">Pre-servicios</div>
|
||||
<div class="text-xs text-orange-400">RCXC / SC</div>
|
||||
</div>
|
||||
<div class="bg-emerald-50 rounded-lg p-3 text-center">
|
||||
<div id="modal-cnt-vta" class="text-2xl font-bold text-emerald-600">0</div>
|
||||
<div class="text-xs text-emerald-700">Ventas</div>
|
||||
<div class="text-xs text-emerald-400">Ventas/Crear</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-amber-50 border border-amber-200 rounded-lg px-4 py-3 text-xs text-amber-700 flex items-start gap-2">
|
||||
@@ -214,6 +238,7 @@ async function previewAutomation() {
|
||||
document.getElementById('cnt-recepciones').textContent = data.recepciones;
|
||||
document.getElementById('cnt-examenes').textContent = data.examenes;
|
||||
document.getElementById('cnt-preservicios').textContent = data.preservicios || 0;
|
||||
document.getElementById('cnt-ventas').textContent = data.ventas || 0;
|
||||
document.getElementById('preview-fecha').textContent = data.fecha;
|
||||
|
||||
const psTable = document.getElementById('preview-ps-table');
|
||||
@@ -240,6 +265,31 @@ async function previewAutomation() {
|
||||
psSection.classList.add('hidden');
|
||||
}
|
||||
|
||||
const vtaTable = document.getElementById('preview-vta-table');
|
||||
const vtaSection = document.getElementById('ventas-section');
|
||||
if (data.ventas_preview && data.ventas_preview.length > 0) {
|
||||
vtaTable.innerHTML = data.ventas_preview.map((vta, i) => {
|
||||
const key = `vta-json-${i}`;
|
||||
_rdaJsonStore[key] = vta.json;
|
||||
return `
|
||||
<div>
|
||||
<div class="flex items-center gap-3 px-4 py-2.5 hover:bg-emerald-50 cursor-pointer select-none border-b border-emerald-100"
|
||||
onclick="toggleVta('${key}', this)">
|
||||
<i class="fas fa-chevron-right text-emerald-300 text-xs transition-transform duration-150" id="vtachev-${i}"></i>
|
||||
<span class="font-mono text-emerald-700 font-medium w-32">${vta.factura}</span>
|
||||
<span class="text-gray-500 w-24 text-xs">${vta.fecha}</span>
|
||||
<span class="font-mono bg-emerald-100 text-emerald-800 px-1.5 py-0.5 rounded text-xs w-14 text-center">${vta.contrato || '—'}</span>
|
||||
<span class="text-gray-600 flex-1 text-xs">Pac. ${vta.paciente}</span>
|
||||
<span class="text-xs text-emerald-500">${vta.examenes} examen(es)</span>
|
||||
</div>
|
||||
<div id="${key}" class="hidden bg-gray-900 text-green-300 text-xs font-mono p-4 overflow-x-auto whitespace-pre border-b border-emerald-100"></div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
vtaSection.classList.remove('hidden');
|
||||
} else {
|
||||
vtaSection.classList.add('hidden');
|
||||
}
|
||||
|
||||
const table = document.getElementById('preview-table');
|
||||
if (data.pacientes === 0) {
|
||||
table.innerHTML = '<div class="p-4 text-gray-400 text-center">No hay pacientes para esta fecha</div>';
|
||||
@@ -297,6 +347,7 @@ function abrirModalConfirmar() {
|
||||
document.getElementById('modal-cnt-rec').textContent = d.recepciones;
|
||||
document.getElementById('modal-cnt-exa').textContent = d.examenes;
|
||||
document.getElementById('modal-cnt-ps').textContent = d.preservicios || 0;
|
||||
document.getElementById('modal-cnt-vta').textContent = d.ventas || 0;
|
||||
|
||||
document.getElementById('modal-lista-pac').innerHTML = d.pacientes_preview.map(p => `
|
||||
<div class="flex items-center gap-3 px-3 py-1.5 border-b border-gray-50 text-xs last:border-0">
|
||||
@@ -327,6 +378,8 @@ async function confirmarEnvio() {
|
||||
document.getElementById('p2-badge').className = 'text-sm text-gray-400';
|
||||
document.getElementById('p3-badge').textContent = 'Esperando...';
|
||||
document.getElementById('p3-badge').className = 'text-sm text-gray-400';
|
||||
document.getElementById('p4-badge').textContent = 'Esperando...';
|
||||
document.getElementById('p4-badge').className = 'text-sm text-gray-400';
|
||||
|
||||
const contrato = document.getElementById('contrato-input').value.trim();
|
||||
const form = new FormData();
|
||||
@@ -364,8 +417,14 @@ async function confirmarEnvio() {
|
||||
col2: `Pac. ${d.paciente} — ${d.examenes} examen(es)`,
|
||||
col3: `${d.ok ? '✅' : '❌'} ${d.msg || ''}`, ok: d.ok,
|
||||
})));
|
||||
renderBadge('p4-badge', r.paso4_ventas.enviados, r.paso4_ventas.errores);
|
||||
renderDetalle('p4-detalle', r.paso4_ventas.detalle.map(d => ({
|
||||
col1: `${d.factura}`,
|
||||
col2: `Pac. ${d.paciente} — ${d.examenes} examen(es)`,
|
||||
col3: `${d.ok ? '✅' : '❌'} ${d.msg || ''}`, ok: d.ok,
|
||||
})));
|
||||
|
||||
const totalErr = r.paso1_terceros.errores + r.paso2_rda.errores + r.paso3_preserv.errores;
|
||||
const totalErr = r.paso1_terceros.errores + r.paso2_rda.errores + r.paso3_preserv.errores + r.paso4_ventas.errores;
|
||||
showToast(totalErr === 0 ? 'Envío completado sin errores' : `Completado con ${totalErr} error(es)`,
|
||||
totalErr === 0 ? 'success' : 'warning');
|
||||
} catch (e) {
|
||||
@@ -405,6 +464,17 @@ function togglePs(id, row) {
|
||||
if (chev) chev.style.transform = hidden ? 'rotate(90deg)' : '';
|
||||
}
|
||||
|
||||
function toggleVta(id, row) {
|
||||
const div = document.getElementById(id);
|
||||
const chev = row.querySelector('[id^="vtachev-"]');
|
||||
const hidden = div.classList.contains('hidden');
|
||||
if (hidden && _rdaJsonStore[id]) {
|
||||
div.textContent = JSON.stringify(_rdaJsonStore[id], null, 2);
|
||||
}
|
||||
div.classList.toggle('hidden', !hidden);
|
||||
if (chev) chev.style.transform = hidden ? 'rotate(90deg)' : '';
|
||||
}
|
||||
|
||||
function renderBadge(id, enviados, errores) {
|
||||
const el = document.getElementById(id);
|
||||
if (errores === 0) {
|
||||
|
||||
@@ -178,6 +178,7 @@ function renderTabla() {
|
||||
<th class="pb-2 font-medium pr-3">IDRECEP.</th>
|
||||
<th class="pb-2 font-medium pr-3">Factura</th>
|
||||
<th class="pb-2 font-medium pr-3">Paciente</th>
|
||||
<th class="pb-2 font-medium pr-3">Contrato</th>
|
||||
<th class="pb-2 font-medium pr-3">Exámenes</th>
|
||||
<th class="pb-2 font-medium pr-3">Estado</th>
|
||||
<th class="pb-2 font-medium"></th>
|
||||
@@ -217,7 +218,8 @@ function filaHtml(item) {
|
||||
<td class="py-2 pr-3 font-mono text-gray-600">${id}</td>
|
||||
<td class="py-2 pr-3 text-gray-600">${escHtml(String(item.factura || '-'))}</td>
|
||||
<td class="py-2 pr-3 text-gray-600">${escHtml(String(item.paciente || '-'))}</td>
|
||||
<td class="py-2 pr-3 text-gray-500 max-w-[200px] truncate" title="${escAttr(examsStr)}">${escHtml(examsStr)}</td>
|
||||
<td class="py-2 pr-3"><span class="px-1.5 py-0.5 bg-emerald-100 text-emerald-700 rounded text-xs font-mono">${escHtml(String(item.contrato || '-'))}</span></td>
|
||||
<td class="py-2 pr-3 text-gray-500 max-w-[180px] truncate" title="${escAttr(examsStr)}">${escHtml(examsStr)}</td>
|
||||
<td id="estado-${id}" class="py-2 pr-3">${badgeHtml}</td>
|
||||
<td class="py-2 flex gap-1 items-center">
|
||||
<button onclick="verDetalle(${id})" title="Ver JSON" class="px-2 py-1 bg-gray-100 hover:bg-gray-200 text-gray-600 rounded text-xs"><i class="fas fa-code"></i></button>
|
||||
|
||||
Reference in New Issue
Block a user