- POST /envios/erp/sync-now?ventana_min=N calls sync_recientes with configurable window - sync_recientes() now returns a result dict (ok, total, created, updated, skipped, errores, detalle) - ERP tab shows a "Prueba manual" card with ventana selector (2m / 30m / 2h / hoy) and result breakdown Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
155 lines
5.9 KiB
Python
155 lines
5.9 KiB
Python
"""
|
|
Scheduler: sync automático de pacientes con recepción reciente → WhatsApp Lab.
|
|
Corre cada minuto y envía solo los pacientes con HORAINICIORECEPCION en los últimos 2 min.
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from app.database import get_connection
|
|
from app.services.firebird_service import get_firebird_from_config
|
|
from app.services.whatsapp_sync import sync_todos, guardar_sync_log
|
|
|
|
_SQL_HOY = """
|
|
SELECT DISTINCT
|
|
p.CODIGO,
|
|
p.TIPOIDENT,
|
|
p.DOCIDENT,
|
|
p.NOMBRES,
|
|
p.APELLIDOS,
|
|
p.DIRECCION,
|
|
p.CIUDAD AS COD_CIUDAD,
|
|
c.NOMBRE AS NOM_CIUDAD,
|
|
p.TELEFONOS,
|
|
p.EMAIL,
|
|
p.F_NACIMIENTO,
|
|
p.SEXO,
|
|
p.TIPORES,
|
|
p.CODETNIA,
|
|
r.HORAINICIORECEPCION
|
|
FROM PACIENTE p
|
|
LEFT JOIN CIUDAD c ON c.CODIGO = p.CIUDAD
|
|
JOIN RECEPCION r ON r.COD_PACIENTE = p.CODIGO
|
|
WHERE r.FECHA_RECEPCION = CURRENT_DATE
|
|
AND r.NUM_FACTURA > 0
|
|
"""
|
|
|
|
_SQL_EXAMENES_HOY = """
|
|
SELECT
|
|
TRIM(p.DOCIDENT) AS DOCIDENT,
|
|
r.IDRECEPCION,
|
|
r.HORAINICIORECEPCION,
|
|
TRIM(rel.COD_EXAMEN) AS COD_EXAMEN,
|
|
TRIM(ex.NOMBRE) AS NOM_EXAMEN,
|
|
COALESCE(NULLIF(TRIM(ex.NUM_ISS), ''), TRIM(rel.COD_EXAMEN)) AS CUPS,
|
|
rel.PRECIO,
|
|
TRIM(r.DIAG_PPAL) AS DIAG_PPAL,
|
|
TRIM(d.CONCEPTO) AS DIAG_CONCEPTO,
|
|
TRIM(m.DOCIDMEDICO) AS DOCIDMEDICO,
|
|
TRIM(r.NIT_EMPRESA) AS NIT_EMPRESA,
|
|
r.VALORTOTAL
|
|
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 DIAGNOSTICO d ON TRIM(d.COD_DIAG) = TRIM(r.DIAG_PPAL)
|
|
LEFT JOIN MEDICO m ON m.CODIGO = r.COD_MEDICO
|
|
JOIN PACIENTE p ON p.CODIGO = r.COD_PACIENTE
|
|
WHERE r.FECHA_RECEPCION = CURRENT_DATE
|
|
AND r.NUM_FACTURA > 0
|
|
ORDER BY r.IDRECEPCION
|
|
"""
|
|
|
|
|
|
def _parse_hora(val) -> datetime | None:
|
|
if not val:
|
|
return None
|
|
s = str(val)
|
|
try:
|
|
if "T" in s:
|
|
dt = datetime.fromisoformat(s)
|
|
ahora = datetime.now()
|
|
return ahora.replace(hour=dt.hour, minute=dt.minute, second=dt.second, microsecond=0)
|
|
partes = s.split(":")
|
|
ahora = datetime.now()
|
|
return ahora.replace(hour=int(partes[0]), minute=int(partes[1]),
|
|
second=int(partes[2].split(".")[0]), microsecond=0)
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
async def sync_recientes(ventana_min: int = 2) -> dict:
|
|
"""Job que corre cada minuto: sincroniza pacientes con recepción en los últimos `ventana_min` min."""
|
|
conn = get_connection()
|
|
configs = {row["key"]: row["value"] for row in conn.execute("SELECT * FROM config").fetchall()}
|
|
conn.close()
|
|
|
|
wa_url = configs.get("whatsapp_url", "").rstrip("/")
|
|
wa_key = configs.get("whatsapp_api_key", "")
|
|
if not wa_url or not wa_key:
|
|
return {"ok": False, "error": "whatsapp_url o whatsapp_api_key no configurados"}
|
|
|
|
fb, ok, msg = get_firebird_from_config(configs)
|
|
if not ok:
|
|
return {"ok": False, "error": f"Firebird: {msg}"}
|
|
|
|
ok_pac, err_pac, rows_pac = fb.execute_query(_SQL_HOY, None)
|
|
ok_ex, _, rows_ex = fb.execute_query(_SQL_EXAMENES_HOY, None)
|
|
fb.disconnect()
|
|
|
|
if not ok_pac:
|
|
return {"ok": False, "error": f"Query pacientes: {err_pac}"}
|
|
|
|
total_hoy = len(rows_pac) if rows_pac else 0
|
|
limite = datetime.now() - timedelta(minutes=ventana_min)
|
|
|
|
recientes = [
|
|
row for row in (rows_pac or [])
|
|
if (h := _parse_hora(row.get("HORAINICIORECEPCION"))) is None or h >= limite
|
|
]
|
|
if not recientes:
|
|
return {"ok": True, "total": 0, "created": 0, "updated": 0,
|
|
"skipped": 0, "errores": 0, "total_hoy": total_hoy,
|
|
"ventana_min": ventana_min, "mensaje": "Sin recepciones en la ventana de tiempo"}
|
|
|
|
examenes_map: dict[str, list] = {}
|
|
if ok_ex and rows_ex:
|
|
for ex in rows_ex:
|
|
h = _parse_hora(ex.get("HORAINICIORECEPCION"))
|
|
if h is not None and h < limite:
|
|
continue
|
|
doc = str(ex.get("DOCIDENT") or "").strip()
|
|
if not doc:
|
|
continue
|
|
examenes_map.setdefault(doc, []).append({
|
|
"cod_examen": (ex.get("COD_EXAMEN") or "").strip(),
|
|
"nombre": (ex.get("NOM_EXAMEN") or "").strip(),
|
|
"cups": (ex.get("CUPS") or "").strip(),
|
|
"precio": ex.get("PRECIO"),
|
|
"recepcion_id": ex.get("IDRECEPCION"),
|
|
"hora": str(ex.get("HORAINICIORECEPCION") or "")[:8],
|
|
"diagnostico_cod": (ex.get("DIAG_PPAL") or "").strip(),
|
|
"diagnostico_nombre": (ex.get("DIAG_CONCEPTO") or "").strip(),
|
|
"medico_docidmedico": (ex.get("DOCIDMEDICO") or "").strip(),
|
|
"nit_empresa": (ex.get("NIT_EMPRESA") or "").strip(),
|
|
"valor_total": ex.get("VALORTOTAL"),
|
|
})
|
|
|
|
ingest_url = f"{wa_url}/api/lab/ingest_paciente.php"
|
|
timeout = int(configs.get("api_timeout", 30))
|
|
resultado = await sync_todos(recientes, ingest_url, wa_key, timeout,
|
|
modo="upsert", examenes_map=examenes_map)
|
|
|
|
if resultado["total"] > 0:
|
|
guardar_sync_log(resultado, 0, origen="scheduler", modo="upsert")
|
|
|
|
return {
|
|
"ok": True,
|
|
"total": resultado["total"],
|
|
"created": resultado["created"],
|
|
"updated": resultado["updated"],
|
|
"skipped": resultado["skipped"],
|
|
"errores": resultado["errores"],
|
|
"total_hoy": total_hoy,
|
|
"ventana_min": ventana_min,
|
|
"detalle": resultado.get("detalle", []),
|
|
}
|