549 lines
23 KiB
Python
549 lines
23 KiB
Python
"""
|
|
Capa de servicios: envuelve la lógica existente (modules/) con caché.
|
|
NO modifica la lógica de negocio — solo la llama y serializa el resultado.
|
|
"""
|
|
from core.data_manager import DataManager
|
|
from modules.ocupabilidad.logic import AnalizadorCursos
|
|
from modules.ventas.logic import VentasLogic
|
|
from modules.cobranza.logic import CobranzaLogic
|
|
from modules.rentabilidad.logic import RentabilidadLogic
|
|
from modules.saldo_pendiente.logic import SaldoLogic
|
|
from cache_manager import cache_get_or_set
|
|
|
|
# Instancia única del DataManager (como @st.cache_resource)
|
|
_DM = None
|
|
|
|
def get_dm() -> DataManager:
|
|
global _DM
|
|
if _DM is None:
|
|
_DM = DataManager()
|
|
return _DM
|
|
|
|
def _serializar_dicts(datos):
|
|
out = []
|
|
for d in (datos or []):
|
|
fila = {}
|
|
for k, v in d.items():
|
|
fila[k] = v if isinstance(v, (str, int, float, bool)) or v is None else str(v)
|
|
out.append(fila)
|
|
return out
|
|
|
|
def _serializar_filas(filas, limite=None):
|
|
out = []
|
|
for f in (filas or []):
|
|
vals = f[:limite] if limite else f
|
|
out.append([str(v) if v is not None else "" for v in vals])
|
|
return out
|
|
|
|
# ── OCUPABILIDAD ───────────────────────────────────────────────────────────
|
|
def ocupabilidad(ano, mes, sede="TODOS", programa="TODOS"):
|
|
def _load():
|
|
dm = get_dm()
|
|
analizador = AnalizadorCursos(dm)
|
|
print(f"[DEBUG Ocupabilidad] Buscando ano={ano}, mes={mes}, sede={sede}, programa={programa}")
|
|
|
|
# Intento 1: Formato original (ej: "6")
|
|
datos = analizador.obtener_datos_procesados(str(ano), str(mes), sede, programa)
|
|
|
|
# Intento 2: Si viene vacío, probar con formato de dos dígitos (ej: "06")
|
|
if not datos and len(str(mes)) == 1:
|
|
mes_padded = str(mes).zfill(2)
|
|
print(f"[DEBUG Ocupabilidad] Sin resultados. Reintentando con mes acolchado: '{mes_padded}'")
|
|
datos = analizador.obtener_datos_procesados(str(ano), mes_padded, sede, programa)
|
|
|
|
filas = analizador.formatear_datos_para_tabla(datos)
|
|
print(f"[DEBUG Ocupabilidad] Proceso completado. Registros encontrados: {len(datos) if datos else 0}")
|
|
return {
|
|
"datos": _serializar_dicts(datos),
|
|
"filas": _serializar_filas(filas),
|
|
}
|
|
return cache_get_or_set("ocupabilidad", (ano, mes, sede, programa), _load)
|
|
|
|
# ── VENTAS ─────────────────────────────────────────────────────────────────
|
|
def ventas(ano, mes, sede="TODOS", programa="TODOS"):
|
|
def _load():
|
|
dm = get_dm()
|
|
dm._tc_override_mes = comisiones_tc_del_mes(ano, mes) # TC de Supabase o None
|
|
dm._fecha_canc_overrides = comisiones_overrides_fecha() # {mat: fecha_canc override}
|
|
dm._inv_neta_overrides = comisiones_overrides_inv_neta() # {mat: inversion_neta override}
|
|
logic = VentasLogic(dm)
|
|
print(f"[DEBUG Ventas] Buscando ano={ano}, mes={mes}, sede={sede}, programa={programa}")
|
|
|
|
mes_str = str(mes)
|
|
datos = logic.obtener_datos_brutos_filtrado(str(ano), mes_str, sede, programa) \
|
|
if hasattr(logic, "obtener_datos_brutos_filtrado") else logic.obtener_datos_brutos(str(ano), mes_str)
|
|
|
|
# Intento 2: Mes de dos dígitos si el original no trajo nada
|
|
if not datos and len(mes_str) == 1:
|
|
mes_padded = mes_str.zfill(2)
|
|
print(f"[DEBUG Ventas] Sin resultados. Reintentando con mes acolchado: '{mes_padded}'")
|
|
datos = logic.obtener_datos_brutos_filtrado(str(ano), mes_padded, sede, programa) \
|
|
if hasattr(logic, "obtener_datos_brutos_filtrado") else logic.obtener_datos_brutos(str(ano), mes_padded)
|
|
|
|
datos = [d for d in (datos or []) if d.get('VENDEDOR', 'SIN VENDEDOR') != 'SIN VENDEDOR']
|
|
filas = logic.formatear_datos_para_tabla(datos)
|
|
print(f"[DEBUG Ventas] Proceso completado. Registros encontrados: {len(datos)}")
|
|
return {
|
|
"filas": _serializar_filas(filas),
|
|
"datos": _serializar_dicts(datos),
|
|
}
|
|
return cache_get_or_set("ventas", (ano, mes, sede, programa), _load)
|
|
|
|
def _clasif_filtro_programa(dm, dscp):
|
|
"""Misma clasificación de programa que ejecutar_consulta_ventas (SEMINARIOS/TEAC/TERC/OTROS)."""
|
|
up = str(dscp or "").upper()
|
|
fdata = dm.meta_data.get("clasificacion_filtro_programa", {})
|
|
for cat in ["OTROS", "SEMINARIOS", "TEAC", "TERC"]:
|
|
for pat in fdata.get(cat, {}).get("patrones", []):
|
|
if pat.upper() in up:
|
|
return cat
|
|
return fdata.get("DEFAULT", "OTROS") if isinstance(fdata.get("DEFAULT"), str) else "OTROS"
|
|
|
|
def ventas_detalle(vendedor, ano, mes, tipo_lista, sede="TODOS", programa="TODOS"):
|
|
def _load():
|
|
dm = get_dm()
|
|
dm._tc_override_mes = comisiones_tc_del_mes(ano, mes)
|
|
dm._fecha_canc_overrides = comisiones_overrides_fecha()
|
|
logic = VentasLogic(dm)
|
|
resultado = logic.obtener_detalle_vendedor(vendedor, str(ano), str(mes))
|
|
filas = resultado.get(tipo_lista, []) if isinstance(resultado, dict) else []
|
|
filas = _aplicar_overrides_comisiones(filas)
|
|
|
|
_sede = str(sede or "TODOS").upper()
|
|
_prog = str(programa or "TODOS").upper()
|
|
if _sede != "TODOS" or _prog != "TODOS":
|
|
out = []
|
|
for f in filas:
|
|
if str(f[0]).strip().upper() == "TOTAL GENERAL":
|
|
continue # el TOTAL se recalcula en el frontend
|
|
if len(f) < 15:
|
|
out.append(f); continue
|
|
if _sede != "TODOS" and str(f[14]).strip().upper() != _sede:
|
|
continue
|
|
_prog_nombre = f[16] if len(f) > 16 else f[8]
|
|
if _prog != "TODOS" and _clasif_filtro_programa(dm, _prog_nombre) != _prog:
|
|
continue
|
|
out.append(f)
|
|
filas = out
|
|
return {"filas": _serializar_filas(filas)}
|
|
return cache_get_or_set("ventas_det", (vendedor, ano, mes, tipo_lista, sede, programa), _load)
|
|
|
|
def comisiones_detalle_todos(ano, mes):
|
|
def _load():
|
|
dm = get_dm()
|
|
logic = VentasLogic(dm)
|
|
filas = logic.buscar_todos_matriculados()
|
|
filas = _aplicar_overrides_comisiones(filas)
|
|
return {"filas": _serializar_filas(filas)}
|
|
return cache_get_or_set("comisiones_det_todos", ("GLOBAL",), _load)
|
|
|
|
def comisiones_overrides_fecha():
|
|
try:
|
|
sb = _sb()
|
|
res = sb.table("comisiones_overrides").select("num_matricula,fecha_cancelacion1").execute()
|
|
out = {}
|
|
for r in (res.data or []):
|
|
mat = str(r["num_matricula"])
|
|
v = r.get("fecha_cancelacion1")
|
|
sv = "" if v is None else str(v).strip()
|
|
if sv == "__VACIO__":
|
|
out[mat] = "__VACIO__"
|
|
elif sv != "":
|
|
out[mat] = sv
|
|
return out
|
|
except Exception:
|
|
return {}
|
|
|
|
def comisiones_overrides_inv_neta():
|
|
try:
|
|
sb = _sb()
|
|
res = sb.table("comisiones_overrides").select("num_matricula,inversion_neta").execute()
|
|
out = {}
|
|
for r in (res.data or []):
|
|
v = r.get("inversion_neta")
|
|
if v is None:
|
|
continue
|
|
s = str(v).replace("S/", "").replace(",", "").strip()
|
|
if s == "":
|
|
continue
|
|
try: out[str(r["num_matricula"])] = float(s)
|
|
except Exception:
|
|
pass
|
|
return out
|
|
except Exception:
|
|
return {}
|
|
|
|
def _aplicar_overrides_comisiones(filas):
|
|
try:
|
|
dm = get_dm()
|
|
if not getattr(dm, "supabase_client", None):
|
|
return filas
|
|
res = dm.supabase_client.table("comisiones_overrides").select("*").execute()
|
|
ov = {str(r["num_matricula"]): r for r in (res.data or [])}
|
|
if not ov:
|
|
return filas
|
|
mapa = {
|
|
"fch_matricula": 2, "promedio_cuota": 5, "inversion_neta": 7,
|
|
"fecha_inicio": 9, "saldo_matricula": 10, "saldo_cuota1": 11,
|
|
"dias_anticipacion": 12, "valor_cuota_adicional": 13, "tipo_programa": 14,
|
|
"fecha_cancelacion1": 3,
|
|
}
|
|
for f in filas:
|
|
if len(f) < 16:
|
|
continue
|
|
mat = str(f[15])
|
|
if len(f) == 16:
|
|
f.append(f[8])
|
|
if len(f) == 17:
|
|
f.append("0")
|
|
if mat in ov:
|
|
r = ov[mat]
|
|
for col, idx in mapa.items():
|
|
val = r.get(col)
|
|
if val is None:
|
|
continue
|
|
if str(val) == "__VACIO__":
|
|
f[idx] = ""
|
|
elif str(val) != "":
|
|
f[idx] = val
|
|
f[17] = "1"
|
|
return filas
|
|
except Exception as e:
|
|
print(f"[overrides comisiones] {e}")
|
|
return filas
|
|
|
|
# ── COBRANZA ───────────────────────────────────────────────────────────────
|
|
def cobranza(ano, mes, sectorista="TODOS", agrupacion="SEDE"):
|
|
def _load():
|
|
dm = get_dm()
|
|
logic = CobranzaLogic(dm)
|
|
print(f"[DEBUG Cobranza] Buscando ano={ano}, mes={mes}, sectorista={sectorista}, agrupacion={agrupacion}")
|
|
|
|
mes_str = str(mes)
|
|
filas = logic.obtener_datos_tabla(str(ano), mes_str, sectorista, agrupacion)
|
|
|
|
if not filas and len(mes_str) == 1:
|
|
mes_padded = mes_str.zfill(2)
|
|
print(f"[DEBUG Cobranza] Sin resultados. Reintentando con mes acolchado: '{mes_padded}'")
|
|
filas = logic.obtener_datos_tabla(str(ano), mes_padded, sectorista, agrupacion)
|
|
|
|
sectoristas = logic.obtener_lista_sectoristas(str(ano), mes_str)
|
|
print(f"[DEBUG Cobranza] Proceso completado. Filas encontradas: {len(filas) if filas else 0}")
|
|
return {
|
|
"filas": _serializar_filas(filas),
|
|
"sectoristas": sectoristas or ["TODOS"],
|
|
}
|
|
return cache_get_or_set("cobranza", (ano, mes, sectorista, agrupacion), _load)
|
|
|
|
def cobranza_detalle(grupo, ano, mes, sectorista, agrupacion="PROGRAMA"):
|
|
def _load():
|
|
dm = get_dm()
|
|
logic = CobranzaLogic(dm)
|
|
if agrupacion == "ASESOR":
|
|
datos = logic.obtener_detalle_programa_formateado(str(ano), str(mes), grupo, "TODOS")
|
|
elif agrupacion == "SEDE":
|
|
crudos = logic.processor.obtener_detalle_programa(str(ano), str(mes), sectorista, "TODOS")
|
|
grupo_up = str(grupo).upper()
|
|
mats_sede = set()
|
|
for d in (crudos or []):
|
|
prog = d.get("PROGRAMA", "") or d.get("dsc_programa", "")
|
|
sede_det = str(logic.processor.clasificar_sede(prog)).upper()
|
|
if sede_det == grupo_up:
|
|
mats_sede.add(str(d.get("MATRICULA", "")))
|
|
todas = logic.obtener_detalle_programa_formateado(str(ano), str(mes), sectorista, "TODOS")
|
|
datos = [f for f in (todas or []) if str(f[0]) in mats_sede]
|
|
else:
|
|
datos = logic.obtener_detalle_programa_formateado(str(ano), str(mes), sectorista, grupo)
|
|
return {"filas": _serializar_filas(datos)}
|
|
return cache_get_or_set("cobranza_det", (grupo, ano, mes, sectorista, agrupacion), _load)
|
|
|
|
# ── RENTABILIDAD ───────────────────────────────────────────────────────────
|
|
def rentabilidad(ano, mes, sede="TODOS", filtro_prog="TODOS"):
|
|
def _load():
|
|
dm = get_dm()
|
|
logic = RentabilidadLogic(dm)
|
|
datos = logic.obtener_datos_procesados(str(ano), str(mes), sede, filtro_prog)
|
|
filas = logic.formatear_datos_para_tabla(datos)
|
|
filas_s = _serializar_filas(filas, 13)
|
|
nombres = [str(f[0]) for f in filas_s if "TOTAL" not in str(f[0]).upper()]
|
|
return {"filas": filas_s, "programas": nombres}
|
|
return cache_get_or_set("rentabilidad", (ano, mes, sede, filtro_prog), _load)
|
|
|
|
def rentabilidad_detalle(programa, ano, mes):
|
|
def _load():
|
|
dm = get_dm()
|
|
logic = RentabilidadLogic(dm)
|
|
resultado = logic.obtener_detalle_programa(programa, str(ano), str(mes))
|
|
return {"filas": _serializar_filas(resultado)}
|
|
return cache_get_or_set("rentabilidad_det", (programa, ano, mes), _load)
|
|
|
|
def rentabilidad_costos(programa, ano, mes):
|
|
def _load():
|
|
dm = get_dm()
|
|
logic = RentabilidadLogic(dm)
|
|
return {"costos": logic.obtener_datos_costos_programa(programa, str(ano), str(mes))}
|
|
return cache_get_or_set("rentabilidad_costos", (programa, ano, mes), _load)
|
|
|
|
# ── SALDO PENDIENTE ────────────────────────────────────────────────────────
|
|
def saldo_pendiente(tipo_cuota):
|
|
def _load():
|
|
dm = get_dm()
|
|
logic = SaldoLogic(dm)
|
|
datos = logic.obtener_saldos_consolidados(tipo_cuota)
|
|
return {"datos": _serializar_dicts(datos)}
|
|
return cache_get_or_set("saldo", (tipo_cuota,), _load)
|
|
|
|
# ── PRECARGA GLOBAL ────────────────────────────────────────────────────────
|
|
def precargar_todo():
|
|
from datetime import datetime
|
|
ahora = datetime.now()
|
|
ano, mes = ahora.year, ahora.month
|
|
tareas = [
|
|
lambda: ocupabilidad(ano, mes),
|
|
lambda: ventas(ano, mes),
|
|
lambda: cobranza(ano, mes, "TODOS", "SEDE"),
|
|
lambda: cobranza(ano, mes, "TODOS", "PROGRAMA"),
|
|
lambda: cobranza(ano, mes, "TODOS", "ASESOR"),
|
|
lambda: rentabilidad(ano, mes),
|
|
lambda: saldo_pendiente("1° Cuota"),
|
|
]
|
|
for t in tareas:
|
|
try:
|
|
t()
|
|
except Exception as e:
|
|
print(f"[precarga] error: {e}")
|
|
|
|
_refresh_count = 0
|
|
|
|
def _recalcular_clave(key):
|
|
from cache_manager import cache_invalidate
|
|
try:
|
|
prefijo, _, resto = key.partition(":")
|
|
args = resto.split("|") if resto else []
|
|
cache_invalidate(prefijo + ":" + resto if resto else prefijo)
|
|
if prefijo == "ocupabilidad" and len(args) == 4:
|
|
return ocupabilidad(args[0], args[1], args[2], args[3])
|
|
if prefijo == "ventas" and len(args) == 2:
|
|
return ventas(args[0], args[1])
|
|
if prefijo == "cobranza" and len(args) == 4:
|
|
return cobranza(args[0], args[1], args[2], args[3])
|
|
if prefijo == "rentabilidad" and len(args) == 4:
|
|
return rentabilidad(args[0], args[1], args[2], args[3])
|
|
if prefijo == "saldo" and len(args) == 1:
|
|
return saldo_pendiente(args[0])
|
|
if prefijo == "cobranza_det_todos" and len(args) == 3:
|
|
return cobranza_detalle_todos(args[0], args[1], args[2])
|
|
except Exception as e:
|
|
print(f"[recalcular] {key}: {e}")
|
|
return None
|
|
|
|
def refrescar_todo():
|
|
global _refresh_count
|
|
from cache_manager import cache_refresh_existing, cache_keys
|
|
_refresh_count += 1
|
|
if _refresh_count % 4 == 0:
|
|
try:
|
|
get_dm().cargar_toda_configuracion()
|
|
print("[config] Recarga de queries/JSON de GitHub completada")
|
|
except Exception as e:
|
|
print(f"[config] error al recargar GitHub: {e}")
|
|
if not cache_keys():
|
|
precargar_todo()
|
|
else:
|
|
cache_refresh_existing(_recalcular_clave)
|
|
|
|
# ── OPERACIONES EXTRA ──────────────────────────────────────────────────────
|
|
def guardar_costos(num_indice, costos_inicial, costos_actual):
|
|
dm = get_dm()
|
|
ok1 = dm.guardar_override_costo(num_indice, "inicial", costos_inicial)
|
|
ok2 = dm.guardar_override_costo(num_indice, "actual", costos_actual)
|
|
from cache_manager import cache_invalidate
|
|
cache_invalidate("rentabilidad")
|
|
cache_invalidate("rentabilidad_costos")
|
|
return bool(ok1 and ok2)
|
|
|
|
def clasificar_programas(nombres):
|
|
dm = get_dm()
|
|
logic = CobranzaLogic(dm)
|
|
return {n: str(logic.processor.clasificar_sede(n)) for n in nombres}
|
|
|
|
def cobranza_detalle_todos(ano, mes, sectorista="TODOS"):
|
|
def _load():
|
|
dm = get_dm()
|
|
logic = CobranzaLogic(dm)
|
|
brutos = logic.processor.obtener_detalle_programa(str(ano), str(mes), sectorista, "TODOS")
|
|
alumnos = []
|
|
for d in (brutos or []):
|
|
prog = d.get("Programa", d.get("PROGRAMA", "-")) or "-"
|
|
cta_ant = float(d.get("CTA_COB_ANT", 0.0)); cob_ant = float(d.get("COB_ANT", 0.0))
|
|
cta_cur = float(d.get("CTA_COB_MES_CURSO", 0.0)); cob_cur = float(d.get("COB_MES_CURSO", 0.0))
|
|
cta_tot = float(d.get("TOTAL_CTA_COB", 0.0)); cob_tot = float(d.get("TOTAL_COBRADO", 0.0))
|
|
saldo = float(d.get("SALDO", 0.0))
|
|
alumnos.append({
|
|
"matricula": str(d.get("MATRICULA", "")),
|
|
"alumno": d.get("ALUMNO", d.get("Alumno", "")),
|
|
"programa": prog,
|
|
"sede": str(logic.processor.clasificar_sede(prog)),
|
|
"frecuencia": d.get("FRECUENCIA", d.get("Frecuencia", "-")),
|
|
"num_cuota": d.get("NUM_CUOTA", "-"),
|
|
"fch_venc": d.get("FCH_VENC", "-"),
|
|
"cta_ant": cta_ant, "cob_ant": cob_ant,
|
|
"cta_cur": cta_cur, "cob_cur": cob_cur,
|
|
"cta_tot": cta_tot, "cob_tot": cob_tot,
|
|
"saldo": saldo,
|
|
})
|
|
return {"alumnos": alumnos}
|
|
return cache_get_or_set("cobranza_det_todos", (ano, mes, sectorista), _load)
|
|
|
|
# ── GESTIÓN DE USUARIOS ────────────────────────────────────────────────────
|
|
def _sb():
|
|
dm = get_dm()
|
|
if not dm.supabase_client:
|
|
raise RuntimeError("Supabase no está conectado")
|
|
return dm.supabase_client
|
|
|
|
def usuarios_listar():
|
|
sb = _sb()
|
|
perfiles = sb.table("perfiles").select("id, nombre, rol, activo").execute().data or []
|
|
try:
|
|
users = sb.auth.admin.list_users()
|
|
lista = users if isinstance(users, list) else getattr(users, "users", [])
|
|
correo_por_id = {str(u.id): u.email for u in lista}
|
|
except Exception:
|
|
correo_por_id = {}
|
|
out = []
|
|
for p in perfiles:
|
|
out.append({
|
|
"id": p["id"],
|
|
"email": correo_por_id.get(str(p["id"]), "-"),
|
|
"nombre": p.get("nombre"),
|
|
"rol": p.get("rol"),
|
|
"activo": p.get("activo", True),
|
|
})
|
|
return {"usuarios": out}
|
|
|
|
def usuarios_crear(email, password, nombre, rol):
|
|
sb = _sb()
|
|
res = sb.auth.admin.create_user({
|
|
"email": email,
|
|
"password": password,
|
|
"email_confirm": True,
|
|
})
|
|
user = getattr(res, "user", None) or res
|
|
uid = str(user.id)
|
|
sb.table("perfiles").upsert({"id": uid, "nombre": nombre or email, "rol": rol, "activo": True}).execute()
|
|
return {"status": "ok", "id": uid}
|
|
|
|
def usuarios_actualizar_rol(user_id, rol):
|
|
sb = _sb()
|
|
sb.table("perfiles").update({"rol": rol}).eq("id", user_id).execute()
|
|
return {"status": "ok"}
|
|
|
|
def usuarios_eliminar(user_id):
|
|
sb = _sb()
|
|
sb.auth.admin.delete_user(user_id)
|
|
return {"status": "ok"}
|
|
|
|
# ── CONFIG MENSUAL COMISIONES ──────────────────────────────────────────────
|
|
def comisiones_config_listar(ano, mes):
|
|
sb = _sb()
|
|
res = sb.table("comisiones_config_mensual").select("*").eq("ano", int(ano)).eq("mes", int(mes)).execute()
|
|
return {"config": res.data or []}
|
|
|
|
def comisiones_config_guardar(ano, mes, filas):
|
|
sb = _sb()
|
|
sb.table("comisiones_config_mensual").delete().eq("ano", int(ano)).eq("mes", int(mes)).execute()
|
|
payload = []
|
|
for f in filas:
|
|
payload.append({
|
|
"ano": int(ano), "mes": int(mes),
|
|
"tipo_programa": f.get("tipo_programa", ""),
|
|
"imp_tc": float(f.get("imp_tc", 0) or 0),
|
|
"meta": float(f.get("meta", 0) or 0),
|
|
"comision": float(f.get("comision", 0) or 0),
|
|
})
|
|
if payload:
|
|
sb.table("comisiones_config_mensual").insert(payload).execute()
|
|
from cache_manager import cache_invalidate
|
|
cache_invalidate("ventas")
|
|
cache_invalidate("ventas_det")
|
|
return {"status": "ok"}
|
|
|
|
def comisiones_tc_del_mes(ano, mes):
|
|
try:
|
|
sb = _sb()
|
|
res = sb.table("comisiones_config_mensual").select("imp_tc").eq("ano", int(ano)).eq("mes", int(mes)).execute()
|
|
for r in (res.data or []):
|
|
tc = float(r.get("imp_tc", 0) or 0)
|
|
if tc > 0: return tc
|
|
except Exception:
|
|
pass
|
|
return None
|
|
|
|
def vendedores_manuales_listar(ano, mes):
|
|
sb = _sb()
|
|
query = sb.table("vendedores_manuales").select("*").eq("ano", int(ano))
|
|
if int(mes) != 0:
|
|
query = query.eq("mes", int(mes))
|
|
res = query.execute()
|
|
return {"vendedores": res.data or []}
|
|
|
|
def vendedores_manuales_crear(nombre, descripcion, fch_emision, monto, ano, mes):
|
|
sb = _sb()
|
|
payload = {
|
|
"nombre": nombre, "descripcion": descripcion or "",
|
|
"fch_emision": fch_emision or "", "monto": float(monto or 0),
|
|
"ano": int(ano), "mes": int(mes),
|
|
}
|
|
res = sb.table("vendedores_manuales").insert(payload).execute()
|
|
return {"status": "ok", "data": res.data}
|
|
|
|
def vendedores_manuales_eliminar(id):
|
|
sb = _sb()
|
|
sb.table("vendedores_manuales").delete().eq("id", id).execute()
|
|
return {"status": "ok"}
|
|
|
|
def vendedores_manuales_actualizar(id, descripcion, fch_emision, monto):
|
|
sb = _sb()
|
|
sb.table("vendedores_manuales").update({
|
|
"descripcion": descripcion or "", "fch_emision": fch_emision or "",
|
|
"monto": float(monto or 0),
|
|
}).eq("id", id).execute()
|
|
return {"status": "ok"}
|
|
|
|
def vendedores_manuales_guardar_lote(nombre, ano, mes, filas):
|
|
sb = _sb()
|
|
sb.table("vendedores_manuales").delete().eq("nombre", nombre).eq("ano", int(ano)).eq("mes", int(mes)).execute()
|
|
payload = []
|
|
for f in filas:
|
|
payload.append({
|
|
"nombre": nombre,
|
|
"descripcion": f.get("descripcion","") or "",
|
|
"fch_emision": f.get("fch_emision","") or "",
|
|
"monto": float(f.get("monto",0) or 0),
|
|
"ano": int(ano), "mes": int(mes),
|
|
})
|
|
if payload:
|
|
sb.table("vendedores_manuales").insert(payload).execute()
|
|
return {"status": "ok"}
|
|
|
|
# ── OVERRIDES DE COMISIONES ────────────────────────────────────────────────
|
|
def comisiones_override_guardar(registros):
|
|
sb = _sb()
|
|
from cache_manager import cache_invalidate
|
|
validos = [r for r in registros if r.get("num_matricula")]
|
|
if validos:
|
|
sb.table("comisiones_overrides").upsert(validos).execute()
|
|
cache_invalidate("ventas_det")
|
|
cache_invalidate("ventas")
|
|
cache_invalidate("comisiones_det_todos")
|
|
return {"status": "ok"}
|
|
|
|
def comisiones_override_restaurar(num_matricula):
|
|
sb = _sb()
|
|
from cache_manager import cache_invalidate
|
|
sb.table("comisiones_overrides").delete().eq("num_matricula", str(num_matricula)).execute()
|
|
cache_invalidate("ventas_det")
|
|
cache_invalidate("ventas")
|
|
cache_invalidate("comisiones_det_todos")
|
|
return {"status": "ok"} |