Actualizar backend/services.py
This commit is contained in:
@@ -745,9 +745,10 @@ def alumno_estado_guardar(matricula, ano, mes, estado, observaciones, usuario=No
|
|||||||
"actualizado_por": usuario or None,
|
"actualizado_por": usuario or None,
|
||||||
}
|
}
|
||||||
sb.table("cobranza_estado_alumno").upsert(payload, on_conflict="matricula,ano,mes").execute()
|
sb.table("cobranza_estado_alumno").upsert(payload, on_conflict="matricula,ano,mes").execute()
|
||||||
# Invalidar la tabla de cobranza para recalcular la columna SALDO POR RETIRAR
|
# Invalidar cobranza (columna SALDO POR RETIRAR) y el apartado Retiros
|
||||||
from cache_manager import cache_invalidate
|
from cache_manager import cache_invalidate
|
||||||
cache_invalidate("cobranza")
|
cache_invalidate("cobranza")
|
||||||
|
cache_invalidate("retiros")
|
||||||
return {"status": "ok"}
|
return {"status": "ok"}
|
||||||
|
|
||||||
def alumnos_por_retirar(ano, mes):
|
def alumnos_por_retirar(ano, mes):
|
||||||
@@ -758,6 +759,188 @@ def alumnos_por_retirar(ano, mes):
|
|||||||
.execute())
|
.execute())
|
||||||
return {"matriculas": [str(r["matricula"]) for r in (res.data or [])]}
|
return {"matriculas": [str(r["matricula"]) for r in (res.data or [])]}
|
||||||
|
|
||||||
|
|
||||||
|
# ── RETIROS (apartado) ─────────────────────────────────────────────────────
|
||||||
|
def retiros(ano="2026", mes=8):
|
||||||
|
"""Apartado RETIROS: alumnos marcados POR RETIRAR en (ano, mes), con sus datos
|
||||||
|
del cronograma de cobranza. El estado POR RETIRAR y la info manual son de ese mes."""
|
||||||
|
def _load():
|
||||||
|
try:
|
||||||
|
mats_ret = set(alumnos_por_retirar(ano, mes).get("matriculas", []))
|
||||||
|
except Exception:
|
||||||
|
mats_ret = set()
|
||||||
|
if not mats_ret:
|
||||||
|
return {"filas": []}
|
||||||
|
dm = get_dm()
|
||||||
|
proc = CobranzaLogic(dm).processor # para clasificar SEDE por programa (REPLACE_META)
|
||||||
|
crono = dm.ejecutar_consulta_cronograma_cobranza(str(ano)) or []
|
||||||
|
# primera fila por matrícula (todas comparten los datos de cabecera)
|
||||||
|
idx = {}
|
||||||
|
for f in crono:
|
||||||
|
m = str(f.get("num_matricula", "")).strip()
|
||||||
|
if m and m not in idx:
|
||||||
|
idx[m] = f
|
||||||
|
# dsc_vendedor + cod_detalle por matrícula (consulta ligera, solo las POR RETIRAR)
|
||||||
|
try:
|
||||||
|
extra = dm.traer_vendedor_cod_detalle(list(mats_ret))
|
||||||
|
except Exception:
|
||||||
|
extra = {}
|
||||||
|
from datetime import datetime as _dtnow
|
||||||
|
_hoy = _dtnow.now()
|
||||||
|
cob_idx = {}
|
||||||
|
try:
|
||||||
|
alum = cobranza_detalle_todos(str(ano), str(_hoy.month), "TODOS").get("alumnos", [])
|
||||||
|
for a in alum:
|
||||||
|
cob_idx[str(a.get("matricula", "")).strip()] = a
|
||||||
|
except Exception:
|
||||||
|
cob_idx = {}
|
||||||
|
# Info manual guardada en Supabase de ESTE mes (una sola lectura). Indexado por matrícula.
|
||||||
|
try:
|
||||||
|
info_idx = retiros_info_obtener(ano, mes)
|
||||||
|
except Exception:
|
||||||
|
info_idx = {}
|
||||||
|
def _d(v):
|
||||||
|
return str(v or "")[:10]
|
||||||
|
filas = []
|
||||||
|
for m in mats_ret:
|
||||||
|
f = idx.get(str(m))
|
||||||
|
if not f:
|
||||||
|
continue
|
||||||
|
ex = extra.get(str(m), {})
|
||||||
|
cb = cob_idx.get(str(m), {})
|
||||||
|
inf = info_idx.get(str(m), {})
|
||||||
|
monto_ant = round((float(cb.get("cta_ant", 0) or 0) - float(cb.get("cob_ant", 0) or 0)), 2)
|
||||||
|
monto_cur = round((float(cb.get("cta_cur", 0) or 0) - float(cb.get("cob_cur", 0) or 0)), 2)
|
||||||
|
_prog = str(f.get("dsc_det_programa", "") or f.get("dsc_programa", "") or "").strip()
|
||||||
|
filas.append({
|
||||||
|
"matricula": str(m),
|
||||||
|
"sede": str(proc.clasificar_sede_meta(_prog)),
|
||||||
|
"fch_matricula": _d(f.get("fch_matricula")),
|
||||||
|
"asesor": str(f.get("dsc_sectorista", "") or "").strip(),
|
||||||
|
"vendedor": ex.get("dsc_vendedor", ""),
|
||||||
|
"programa": str(f.get("dsc_det_programa", "") or f.get("dsc_programa", "") or "").strip(),
|
||||||
|
"cod_detalle": ex.get("cod_detalle", ""),
|
||||||
|
"fch_inicio": _d(f.get("fch_inicio")),
|
||||||
|
"fch_retiro": _d(f.get("fch_retiro")),
|
||||||
|
"alumno": ex.get("dsc_alumno", ""),
|
||||||
|
"num_cuotas_pagadas": ex.get("num_cuotas_pagadas", 0),
|
||||||
|
"num_cuotas_vencidas": ex.get("num_cuotas_vencidas", 0),
|
||||||
|
"fch_vencimiento": ex.get("fch_venc_mas_antigua", ""),
|
||||||
|
"monto_venc_ant": monto_ant,
|
||||||
|
"monto_venc_cur": monto_cur,
|
||||||
|
"imp_total_pagado": ex.get("imp_total_pagado", 0),
|
||||||
|
"monto_dejo_pagar": round(float(ex.get("inv_neta", 0) or 0) - float(ex.get("imp_total_pagado", 0) or 0), 2),
|
||||||
|
"n_asistencia": inf.get("n_asistencia"),
|
||||||
|
"n_faltas": inf.get("n_faltas"),
|
||||||
|
"ultimo_dia_asist": inf.get("ultimo_dia_asist") or "",
|
||||||
|
"hasta_sesion": inf.get("hasta_sesion") or "",
|
||||||
|
"fch_ult_resp": inf.get("fch_ult_resp") or "",
|
||||||
|
"fch_envio_correo": inf.get("fch_envio_correo") or "",
|
||||||
|
"retiro_wsp_class": inf.get("retiro_wsp_class") or "",
|
||||||
|
"motivo_retiro": inf.get("motivo_retiro") or "",
|
||||||
|
"detalle_retiro": inf.get("detalle_retiro") or "",
|
||||||
|
})
|
||||||
|
filas.sort(key=lambda x: x["fch_matricula"], reverse=True)
|
||||||
|
return {"filas": filas}
|
||||||
|
return cache_get_or_set("retiros", (str(ano), str(mes)), _load)
|
||||||
|
|
||||||
|
|
||||||
|
_RETIROS_INFO_CAMPOS = [
|
||||||
|
"n_asistencia", "n_faltas", "ultimo_dia_asist", "hasta_sesion",
|
||||||
|
"fch_ult_resp", "fch_envio_correo", "retiro_wsp_class",
|
||||||
|
"motivo_retiro", "detalle_retiro",
|
||||||
|
]
|
||||||
|
|
||||||
|
def _ri_num(v):
|
||||||
|
if v is None or v == "":
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return int(v)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _ri_manual_payload(campos):
|
||||||
|
"""Solo las 9 columnas manuales, normalizadas para Supabase."""
|
||||||
|
c = campos or {}
|
||||||
|
return {
|
||||||
|
"n_asistencia": _ri_num(c.get("n_asistencia")),
|
||||||
|
"n_faltas": _ri_num(c.get("n_faltas")),
|
||||||
|
"ultimo_dia_asist": c.get("ultimo_dia_asist") or None,
|
||||||
|
"hasta_sesion": c.get("hasta_sesion") or None,
|
||||||
|
"fch_ult_resp": c.get("fch_ult_resp") or None,
|
||||||
|
"fch_envio_correo": c.get("fch_envio_correo") or None,
|
||||||
|
"retiro_wsp_class": c.get("retiro_wsp_class") or None,
|
||||||
|
"motivo_retiro": c.get("motivo_retiro") or None,
|
||||||
|
"detalle_retiro": c.get("detalle_retiro") or None,
|
||||||
|
}
|
||||||
|
|
||||||
|
def retiros_info_obtener(ano, mes):
|
||||||
|
"""Lee la info manual guardada de (ano, mes) en una sola consulta.
|
||||||
|
Devuelve un dict indexado por matrícula (egress mínimo: 1 lectura)."""
|
||||||
|
sb = _sb()
|
||||||
|
cols = "matricula," + ",".join(_RETIROS_INFO_CAMPOS)
|
||||||
|
res = (sb.table("retiros_info").select(cols)
|
||||||
|
.eq("ano", int(ano)).eq("mes", int(mes)).execute())
|
||||||
|
out = {}
|
||||||
|
for r in (res.data or []):
|
||||||
|
out[str(r.get("matricula", "")).strip()] = r
|
||||||
|
return out
|
||||||
|
|
||||||
|
def retiros_info_guardar(matricula, ano, mes, campos, usuario=None):
|
||||||
|
"""INFO: guarda SOLO las 9 columnas manuales de un alumno en (matricula, ano, mes)."""
|
||||||
|
sb = _sb()
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
payload = {
|
||||||
|
"matricula": str(matricula),
|
||||||
|
"ano": int(ano),
|
||||||
|
"mes": int(mes),
|
||||||
|
**_ri_manual_payload(campos),
|
||||||
|
"actualizado_en": datetime.now(timezone.utc).isoformat(),
|
||||||
|
"actualizado_por": usuario or None,
|
||||||
|
}
|
||||||
|
sb.table("retiros_info").upsert(payload, on_conflict="matricula,ano,mes").execute()
|
||||||
|
from cache_manager import cache_invalidate
|
||||||
|
cache_invalidate("retiros")
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
def retiros_snapshot_guardar(ano, mes, filas, usuario=None):
|
||||||
|
"""GUARDAR: reescribe TODO el mes. Borra los registros de (ano, mes) y reinserta
|
||||||
|
las filas recibidas con TODAS las columnas (snapshot calculado + 9 manuales)."""
|
||||||
|
sb = _sb()
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
ano = int(ano); mes = int(mes)
|
||||||
|
now = datetime.now(timezone.utc).isoformat()
|
||||||
|
registros = []
|
||||||
|
for f in (filas or []):
|
||||||
|
registros.append({
|
||||||
|
"matricula": str(f.get("matricula", "")).strip(),
|
||||||
|
"ano": ano, "mes": mes,
|
||||||
|
"sede": f.get("sede") or None,
|
||||||
|
"fch_matricula": f.get("fch_matricula") or None,
|
||||||
|
"sectorista": f.get("asesor") or None,
|
||||||
|
"asesor_comercial": f.get("vendedor") or None,
|
||||||
|
"programa": f.get("programa") or None,
|
||||||
|
"cod_detalle": (str(f.get("cod_detalle")) if f.get("cod_detalle") not in (None, "") else None),
|
||||||
|
"fch_inicio": f.get("fch_inicio") or None,
|
||||||
|
"fch_retiro": f.get("fch_retiro") or None,
|
||||||
|
"alumno": f.get("alumno") or None,
|
||||||
|
"num_cuotas_pagadas": _ri_num(f.get("num_cuotas_pagadas")),
|
||||||
|
"num_cuotas_vencidas": _ri_num(f.get("num_cuotas_vencidas")),
|
||||||
|
"fch_vencimiento": f.get("fch_vencimiento") or None,
|
||||||
|
"monto_venc_ant": f.get("monto_venc_ant"),
|
||||||
|
"monto_venc_cur": f.get("monto_venc_cur"),
|
||||||
|
"monto_dejo_pagar": f.get("monto_dejo_pagar"),
|
||||||
|
**_ri_manual_payload(f),
|
||||||
|
"actualizado_en": now,
|
||||||
|
"actualizado_por": usuario or None,
|
||||||
|
})
|
||||||
|
sb.table("retiros_info").delete().eq("ano", ano).eq("mes", mes).execute()
|
||||||
|
if registros:
|
||||||
|
sb.table("retiros_info").insert(registros).execute()
|
||||||
|
from cache_manager import cache_invalidate
|
||||||
|
cache_invalidate("retiros")
|
||||||
|
return {"status": "ok", "guardados": len(registros)}
|
||||||
|
|
||||||
def usuarios_listar():
|
def usuarios_listar():
|
||||||
sb = _sb()
|
sb = _sb()
|
||||||
perfiles = sb.table("perfiles").select("id, nombre, rol, activo").execute().data or []
|
perfiles = sb.table("perfiles").select("id, nombre, rol, activo").execute().data or []
|
||||||
|
|||||||
Reference in New Issue
Block a user