Actualizar backend/modules/ventas/logic.py
This commit is contained in:
@@ -25,10 +25,13 @@ class VentasLogic:
|
|||||||
return s
|
return s
|
||||||
return "LIMA"
|
return "LIMA"
|
||||||
|
|
||||||
def buscar_todos_matriculados(self):
|
def buscar_todos_matriculados(self, tc_por_mes=None):
|
||||||
"""Devuelve TODOS los matriculados (todos los años/meses) para el buscador.
|
"""Devuelve TODOS los matriculados (todos los años/meses) para el buscador.
|
||||||
Ejecuta la query de matrículas quitando el filtro de YEAR/MONTH de fch_matricula.
|
Ejecuta la query de matrículas quitando el filtro de YEAR/MONTH de fch_matricula.
|
||||||
Retorna filas con el mismo formato que el detalle (para reusar columnas)."""
|
Retorna filas con el mismo formato que el detalle (para reusar columnas).
|
||||||
|
tc_por_mes: dict {(ano,mes): imp_tc_del_mes} para convertir dólares según el
|
||||||
|
mes de la fecha de cancelación (si no hay TC del mes, usa el imp_tc del SQL)."""
|
||||||
|
tc_por_mes = tc_por_mes or {}
|
||||||
import re as _re
|
import re as _re
|
||||||
sql_base = self.data_manager.query_matriculas_sql
|
sql_base = self.data_manager.query_matriculas_sql
|
||||||
if not sql_base:
|
if not sql_base:
|
||||||
@@ -82,7 +85,7 @@ class VentasLogic:
|
|||||||
# CLASIFICAR sede: prioriza dsc_programa
|
# CLASIFICAR sede: prioriza dsc_programa
|
||||||
prog_clasificacion = (d.get('dsc_programa') or d.get('dsc_det_programa') or d.get('dsc_promocion') or '')
|
prog_clasificacion = (d.get('dsc_programa') or d.get('dsc_det_programa') or d.get('dsc_promocion') or '')
|
||||||
vendedor = d.get('dsc_vendedor', 'SIN VENDEDOR') or 'SIN VENDEDOR'
|
vendedor = d.get('dsc_vendedor', 'SIN VENDEDOR') or 'SIN VENDEDOR'
|
||||||
inv = d.get('INV_NETA', 0) or 0
|
inv = float(d.get('INV_NETA', 0) or 0)
|
||||||
saldo_mat = d.get('imp_saldo_matricula', 0) or 0
|
saldo_mat = d.get('imp_saldo_matricula', 0) or 0
|
||||||
saldo_c1 = d.get('imp_saldo_cuota1', 0) or 0
|
saldo_c1 = d.get('imp_saldo_cuota1', 0) or 0
|
||||||
def _fdate(v):
|
def _fdate(v):
|
||||||
@@ -93,7 +96,34 @@ class VentasLogic:
|
|||||||
return s
|
return s
|
||||||
f_mat = _fdate(d.get('fch_matricula'))
|
f_mat = _fdate(d.get('fch_matricula'))
|
||||||
f_ini = _fdate(d.get('fch_inicio'))
|
f_ini = _fdate(d.get('fch_inicio'))
|
||||||
f_canc = _fdate(fechas_canc.get(mat_id))
|
_canc_raw = fechas_canc.get(mat_id)
|
||||||
|
f_canc = _fdate(_canc_raw)
|
||||||
|
|
||||||
|
# Conversión a soles si la INV_NETA está en DÓLARES.
|
||||||
|
# TC según el MES/AÑO de la fecha de cancelación: si hay "IMP_TC del mes"
|
||||||
|
# configurado para ese mes, se usa; si no, se usa el imp_tc del SQL.
|
||||||
|
cod_moneda = str(d.get('cod_moneda', 'SOL') or 'SOL').strip().upper()
|
||||||
|
if cod_moneda == 'DOL':
|
||||||
|
tc = 0.0
|
||||||
|
# año/mes de la fecha de cancelación
|
||||||
|
_ym = None
|
||||||
|
if _canc_raw:
|
||||||
|
try:
|
||||||
|
if hasattr(_canc_raw, 'year'):
|
||||||
|
_ym = (int(_canc_raw.year), int(_canc_raw.month))
|
||||||
|
else:
|
||||||
|
_s = str(_canc_raw)[:10]
|
||||||
|
_p = _s.split('-')
|
||||||
|
if len(_p) == 3: _ym = (int(_p[0]), int(_p[1]))
|
||||||
|
except Exception:
|
||||||
|
_ym = None
|
||||||
|
if _ym and _ym in tc_por_mes:
|
||||||
|
tc = float(tc_por_mes[_ym]) # IMP_TC del mes de la cancelación
|
||||||
|
if tc <= 0:
|
||||||
|
try: tc = float(d.get('imp_tc', 0) or 0) # fallback: TC del SQL
|
||||||
|
except: tc = 0.0
|
||||||
|
if tc and tc > 0:
|
||||||
|
inv = round(inv * tc, 2)
|
||||||
sede = self._clasificar_sede_comisiones(prog_clasificacion)
|
sede = self._clasificar_sede_comisiones(prog_clasificacion)
|
||||||
# mismo layout que el detalle: 16 columnas
|
# mismo layout que el detalle: 16 columnas
|
||||||
fila = [
|
fila = [
|
||||||
@@ -164,8 +194,13 @@ class VentasLogic:
|
|||||||
lista_vip_str.append(str(val))
|
lista_vip_str.append(str(val))
|
||||||
except: pass
|
except: pass
|
||||||
|
|
||||||
# Lista PURA de BASE_PENDIENTES (sin overrides). Venta Pendientes usa SOLO esto.
|
# BASE_PENDIENTES: lista base + matrículas con override de fecha (editadas).
|
||||||
|
# Si editaste la fecha de cancelación de un alumno, también puede contar en
|
||||||
|
# "Venta Pendientes" (el filtro real de si cuenta lo hace la fecha del override
|
||||||
|
# más abajo; los vaciados NO cuentan por fecha_vaciada_override).
|
||||||
set_lista_pendientes = set(set_vip_actual)
|
set_lista_pendientes = set(set_vip_actual)
|
||||||
|
set_base_pura = set(set_vip_actual) # lista BASE sin overrides (para la regla)
|
||||||
|
set_override_fecha = set() # matrículas que entran por override
|
||||||
|
|
||||||
# Incluir matrículas con override de fecha (igual que ejecutar_consulta_ventas)
|
# Incluir matrículas con override de fecha (igual que ejecutar_consulta_ventas)
|
||||||
_fov_vip = getattr(self.data_manager, '_fecha_canc_overrides', None) or {}
|
_fov_vip = getattr(self.data_manager, '_fecha_canc_overrides', None) or {}
|
||||||
@@ -173,6 +208,8 @@ class VentasLogic:
|
|||||||
try:
|
try:
|
||||||
val = int(float(str(mk)))
|
val = int(float(str(mk)))
|
||||||
set_vip_actual.add(val)
|
set_vip_actual.add(val)
|
||||||
|
set_lista_pendientes.add(val) # las editadas también pueden ser pendientes
|
||||||
|
set_override_fecha.add(val)
|
||||||
if str(val) not in lista_vip_str:
|
if str(val) not in lista_vip_str:
|
||||||
lista_vip_str.append(str(val))
|
lista_vip_str.append(str(val))
|
||||||
except: pass
|
except: pass
|
||||||
@@ -224,15 +261,26 @@ class VentasLogic:
|
|||||||
# (una con fch_cancelacion y otra sin). Nos quedamos con UNA sola fila por
|
# (una con fch_cancelacion y otra sin). Nos quedamos con UNA sola fila por
|
||||||
# matrícula, priorizando la que TENGA fecha de cancelación (verdad del pago).
|
# matrícula, priorizando la que TENGA fecha de cancelación (verdad del pago).
|
||||||
_dedup = {}
|
_dedup = {}
|
||||||
|
_max_inv = {} # mat -> mayor INV_NETA visto (por si la fila elegida trae 0)
|
||||||
for _d in datos_raw:
|
for _d in datos_raw:
|
||||||
_m = str(_d.get('num_matricula', '')).strip()
|
_m = str(_d.get('num_matricula', '')).strip()
|
||||||
if _m.endswith('.0'): _m = _m[:-2]
|
if _m.endswith('.0'): _m = _m[:-2]
|
||||||
|
try: _inv_d = float(_d.get('INV_NETA', 0) or 0)
|
||||||
|
except: _inv_d = 0.0
|
||||||
|
if _inv_d > _max_inv.get(_m, 0.0): _max_inv[_m] = _inv_d
|
||||||
_prev = _dedup.get(_m)
|
_prev = _dedup.get(_m)
|
||||||
_tiene_f = bool(_d.get('fch_cancelacion_cuota1'))
|
_tiene_f = bool(_d.get('fch_cancelacion_cuota1'))
|
||||||
if _prev is None:
|
if _prev is None:
|
||||||
_dedup[_m] = _d
|
_dedup[_m] = _d
|
||||||
elif _tiene_f and not bool(_prev.get('fch_cancelacion_cuota1')):
|
elif _tiene_f and not bool(_prev.get('fch_cancelacion_cuota1')):
|
||||||
_dedup[_m] = _d # reemplazar por la que sí tiene fecha
|
_dedup[_m] = _d # reemplazar por la que sí tiene fecha
|
||||||
|
# Si la fila elegida quedó con INV_NETA 0 pero otra fila de la misma matrícula
|
||||||
|
# traía el monto, recuperarlo (el LEFT JOIN puede partir fecha y monto).
|
||||||
|
for _m, _d in _dedup.items():
|
||||||
|
try: _inv_cur = float(_d.get('INV_NETA', 0) or 0)
|
||||||
|
except: _inv_cur = 0.0
|
||||||
|
if _inv_cur <= 0 and _max_inv.get(_m, 0.0) > 0:
|
||||||
|
_d['INV_NETA'] = _max_inv[_m]
|
||||||
datos_raw = list(_dedup.values())
|
datos_raw = list(_dedup.values())
|
||||||
|
|
||||||
# 🔥 DESCARGA DEL CRONOGRAMA COMPLETO (SIN IMPORTAR EL MES) 🔥
|
# 🔥 DESCARGA DEL CRONOGRAMA COMPLETO (SIN IMPORTAR EL MES) 🔥
|
||||||
@@ -386,6 +434,16 @@ class VentasLogic:
|
|||||||
if cod_moneda == 'DOL': inv_neta_soles = round(inv_neta_raw * imp_tc, 2)
|
if cod_moneda == 'DOL': inv_neta_soles = round(inv_neta_raw * imp_tc, 2)
|
||||||
else: inv_neta_soles = round(inv_neta_raw, 2)
|
else: inv_neta_soles = round(inv_neta_raw, 2)
|
||||||
|
|
||||||
|
# Override de INVERSIÓN NETA (Supabase): monto final en soles, reemplaza
|
||||||
|
# al SQL. Igual que en data_manager, para que editados no salgan en S/ 0.
|
||||||
|
_inv_ov = getattr(self.data_manager, '_inv_neta_overrides', None)
|
||||||
|
if _inv_ov:
|
||||||
|
_mk_inv = str(mat_id).strip()
|
||||||
|
if _mk_inv.endswith('.0'): _mk_inv = _mk_inv[:-2]
|
||||||
|
if _mk_inv in _inv_ov:
|
||||||
|
try: inv_neta_soles = round(float(_inv_ov[_mk_inv]), 2)
|
||||||
|
except: pass
|
||||||
|
|
||||||
row_year, row_month = -1, -1
|
row_year, row_month = -1, -1
|
||||||
fecha_mat_limpia = ""
|
fecha_mat_limpia = ""
|
||||||
if fch_mat:
|
if fch_mat:
|
||||||
@@ -410,6 +468,8 @@ class VentasLogic:
|
|||||||
except: fch_canc_limpia = str(fch_canc_raw)[:10]
|
except: fch_canc_limpia = str(fch_canc_raw)[:10]
|
||||||
|
|
||||||
clase_alumno = self.clasificar_programa(nombre_prog_crudo)
|
clase_alumno = self.clasificar_programa(nombre_prog_crudo)
|
||||||
|
# Sede del alumno (LIMA/AREQUIPA/PIURA/TRUJILLO) para la regla de PROMEDIO CUOTA.
|
||||||
|
_sede_prom = self._clasificar_sede_comisiones(nombre_prog_crudo)
|
||||||
linea_actual = self.identificar_linea_carrera(nombre_prog_crudo)
|
linea_actual = self.identificar_linea_carrera(nombre_prog_crudo)
|
||||||
fecha_inicio_actual = fecha_inicio_prog.get(nombre_prog_crudo)
|
fecha_inicio_actual = fecha_inicio_prog.get(nombre_prog_crudo)
|
||||||
|
|
||||||
@@ -484,18 +544,13 @@ class VentasLogic:
|
|||||||
# ===============================================================
|
# ===============================================================
|
||||||
# DIVISIÓN SEGÚN CLASIFICACIÓN
|
# DIVISIÓN SEGÚN CLASIFICACIÓN
|
||||||
# ===============================================================
|
# ===============================================================
|
||||||
if suma_cuotas > 0:
|
# PROMEDIO CUOTA: SOLO para LIMA TEAC o LIMA TERC → suma cuotas / 5.
|
||||||
if clase_alumno in ["TEAC", "TERC", "CARRERA"]:
|
# Cualquier otro (seminarios, otras sedes, CARRERA/OTROS) → VACÍO.
|
||||||
|
if suma_cuotas > 0 and _sede_prom == "LIMA" and clase_alumno in ["TEAC", "TERC"]:
|
||||||
valor_cuota_final = suma_cuotas / 5.0
|
valor_cuota_final = suma_cuotas / 5.0
|
||||||
elif clase_alumno in ["AREQUIPA", "TRUJILLO", "PIURA"]:
|
|
||||||
valor_cuota_final = suma_cuotas / 6.0
|
|
||||||
else:
|
|
||||||
valor_cuota_final = suma_cuotas
|
|
||||||
|
|
||||||
str_cuota_val = f"S/ {valor_cuota_final:,.0f}"
|
str_cuota_val = f"S/ {valor_cuota_final:,.0f}"
|
||||||
else:
|
else:
|
||||||
valor_cuota_final = 0.0
|
valor_cuota_final = 0.0
|
||||||
# Si no tiene cuotas mayores a 0, se queda en blanco limpio
|
|
||||||
str_cuota_val = ""
|
str_cuota_val = ""
|
||||||
|
|
||||||
# ===============================================================
|
# ===============================================================
|
||||||
@@ -583,7 +638,9 @@ class VentasLogic:
|
|||||||
# Índice [15]: num_matricula (clave para overrides de Comisiones)
|
# Índice [15]: num_matricula (clave para overrides de Comisiones)
|
||||||
mat_id,
|
mat_id,
|
||||||
# Índice [16]: nombre para CLASIFICAR (prioriza dsc_programa). Oculto en UI.
|
# Índice [16]: nombre para CLASIFICAR (prioriza dsc_programa). Oculto en UI.
|
||||||
prog_clasificacion
|
prog_clasificacion,
|
||||||
|
# Índice [17]: moneda (SOL/DOL) — para el export de Comisiones.
|
||||||
|
cod_moneda,
|
||||||
]
|
]
|
||||||
|
|
||||||
es_venta_del_mes = (str(row_year) == str(ano)) and (str(row_month) == str(mes_numero))
|
es_venta_del_mes = (str(row_year) == str(ano)) and (str(row_month) == str(mes_numero))
|
||||||
@@ -638,18 +695,37 @@ class VentasLogic:
|
|||||||
# VENTA PENDIENTES: SOLO de tu lista BASE_PENDIENTES y que pagaron su 1ª
|
# VENTA PENDIENTES: SOLO de tu lista BASE_PENDIENTES y que pagaron su 1ª
|
||||||
# cuota EN EL MES/AÑO DEL FILTRO. Usa la FECHA REAL DEL SQL (sin override),
|
# cuota EN EL MES/AÑO DEL FILTRO. Usa la FECHA REAL DEL SQL (sin override),
|
||||||
# porque los overrides NO deben afectar Venta Pendientes.
|
# porque los overrides NO deben afectar Venta Pendientes.
|
||||||
|
# El OVERRIDE manda: usa la fecha con override aplicado (fch_canc_raw).
|
||||||
|
# Si el override la vació, NO cuenta (aunque el SQL tenga fecha).
|
||||||
|
# Parseo tolerante: acepta datetime, 'yyyy-mm-dd' o 'dd/mm/yyyy'.
|
||||||
_pago_mes_real = False
|
_pago_mes_real = False
|
||||||
if fch_canc_real_sql and str(fch_canc_real_sql).strip() != "None":
|
if not fecha_vaciada_override and fch_canc_raw and str(fch_canc_raw).strip() not in ("", "None"):
|
||||||
|
_fa, _fm = None, None
|
||||||
try:
|
try:
|
||||||
if isinstance(fch_canc_real_sql, str):
|
if hasattr(fch_canc_raw, "year"):
|
||||||
_fo = datetime.strptime(fch_canc_real_sql[:10], '%Y-%m-%d')
|
_fa, _fm = fch_canc_raw.year, fch_canc_raw.month
|
||||||
_fa, _fm = _fo.year, _fo.month
|
|
||||||
else:
|
else:
|
||||||
_fa, _fm = fch_canc_real_sql.year, fch_canc_real_sql.month
|
_s = str(fch_canc_raw).strip()[:10]
|
||||||
if int(_fa) == int(ano) and int(_fm) == int(mes_numero):
|
if len(_s) == 10 and _s[4] == '-': # yyyy-mm-dd
|
||||||
|
_p = _s.split('-'); _fa, _fm = int(_p[0]), int(_p[1])
|
||||||
|
elif len(_s) == 10 and _s[2] == '/': # dd/mm/yyyy
|
||||||
|
_p = _s.split('/'); _fa, _fm = int(_p[2]), int(_p[1])
|
||||||
|
if _fa is not None and int(_fa) == int(ano) and int(_fm) == int(mes_numero):
|
||||||
_pago_mes_real = True
|
_pago_mes_real = True
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# Las que entran SOLO por override de fecha (no están en la lista base)
|
||||||
|
# deben ser de un mes ANTERIOR al filtro: Venta Pendientes = "mes pasado".
|
||||||
|
_es_pend = matricula_int in set_lista_pendientes
|
||||||
|
if _es_pend and matricula_int in set_override_fecha and matricula_int not in set_base_pura:
|
||||||
|
_mat_anterior = False
|
||||||
|
try:
|
||||||
|
_ym_mat = int(row_year) * 100 + int(row_month)
|
||||||
|
_ym_filtro = int(ano) * 100 + int(mes_numero)
|
||||||
|
_mat_anterior = _ym_mat < _ym_filtro
|
||||||
except: pass
|
except: pass
|
||||||
if matricula_int in set_lista_pendientes and _pago_mes_real:
|
_es_pend = _mat_anterior
|
||||||
|
if _es_pend and _pago_mes_real:
|
||||||
agregar_a_lista("Venta Pendientes")
|
agregar_a_lista("Venta Pendientes")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user