130 lines
6.1 KiB
Python
130 lines
6.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
from dateutil.relativedelta import relativedelta
|
|
|
|
from odoo import models, fields, api
|
|
|
|
from ..lealtad import get_lealtad, nivel_por_codigo, nivel_para_gasto
|
|
|
|
|
|
class ResPartner(models.Model):
|
|
_inherit = 'res.partner'
|
|
|
|
# Relación inversa con monedero
|
|
monedero_ids = fields.One2many('skeen.monedero', 'partner_id', string='Monederos')
|
|
wallet_points = fields.Integer(string='Puntos Monedero', compute='_compute_wallet_points', store=True)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Nivel de lealtad (VIP). El nivel se decide por el gasto acumulado en
|
|
# una ventana y se guarda su código (los niveles viven en el bloque
|
|
# `lealtad` de los ajustes). `lealtad_manual` + `lealtad_nivel_revisar`
|
|
# permiten asignarlo a mano sin que se sobreescriba solo.
|
|
# ------------------------------------------------------------------
|
|
lealtad_nivel_codigo = fields.Char(string='Nivel de lealtad', copy=False)
|
|
lealtad_nivel_desde = fields.Date(string='Nivel desde', copy=False)
|
|
lealtad_nivel_revisar = fields.Date(string='Revisar nivel el', copy=False)
|
|
lealtad_manual = fields.Boolean(string='Nivel asignado a mano', copy=False,
|
|
default=False)
|
|
lealtad_nivel_nombre = fields.Char(
|
|
string='Nivel (nombre)', compute='_compute_lealtad_beneficios')
|
|
lealtad_descuento_pct = fields.Float(
|
|
string='Descuento de nivel (%)', compute='_compute_lealtad_beneficios')
|
|
lealtad_factor_puntos = fields.Float(
|
|
string='Factor de puntos', compute='_compute_lealtad_beneficios')
|
|
lealtad_gasto = fields.Float(
|
|
string='Gasto en la ventana', compute='_compute_lealtad_beneficios')
|
|
|
|
@api.depends('monedero_ids.points')
|
|
def _compute_wallet_points(self):
|
|
for partner in self:
|
|
monedero = partner.monedero_ids[:1]
|
|
partner.wallet_points = monedero.points if monedero else 0
|
|
|
|
@api.depends('lealtad_nivel_codigo')
|
|
def _compute_lealtad_beneficios(self):
|
|
cfg = get_lealtad(self.env)
|
|
for partner in self:
|
|
nivel = nivel_por_codigo(cfg, partner.lealtad_nivel_codigo)
|
|
partner.lealtad_nivel_nombre = (nivel or {}).get('nombre') or ''
|
|
partner.lealtad_descuento_pct = float((nivel or {}).get('descuento_pct') or 0.0)
|
|
partner.lealtad_factor_puntos = float((nivel or {}).get('factor_puntos') or 1.0)
|
|
ventana = 12
|
|
if cfg.get('niveles'):
|
|
ventana = max(int(n.get('ventana_meses') or 12) for n in cfg['niveles'])
|
|
partner.lealtad_gasto = partner._lealtad_gasto_ventana(ventana)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Consultas y recálculo de nivel
|
|
# ------------------------------------------------------------------
|
|
def _lealtad_gasto_ventana(self, meses):
|
|
"""Gasto (suma de ventas no canceladas) del paciente en `meses` meses."""
|
|
self.ensure_one()
|
|
meses = max(int(meses or 12), 1)
|
|
desde = fields.Date.today() - relativedelta(months=meses)
|
|
ventas = self.env['skeen.venta'].sudo().search([
|
|
('partner_id', '=', self.id),
|
|
('state', '!=', 'cancelled'),
|
|
('date', '>=', desde),
|
|
])
|
|
return sum(v.total for v in ventas)
|
|
|
|
def _lealtad_recalcular(self, cfg=None):
|
|
"""Recalcula el nivel VIP de los pacientes, respetando vigencias.
|
|
|
|
- Un nivel no baja de inmediato: se conserva hasta que venza su
|
|
`vigencia_meses` contados desde `lealtad_nivel_desde`.
|
|
- Una asignación manual con fecha de revisión futura no se toca.
|
|
Solo aplica con `modo='configurable'`; en clásico no hay niveles.
|
|
"""
|
|
cfg = cfg or get_lealtad(self.env)
|
|
if not cfg.get('activo') or cfg.get('modo') != 'configurable' or not cfg.get('niveles'):
|
|
return 0
|
|
hoy = fields.Date.today()
|
|
cambios = 0
|
|
for partner in self:
|
|
if partner.lealtad_manual and partner.lealtad_nivel_revisar \
|
|
and partner.lealtad_nivel_revisar > hoy:
|
|
continue
|
|
vals = {}
|
|
if partner.lealtad_manual and partner.lealtad_nivel_revisar \
|
|
and partner.lealtad_nivel_revisar <= hoy:
|
|
partner.lealtad_manual = False
|
|
partner.lealtad_nivel_revisar = False
|
|
ventana = max(int(n.get('ventana_meses') or 12) for n in cfg['niveles'])
|
|
gasto = partner._lealtad_gasto_ventana(ventana)
|
|
nuevo = nivel_para_gasto(cfg, gasto)
|
|
actual = nivel_por_codigo(cfg, partner.lealtad_nivel_codigo)
|
|
# Bajar de nivel: solo cuando venció la vigencia del actual.
|
|
if actual and (nuevo is None or nuevo['gasto_minimo'] < actual['gasto_minimo']):
|
|
desde = partner.lealtad_nivel_desde
|
|
vence = (desde + relativedelta(
|
|
months=int(actual.get('vigencia_meses') or 0))) if desde else None
|
|
if vence and hoy < vence:
|
|
continue
|
|
if not nuevo:
|
|
if partner.lealtad_nivel_codigo:
|
|
vals = {'lealtad_nivel_codigo': False, 'lealtad_nivel_desde': False}
|
|
elif partner.lealtad_manual:
|
|
vals = {'lealtad_manual': False, 'lealtad_nivel_revisar': False}
|
|
elif nuevo['codigo'] != partner.lealtad_nivel_codigo:
|
|
vals = {
|
|
'lealtad_nivel_codigo': nuevo['codigo'],
|
|
'lealtad_nivel_desde': hoy,
|
|
'lealtad_manual': False,
|
|
'lealtad_nivel_revisar': False,
|
|
}
|
|
if vals:
|
|
partner.write(vals)
|
|
cambios += 1
|
|
return cambios
|
|
|
|
@api.model
|
|
def _lealtad_recalcular_todos(self):
|
|
"""Proceso diario: recalcula el nivel de todos los pacientes."""
|
|
cfg = get_lealtad(self.env)
|
|
if not cfg.get('activo') or cfg.get('modo') != 'configurable' or not cfg.get('niveles'):
|
|
return True
|
|
partners = self.sudo().search([('is_patient', '=', True)])
|
|
partners._lealtad_recalcular(cfg)
|
|
return True
|