# -*- 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