149 lines
6.2 KiB
Python
149 lines
6.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Liquidación de comisiones por periodo.
|
|
|
|
Es el único lugar donde la comisión se PERSISTE: al generar el borrador se corre
|
|
el motor y el desglose completo (por venta/línea/regla y metas alcanzadas) queda
|
|
congelado en `detalle` (JSON). Así el recibo de un periodo ya cerrado no cambia
|
|
aunque después se editen reglas, precios o devoluciones.
|
|
|
|
Estados: borrador -> pagada, o borrador -> cancelada. Una liquidación pagada o
|
|
cancelada es inmutable (se protege el histórico de lo ya entregado).
|
|
"""
|
|
|
|
import json
|
|
|
|
from odoo import models, fields, api
|
|
from odoo.exceptions import UserError
|
|
|
|
ESTADOS = ('borrador', 'pagada', 'cancelada')
|
|
ESTADO_LABEL = {'borrador': 'Borrador', 'pagada': 'Pagada', 'cancelada': 'Cancelada'}
|
|
|
|
|
|
class SkeenComisionLiquidacion(models.Model):
|
|
_name = 'skeen.comision.liquidacion'
|
|
_description = 'Liquidación de comisiones SKEEN'
|
|
_order = 'periodo_inicio desc, id desc'
|
|
|
|
name = fields.Char(string='Folio', readonly=True, copy=False, index=True)
|
|
empleado_id = fields.Many2one('hr.employee', string='Empleado', required=True,
|
|
ondelete='restrict', index=True)
|
|
periodo_inicio = fields.Date(string='Periodo desde', required=True)
|
|
periodo_fin = fields.Date(string='Periodo hasta', required=True)
|
|
periodo = fields.Char(string='Periodo (AAAA-MM)', compute='_compute_periodo',
|
|
store=True, index=True)
|
|
|
|
total_calculado = fields.Float(string='Comisión calculada', default=0.0)
|
|
total_ajustes = fields.Float(string='Ajustes', compute='_compute_total',
|
|
store=True)
|
|
total = fields.Float(string='Total a pagar', compute='_compute_total', store=True)
|
|
|
|
estado = fields.Selection([(e, ESTADO_LABEL[e]) for e in ESTADOS],
|
|
string='Estado', default='borrador', required=True,
|
|
index=True)
|
|
fecha_pago = fields.Date(string='Fecha de pago')
|
|
metodo_pago = fields.Char(string='Método de pago')
|
|
referencia = fields.Char(string='Referencia')
|
|
notas = fields.Text(string='Notas')
|
|
detalle = fields.Text(string='Desglose (JSON)')
|
|
creado_por_login = fields.Char(string='Creada por')
|
|
pagada_por_login = fields.Char(string='Pagada por')
|
|
|
|
ajuste_ids = fields.One2many('skeen.comision.ajuste', 'liquidacion_id',
|
|
string='Ajustes')
|
|
|
|
_sql_constraints = [
|
|
('unique_empleado_periodo',
|
|
'unique(empleado_id, periodo_inicio, periodo_fin)',
|
|
'Ya existe una liquidación de este empleado para el mismo periodo.'),
|
|
]
|
|
|
|
@api.depends('periodo_inicio')
|
|
def _compute_periodo(self):
|
|
for l in self:
|
|
l.periodo = l.periodo_inicio.strftime('%Y-%m') if l.periodo_inicio else ''
|
|
|
|
@api.depends('ajuste_ids.monto', 'total_calculado')
|
|
def _compute_total(self):
|
|
for l in self:
|
|
l.total_ajustes = sum(a.monto for a in l.ajuste_ids)
|
|
l.total = (l.total_calculado or 0.0) + l.total_ajustes
|
|
|
|
@api.model_create_multi
|
|
def create(self, vals_list):
|
|
seq = self.env['ir.sequence'].sudo().search(
|
|
[('code', '=', 'skeen.comision.liquidacion')], limit=1)
|
|
for vals in vals_list:
|
|
if not vals.get('name'):
|
|
vals['name'] = (seq.next_by_id() if seq else False) or 'LIQ-0001'
|
|
return super().create(vals_list)
|
|
|
|
def ajustar(self, monto, motivo='', usuario=''):
|
|
"""Agrega un ajuste manual con signo (+/-)."""
|
|
self.ensure_one()
|
|
if self.estado != 'borrador':
|
|
raise UserError('Solo una liquidación en borrador admite ajustes.')
|
|
try:
|
|
monto = float(monto)
|
|
except (TypeError, ValueError):
|
|
raise UserError('El monto del ajuste debe ser un número.')
|
|
if abs(monto) < 1e-9:
|
|
raise UserError('El monto del ajuste no puede ser cero.')
|
|
return self.env['skeen.comision.ajuste'].create({
|
|
'liquidacion_id': self.id,
|
|
'empleado_id': self.empleado_id.id,
|
|
'periodo': self.periodo,
|
|
'monto': monto,
|
|
'motivo': (motivo or '').strip(),
|
|
'usuario': usuario or '',
|
|
'fecha': fields.Datetime.now(),
|
|
})
|
|
|
|
def pagar(self, fecha=None, metodo='', referencia='', usuario=''):
|
|
self.ensure_one()
|
|
if self.estado != 'borrador':
|
|
raise UserError('Solo una liquidación en borrador se puede pagar.')
|
|
self.write({
|
|
'estado': 'pagada',
|
|
'fecha_pago': fecha or fields.Date.today(),
|
|
'metodo_pago': (metodo or '').strip(),
|
|
'referencia': (referencia or '').strip(),
|
|
'pagada_por_login': usuario or '',
|
|
})
|
|
return True
|
|
|
|
def cancelar(self, motivo=''):
|
|
self.ensure_one()
|
|
if self.estado == 'cancelada':
|
|
raise UserError('La liquidación ya está cancelada.')
|
|
if self.estado == 'pagada':
|
|
raise UserError('Una liquidación pagada no se cancela; registra un ajuste.')
|
|
texto = (self.notas or '').strip()
|
|
self.write({'estado': 'cancelada',
|
|
'notas': (texto + ('\n' if texto else '') +
|
|
'Cancelada: ' + (motivo or 'sin motivo')).strip()})
|
|
return True
|
|
|
|
def detalle_dict(self):
|
|
self.ensure_one()
|
|
try:
|
|
return json.loads(self.detalle or '{}')
|
|
except (ValueError, TypeError):
|
|
return {}
|
|
|
|
|
|
class SkeenComisionAjuste(models.Model):
|
|
_name = 'skeen.comision.ajuste'
|
|
_description = 'Ajuste manual de comisión'
|
|
_order = 'fecha desc, id desc'
|
|
|
|
liquidacion_id = fields.Many2one('skeen.comision.liquidacion', string='Liquidación',
|
|
ondelete='cascade', index=True)
|
|
empleado_id = fields.Many2one('hr.employee', string='Empleado', required=True,
|
|
ondelete='cascade', index=True)
|
|
periodo = fields.Char(string='Periodo (AAAA-MM)')
|
|
monto = fields.Float(string='Monto', default=0.0,
|
|
help='Positivo suma a la comisión; negativo la reduce.')
|
|
motivo = fields.Char(string='Motivo')
|
|
usuario = fields.Char(string='Usuario')
|
|
fecha = fields.Datetime(string='Fecha', default=fields.Datetime.now)
|