fix: ventas page uses same SQL as automation (FACTURA_DIAN + FECHAFACT)
- Hardcode _SQL_VENTAS and _SQL_VENTAS_BY_FACTURA matching automation.py - Remove stored query selector from UI — ventas always queries CMXC by billing date - send-one now fetches factura directly by PREFIJO+NUM_FACTURA (no date range requery) - Remove query_id dependency from all endpoints and JS formParams Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
c0e66989ca
commit
22e42463c7
+82
-67
@@ -13,6 +13,44 @@ from app.routes.contratos import load_excluded_ventas_set
|
||||
|
||||
router = APIRouter(prefix="/ventas", tags=["ventas"])
|
||||
|
||||
_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,
|
||||
CAST(fd.FECHAFACT AS VARCHAR(30)) AS FECHAFACT
|
||||
FROM RECEPCION r
|
||||
JOIN FACTURA_DIAN fd ON fd.PREFIJO = r.PREFIJO AND fd.NUM_FACTURA = r.NUM_FACTURA
|
||||
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 CAST(fd.FECHAFACT AS TIMESTAMP) BETWEEN :fecha_ini AND :fecha_fin
|
||||
AND (fd.ANULADA IS NULL OR fd.ANULADA = 'F')
|
||||
AND r.PREFIJO = 'CMXC'
|
||||
ORDER BY r.NUM_FACTURA, r.IDRECEPCION
|
||||
"""
|
||||
|
||||
_SQL_VENTAS_BY_FACTURA = """
|
||||
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.PREFIJO = :prefijo AND r.NUM_FACTURA = :num_factura
|
||||
ORDER BY r.IDRECEPCION
|
||||
"""
|
||||
|
||||
|
||||
def _cfg():
|
||||
conn = get_connection()
|
||||
@@ -21,35 +59,34 @@ def _cfg():
|
||||
return cfg
|
||||
|
||||
|
||||
def _query_rows(cfg, query_text, factura, fecha_inicio, fecha_fin):
|
||||
import re as _re
|
||||
def _query_rows(cfg, fecha_inicio, fecha_fin):
|
||||
fb, ok, msg = get_firebird_from_config(cfg)
|
||||
if not ok:
|
||||
return None, msg
|
||||
nums = _re.findall(r'\d+', factura or "")
|
||||
num_val = int(nums[-1]) if nums else 0
|
||||
prefix = _re.sub(r'[\d\s]', '', factura or "").strip().upper()
|
||||
|
||||
params = {}
|
||||
if ":fecha_ini" in query_text:
|
||||
params["fecha_ini"] = f"{fecha_inicio} 00:00:00"
|
||||
if ":fecha_fin" in query_text:
|
||||
params["fecha_fin"] = f"{fecha_fin} 23:59:59"
|
||||
if ":num_factura" in query_text:
|
||||
params["num_factura"] = num_val
|
||||
|
||||
ok2, err, rows = fb.execute_query(query_text, params)
|
||||
ok2, err, rows = fb.execute_query(_SQL_VENTAS, {
|
||||
"fecha_ini": f"{fecha_inicio} 00:00:00",
|
||||
"fecha_fin": f"{fecha_fin} 23:59:59",
|
||||
})
|
||||
fb.disconnect()
|
||||
if not ok2:
|
||||
return None, err
|
||||
|
||||
if rows and "PREFIJO" in rows[0]:
|
||||
filter_prefix = prefix if prefix else "CMXC"
|
||||
rows = [r for r in rows if str(r.get("PREFIJO") or "").strip().upper() == filter_prefix]
|
||||
|
||||
return rows, None
|
||||
|
||||
|
||||
def _query_rows_by_factura(cfg, prefijo, num_factura):
|
||||
fb, ok, msg = get_firebird_from_config(cfg)
|
||||
if not ok:
|
||||
return None, msg
|
||||
ok2, err, rows = fb.execute_query(_SQL_VENTAS_BY_FACTURA, {
|
||||
"prefijo": prefijo,
|
||||
"num_factura": int(num_factura),
|
||||
})
|
||||
fb.disconnect()
|
||||
if not ok2:
|
||||
return None, err
|
||||
return [dict(r) for r in rows], None
|
||||
|
||||
|
||||
def _agrupar(rows):
|
||||
"""Agrupa por PREFIJO+NUM_FACTURA para incluir todos los servicios de una factura."""
|
||||
from collections import defaultdict
|
||||
@@ -81,7 +118,7 @@ def _is_sent(factura_key):
|
||||
return None
|
||||
|
||||
|
||||
def _guardar_envio(user_id, factura, idrecepcion, contrato, json_data,
|
||||
def _guardar_envio(user_id, factura, contrato, json_data,
|
||||
respuesta, ok, fecha_inicio, fecha_fin, servicios):
|
||||
try:
|
||||
conn = get_connection()
|
||||
@@ -91,7 +128,7 @@ def _guardar_envio(user_id, factura, idrecepcion, contrato, json_data,
|
||||
status, json_enviado, respuesta_api, mensaje_tns, created_at)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
""", (
|
||||
user_id, "ventas", factura, idrecepcion, contrato,
|
||||
user_id, "ventas", factura, None, contrato,
|
||||
fecha_inicio, fecha_fin, 1, servicios,
|
||||
"success" if ok else "error",
|
||||
json_lib.dumps(json_data, ensure_ascii=False)[:10000],
|
||||
@@ -118,34 +155,22 @@ def _parse_tns(r):
|
||||
|
||||
@router.get("")
|
||||
async def ventas_page(request: Request, user: dict = Depends(get_current_user)):
|
||||
conn = get_connection()
|
||||
queries = conn.execute(
|
||||
"SELECT * FROM queries WHERE query_type = 'ventas' ORDER BY name"
|
||||
).fetchall()
|
||||
conn.close()
|
||||
return request.app.state.templates.TemplateResponse("ventas.html", {
|
||||
"request": request, "user": user, "queries": queries,
|
||||
"request": request, "user": user,
|
||||
})
|
||||
|
||||
|
||||
@router.post("/preview")
|
||||
async def preview_ventas(
|
||||
request: Request, user: dict = Depends(get_current_user),
|
||||
query_id: int = Form(...), factura: str = Form(""),
|
||||
fecha_inicio: str = Form(...), fecha_fin: str = 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"})
|
||||
|
||||
rows, err = _query_rows(cfg, q["query_text"], factura, fecha_inicio, fecha_fin)
|
||||
cfg = _cfg()
|
||||
rows, err = _query_rows(cfg, fecha_inicio, fecha_fin)
|
||||
if rows is None:
|
||||
return JSONResponse({"success": False, "message": err})
|
||||
if not rows:
|
||||
return JSONResponse({"success": False, "message": "Sin datos"})
|
||||
return JSONResponse({"success": False, "message": "Sin datos para ese rango de fechas"})
|
||||
|
||||
excluded_ventas = load_excluded_ventas_set()
|
||||
grupos_all = _agrupar(rows)
|
||||
@@ -184,22 +209,23 @@ async def preview_ventas(
|
||||
@router.post("/send-one")
|
||||
async def send_one(
|
||||
request: Request, user: dict = Depends(get_current_user),
|
||||
factura_key: str = Form(...), query_id: int = Form(...),
|
||||
factura: str = Form(""), fecha_inicio: str = Form(...), fecha_fin: str = Form(...),
|
||||
factura_key: str = Form(...),
|
||||
fecha_inicio: str = Form(...), fecha_fin: str = 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"})
|
||||
import re as _re
|
||||
cfg = _cfg()
|
||||
|
||||
rows, err = _query_rows(cfg, q["query_text"], factura, fecha_inicio, fecha_fin)
|
||||
if rows is None:
|
||||
parts = factura_key.split("-", 1)
|
||||
if len(parts) != 2:
|
||||
return JSONResponse({"success": False, "message": f"Clave de factura inválida: {factura_key}"})
|
||||
prefijo, num_str = parts[0], parts[1]
|
||||
nums = _re.findall(r'\d+', num_str)
|
||||
if not nums:
|
||||
return JSONResponse({"success": False, "message": f"Número de factura inválido: {factura_key}"})
|
||||
|
||||
grupo_rows, err = _query_rows_by_factura(cfg, prefijo, nums[-1])
|
||||
if grupo_rows is None:
|
||||
return JSONResponse({"success": False, "message": err})
|
||||
|
||||
grupos = _agrupar(rows)
|
||||
grupo_rows = grupos.get(factura_key)
|
||||
if not grupo_rows:
|
||||
return JSONResponse({"success": False, "message": f"Factura {factura_key} no encontrada"})
|
||||
|
||||
@@ -212,9 +238,6 @@ async def send_one(
|
||||
headers = {"Content-Type": "application/json", "Authorization": f"Bearer {token}"}
|
||||
endpoint = f"{TNS_BASE}/v2/facturacion/Ventas/Crear"
|
||||
venta_json = _build_venta(grupo_rows, cfg)
|
||||
num_fac = str(grupo_rows[0].get("NUM_FACTURA") or "").strip()
|
||||
prefijo = str(grupo_rows[0].get("PREFIJO") or "").strip()
|
||||
factura_display = f"{prefijo}-{num_fac}"
|
||||
contrato = str(grupo_rows[0].get("CODCONTRATO") or "").strip()
|
||||
|
||||
raw_resp = ""
|
||||
@@ -228,11 +251,11 @@ async def send_one(
|
||||
except Exception as ex:
|
||||
msg_tns = str(ex)
|
||||
|
||||
_guardar_envio(user["user_id"], factura_display, None, contrato,
|
||||
_guardar_envio(user["user_id"], factura_key, contrato,
|
||||
venta_json, raw_resp, ok_v, fecha_inicio, fecha_fin, len(grupo_rows))
|
||||
|
||||
log_activity(user["user_id"], user["username"], "venta_enviada",
|
||||
f"Factura {factura_display} | {'OK' if ok_v else 'ERROR: '+msg_tns[:80]}",
|
||||
f"Factura {factura_key} | {'OK' if ok_v else 'ERROR: '+msg_tns[:80]}",
|
||||
get_ip(request))
|
||||
return JSONResponse({"success": ok_v, "message": msg_tns, "raw_tns": raw_resp, "factura_key": factura_key})
|
||||
|
||||
@@ -240,18 +263,11 @@ async def send_one(
|
||||
@router.post("/send")
|
||||
async def send_ventas(
|
||||
request: Request, user: dict = Depends(get_current_user),
|
||||
query_id: int = Form(...), factura: str = Form(""),
|
||||
fecha_inicio: str = Form(...), fecha_fin: str = Form(...),
|
||||
solo_pendientes: str = Form("0"),
|
||||
):
|
||||
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"})
|
||||
|
||||
rows, err = _query_rows(cfg, q["query_text"], factura, fecha_inicio, fecha_fin)
|
||||
cfg = _cfg()
|
||||
rows, err = _query_rows(cfg, fecha_inicio, fecha_fin)
|
||||
if rows is None:
|
||||
return JSONResponse({"success": False, "message": err})
|
||||
if not rows:
|
||||
@@ -264,7 +280,6 @@ async def send_ventas(
|
||||
if solo_pendientes == "1":
|
||||
grupos = {k: v for k, v in grupos.items() if not _is_sent(k)}
|
||||
|
||||
|
||||
token, token_err = await get_tns_token(
|
||||
cfg.get("tns_empresa", ""), cfg.get("tns_usuario", ""), cfg.get("tns_password", "")
|
||||
)
|
||||
@@ -289,7 +304,7 @@ async def send_ventas(
|
||||
except Exception as ex:
|
||||
msg_tns = str(ex)
|
||||
|
||||
_guardar_envio(user["user_id"], factura_key, None, contrato,
|
||||
_guardar_envio(user["user_id"], factura_key, contrato,
|
||||
venta_json, raw_resp, ok_v, fecha_inicio, fecha_fin, len(grupo_rows))
|
||||
resultados.append({"factura": factura_key, "success": ok_v, "msg": msg_tns})
|
||||
|
||||
|
||||
@@ -11,18 +11,9 @@
|
||||
<h3 class="font-semibold text-gray-800"><i class="fas fa-cog mr-2 text-emerald-500"></i>Parámetros</h3>
|
||||
</div>
|
||||
<div class="p-5 space-y-4">
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Consulta SQL</label>
|
||||
<select id="f-query" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
<option value="">Seleccionar consulta...</option>
|
||||
{% for q in queries %}
|
||||
<option value="{{ q.id }}">{{ q.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-medium text-gray-600 mb-1">Factura <span class="text-gray-400">(opcional)</span></label>
|
||||
<input id="f-factura" type="text" placeholder="Ej: LHXC03404"
|
||||
<input id="f-factura" type="text" placeholder="Ej: CMXC309"
|
||||
class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
|
||||
</div>
|
||||
<div class="grid grid-cols-2 gap-3">
|
||||
@@ -126,7 +117,6 @@ let formParams = {};
|
||||
|
||||
function getParams() {
|
||||
return {
|
||||
query_id: document.getElementById('f-query').value,
|
||||
factura: document.getElementById('f-factura').value,
|
||||
fecha_inicio: document.getElementById('f-fi').value,
|
||||
fecha_fin: document.getElementById('f-ff').value,
|
||||
@@ -135,7 +125,6 @@ function getParams() {
|
||||
|
||||
async function cargarRegistros() {
|
||||
formParams = getParams();
|
||||
if (!formParams.query_id) { showToast('Selecciona una consulta SQL', 'warning'); return; }
|
||||
if (!formParams.fecha_inicio || !formParams.fecha_fin) { showToast('Ingresa las fechas', 'warning'); return; }
|
||||
|
||||
document.getElementById('tabla-wrap').innerHTML = `
|
||||
|
||||
Reference in New Issue
Block a user