feat(transaccion): envío RDA directo por prefijo + número de factura
Nuevo endpoint POST /transaccion/send-direct y panel "Enviar RDA directo" en la UI: ingresa Tipo (prefijo) y Número, busca en Firebird y envía a TNS sin necesidad de rango de fechas ni pasar por el flujo de preview. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
d3a6ab7ab6
commit
3baa6e5f75
@@ -308,3 +308,106 @@ async def send_transaccion(
|
||||
"total_errores": err_count,
|
||||
"resultados": resultados,
|
||||
})
|
||||
|
||||
|
||||
@router.post("/send-direct")
|
||||
async def send_direct(
|
||||
request: Request,
|
||||
user: dict = Depends(get_current_user),
|
||||
prefijo: str = Form(...),
|
||||
numero: str = Form(...),
|
||||
query_id: int = Form(...),
|
||||
):
|
||||
conn = get_connection()
|
||||
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"})
|
||||
|
||||
prefijo = prefijo.strip().upper()
|
||||
numero = numero.strip()
|
||||
factura_str = f"{prefijo}{numero}"
|
||||
|
||||
rows, err, _ = _query_rows(cfg, q["query_text"], factura_str, "2000-01-01", "2099-12-31")
|
||||
if rows is None:
|
||||
return JSONResponse({"success": False, "message": err})
|
||||
if not rows:
|
||||
return JSONResponse({"success": False, "message": f"No se encontró factura {factura_str} en Firebird"})
|
||||
|
||||
grupos = agrupar_por_recepcion(rows)
|
||||
if not grupos:
|
||||
return JSONResponse({"success": False, "message": "Sin datos agrupables"})
|
||||
|
||||
token, token_err = await get_tns_token(
|
||||
cfg.get("tns_empresa", ""), cfg.get("tns_usuario", ""), cfg.get("tns_password", "")
|
||||
)
|
||||
if not token:
|
||||
return JSONResponse({"success": False, "message": f"Error login TNS: {token_err}"})
|
||||
|
||||
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"}
|
||||
api_sucursal = cfg.get("api_sucursal", "") or "00"
|
||||
endpoint = f"{TNS_BASE}/v2/rda/RdaPaciente/Insertar?codigosucursal={api_sucursal}"
|
||||
contrato_map = load_contrato_map()
|
||||
|
||||
resultados = []
|
||||
async with httpx.AsyncClient(timeout=int(cfg.get("api_timeout", 30))) as client:
|
||||
for id_rec, grupo_rows in grupos.items():
|
||||
num_fac = str(grupo_rows[0].get("NUM_FACTURA") or "").strip()
|
||||
rda_json = generar_rda_paciente(
|
||||
grupo_rows,
|
||||
cfg.get("profesional_default", ""),
|
||||
cfg.get("especialidad_default", ""),
|
||||
cfg.get("remisionante_default", "00"),
|
||||
cfg.get("prefijo_tns_default", "00"),
|
||||
num_fac,
|
||||
contrato_map=contrato_map,
|
||||
)
|
||||
raw_resp = ""
|
||||
ok_rda = False
|
||||
msg_tns = ""
|
||||
try:
|
||||
r = await client.post(endpoint, json=rda_json, headers=headers)
|
||||
raw_resp = r.text
|
||||
try:
|
||||
data = r.json()
|
||||
ok_rda = bool(data.get("status") or (data.get("data") or {}).get("success", False))
|
||||
msg_tns = ((data.get("data") or {}).get("response") or data.get("message") or raw_resp[:300])
|
||||
except Exception:
|
||||
ok_rda = r.status_code < 300
|
||||
msg_tns = raw_resp[:300]
|
||||
except Exception as ex:
|
||||
msg_tns = str(ex)
|
||||
|
||||
conn = get_connection()
|
||||
conn.execute("""
|
||||
INSERT INTO envios (user_id, tipo, factura, idrecepcion, contrato,
|
||||
fecha_inicio, fecha_fin, pacientes_count, servicios_count,
|
||||
status, json_enviado, respuesta_api, mensaje_tns, created_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
""", (
|
||||
user["user_id"], "transaccion", factura_str, id_rec,
|
||||
str(grupo_rows[0].get("CODCONTRATO", "")),
|
||||
"2000-01-01", "2099-12-31", 1, len(grupo_rows),
|
||||
"success" if ok_rda else "error",
|
||||
json_lib.dumps(rda_json, ensure_ascii=False)[:5000],
|
||||
raw_resp[:2000], msg_tns, datetime.now().isoformat(),
|
||||
))
|
||||
conn.commit()
|
||||
conn.close()
|
||||
resultados.append({
|
||||
"idrecepcion": id_rec,
|
||||
"factura": factura_str,
|
||||
"success": ok_rda,
|
||||
"message": msg_tns,
|
||||
"raw_tns": raw_resp[:1000],
|
||||
"json": rda_json,
|
||||
})
|
||||
|
||||
ok_count = sum(1 for r in resultados if r["success"])
|
||||
return JSONResponse({
|
||||
"success": ok_count > 0,
|
||||
"total": len(resultados),
|
||||
"resultados": resultados,
|
||||
})
|
||||
|
||||
@@ -47,6 +47,43 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enviar RDA directo -->
|
||||
<div class="bg-white rounded-xl shadow-sm border border-indigo-200">
|
||||
<div class="px-6 py-4 border-b border-indigo-100 flex items-center gap-2">
|
||||
<i class="fas fa-bolt text-indigo-500"></i>
|
||||
<h3 class="font-semibold text-gray-800">Enviar RDA directo</h3>
|
||||
</div>
|
||||
<div class="p-5 space-y-3">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Consulta SQL</label>
|
||||
<select id="d-query" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<option value="">Seleccionar...</option>
|
||||
{% for q in queries %}
|
||||
<option value="{{ q.id }}">{{ q.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Tipo</label>
|
||||
<input id="d-prefijo" type="text" placeholder="LHXC"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm uppercase"
|
||||
style="text-transform:uppercase">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Número</label>
|
||||
<input id="d-numero" type="text" placeholder="4358"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
</div>
|
||||
<button onclick="enviarDirecto()" id="btn-directo"
|
||||
class="w-full px-4 py-2 bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg text-sm font-medium">
|
||||
<i class="fas fa-paper-plane mr-1"></i> Enviar ahora
|
||||
</button>
|
||||
<div id="directo-result" class="hidden"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resumen -->
|
||||
<div id="resumen-box" class="hidden bg-white rounded-xl shadow-sm border border-gray-200 p-5">
|
||||
<div class="grid grid-cols-3 gap-2 text-center mb-4">
|
||||
@@ -351,6 +388,60 @@ function closeModal() {
|
||||
document.getElementById('modal-detalle').classList.add('hidden');
|
||||
}
|
||||
|
||||
async function enviarDirecto() {
|
||||
const qid = document.getElementById('d-query').value;
|
||||
const prefijo = document.getElementById('d-prefijo').value.trim().toUpperCase();
|
||||
const numero = document.getElementById('d-numero').value.trim();
|
||||
const resultBox = document.getElementById('directo-result');
|
||||
if (!qid) { showToast('Selecciona una consulta SQL', 'warning'); return; }
|
||||
if (!prefijo || !numero) { showToast('Ingresa Tipo y Número', 'warning'); return; }
|
||||
|
||||
const btn = document.getElementById('btn-directo');
|
||||
showLoading(btn);
|
||||
resultBox.classList.add('hidden');
|
||||
|
||||
const fd = new URLSearchParams({ query_id: qid, prefijo, numero });
|
||||
let result;
|
||||
try {
|
||||
const resp = await fetch('/transaccion/send-direct', { method: 'POST', credentials: 'include', body: fd });
|
||||
result = await resp.json();
|
||||
} catch(e) {
|
||||
result = { success: false, message: String(e) };
|
||||
}
|
||||
hideLoading(btn, '<i class="fas fa-paper-plane mr-1"></i> Enviar ahora');
|
||||
|
||||
if (result.success) {
|
||||
const r0 = (result.resultados || [])[0] || {};
|
||||
resultBox.innerHTML = `
|
||||
<div class="bg-green-50 border border-green-200 rounded-lg p-3 text-xs space-y-1">
|
||||
<div class="font-semibold text-green-700"><i class="fas fa-check-circle mr-1"></i>Enviado correctamente</div>
|
||||
<div class="text-gray-600">Factura: <strong>${escHtml(r0.factura||'')}</strong> — IDRECEP: ${r0.idrecepcion||''}</div>
|
||||
<div class="text-gray-500">${escHtml(r0.message||'')}</div>
|
||||
<button onclick="verDirectoJSON(this)" data-json='${JSON.stringify(r0.json||{}).replace(/'/g,"'")}'
|
||||
class="mt-1 text-blue-600 underline">Ver JSON enviado</button>
|
||||
</div>`;
|
||||
} else {
|
||||
const msgs = (result.resultados||[]).map(r => escHtml(r.message||'')).join('<br>') || escHtml(result.message||'Error');
|
||||
resultBox.innerHTML = `
|
||||
<div class="bg-red-50 border border-red-200 rounded-lg p-3 text-xs">
|
||||
<div class="font-semibold text-red-700"><i class="fas fa-times-circle mr-1"></i>Error al enviar</div>
|
||||
<div class="text-gray-600 mt-1">${msgs}</div>
|
||||
</div>`;
|
||||
}
|
||||
resultBox.classList.remove('hidden');
|
||||
}
|
||||
|
||||
function verDirectoJSON(btn) {
|
||||
try {
|
||||
const obj = JSON.parse(btn.dataset.json);
|
||||
document.getElementById('modal-titulo').innerHTML = '<i class="fas fa-code mr-2 text-indigo-500"></i>JSON — Envío directo';
|
||||
document.getElementById('tab-json').innerHTML = syntaxHighlight(obj);
|
||||
document.getElementById('tab-tns').textContent = '(usa ver resultado arriba)';
|
||||
showTab('tab-json');
|
||||
document.getElementById('modal-detalle').classList.remove('hidden');
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
function escHtml(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); }
|
||||
function escAttr(s) { return String(s).replace(/"/g,'"').replace(/'/g,'''); }
|
||||
|
||||
|
||||
Reference in New Issue
Block a user