diff --git a/app/routes/ventas.py b/app/routes/ventas.py index 288fad8..361c244 100644 --- a/app/routes/ventas.py +++ b/app/routes/ventas.py @@ -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}) diff --git a/app/templates/ventas.html b/app/templates/ventas.html index e20b1f0..68d0c69 100644 --- a/app/templates/ventas.html +++ b/app/templates/ventas.html @@ -11,18 +11,9 @@