Oleada UX 4: Pagos/Adeudos (cobro en 1 click con recibo, plan de pagos), Monedero (movimientos narrados, ajustes auditados, MXN+USD), Inventario (alta rapida, conteo fisico con ajuste e historial, alertas accionables) + fix de folios de pago y cantidades de movimientos
This commit is contained in:
@@ -107,22 +107,119 @@ class SkeenInventarioMove(models.Model):
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
moves = super().create(vals_list)
|
||||
for m in moves:
|
||||
item = m.item_id
|
||||
if not item:
|
||||
# Las existencias anterior/resultante se calculan ANTES del INSERT: si
|
||||
# se escribieran después con write(), Odoo omite el UPDATE cuando el
|
||||
# valor coincide con el de la caché (p. ej. after_qty = 0) y la columna
|
||||
# quedaría NULL en la base.
|
||||
Item = self.env['skeen.inventario.item'].sudo()
|
||||
for vals in vals_list:
|
||||
item = Item.browse(vals.get('item_id'))
|
||||
if not item.exists():
|
||||
continue
|
||||
before = item.qty or 0.0
|
||||
q = m.qty or 0.0
|
||||
if m.type in ('compra', 'cancelacion'):
|
||||
q = vals.get('qty') or 0.0
|
||||
mtype = vals.get('type')
|
||||
if mtype in ('compra', 'cancelacion'):
|
||||
after = before + q
|
||||
elif m.type in ('venta', 'baja'):
|
||||
elif mtype in ('venta', 'baja'):
|
||||
after = before - q
|
||||
elif m.type == 'ajuste':
|
||||
elif mtype == 'ajuste':
|
||||
after = q # ajuste fija la existencia al valor del conteo
|
||||
else:
|
||||
after = before
|
||||
after = max(0.0, after)
|
||||
item.write({'qty': after, 'last_count_date': fields.Date.today() if m.type == 'ajuste' else item.last_count_date})
|
||||
m.write({'before_qty': before, 'after_qty': after})
|
||||
vals['before_qty'] = before
|
||||
vals['after_qty'] = max(0.0, after)
|
||||
|
||||
moves = super().create(vals_list)
|
||||
for m in moves:
|
||||
if not m.item_id:
|
||||
continue
|
||||
vals_item = {'qty': m.after_qty}
|
||||
if m.type == 'ajuste':
|
||||
vals_item['last_count_date'] = fields.Date.today()
|
||||
m.item_id.write(vals_item)
|
||||
return moves
|
||||
|
||||
|
||||
class SkeenInventarioConteo(models.Model):
|
||||
"""Conteo físico cíclico de inventario.
|
||||
|
||||
Guarda la foto del conteo (sistema vs contado por item) y los ajustes
|
||||
generados, para poder consultar el historial y reimprimir el resumen.
|
||||
"""
|
||||
_name = 'skeen.inventario.conteo'
|
||||
_description = 'Conteo físico de inventario SKEEN'
|
||||
_order = 'date desc, id desc'
|
||||
|
||||
name = fields.Char(string='Referencia', readonly=True, copy=False)
|
||||
date = fields.Datetime(string='Fecha', default=fields.Datetime.now)
|
||||
user_name = fields.Char(string='Realizado por')
|
||||
notes = fields.Text(string='Notas')
|
||||
line_ids = fields.One2many('skeen.inventario.conteo.line', 'conteo_id', string='Líneas')
|
||||
|
||||
total_items = fields.Integer(string='Artículos contados', compute='_compute_totals', store=True)
|
||||
total_diferencias = fields.Integer(string='Con diferencia', compute='_compute_totals', store=True)
|
||||
total_faltante = fields.Float(string='Faltante', compute='_compute_totals', store=True)
|
||||
total_sobrante = fields.Float(string='Sobrante', compute='_compute_totals', store=True)
|
||||
total_value_diff = fields.Float(string='Diferencia valorizada', compute='_compute_totals', store=True)
|
||||
|
||||
@api.depends('line_ids.difference', 'line_ids.value_diff')
|
||||
def _compute_totals(self):
|
||||
for c in self:
|
||||
c.total_items = len(c.line_ids)
|
||||
c.total_diferencias = len(c.line_ids.filtered(lambda l: abs(l.difference) > 1e-9))
|
||||
c.total_faltante = sum(l.value_diff for l in c.line_ids if l.difference < 0)
|
||||
c.total_sobrante = sum(l.value_diff for l in c.line_ids if l.difference > 0)
|
||||
c.total_value_diff = c.total_faltante + c.total_sobrante
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
for vals in vals_list:
|
||||
if not vals.get('name'):
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('skeen.inventario.conteo') or 'CONTEO-001'
|
||||
return super().create(vals_list)
|
||||
|
||||
def apply_count(self):
|
||||
"""Genera un movimiento type='ajuste' por cada línea con diferencia.
|
||||
|
||||
El movimiento de ajuste fija la existencia del artículo al valor
|
||||
contado (ver SkeenInventarioMove.create) y sella la fecha de conteo.
|
||||
Devuelve la cantidad de ajustes creados.
|
||||
"""
|
||||
Move = self.env['skeen.inventario.move']
|
||||
ajustes = 0
|
||||
for line in self.line_ids:
|
||||
if abs(line.difference) <= 1e-9:
|
||||
continue
|
||||
move = Move.create({
|
||||
'item_id': line.item_id.id,
|
||||
'type': 'ajuste',
|
||||
'qty': line.qty_contado,
|
||||
'reference': self.name,
|
||||
'notes': f'Conteo físico {self.name}',
|
||||
})
|
||||
line.write({'move_id': move.id})
|
||||
ajustes += 1
|
||||
return ajustes
|
||||
|
||||
|
||||
class SkeenInventarioConteoLine(models.Model):
|
||||
_name = 'skeen.inventario.conteo.line'
|
||||
_description = 'Línea de conteo físico'
|
||||
_order = 'id'
|
||||
|
||||
conteo_id = fields.Many2one('skeen.inventario.conteo', string='Conteo', required=True, ondelete='cascade')
|
||||
item_id = fields.Many2one('skeen.inventario.item', string='Artículo', required=True, ondelete='cascade')
|
||||
unit = fields.Char(related='item_id.unit', string='Unidad', readonly=True)
|
||||
qty_sistema = fields.Float(string='Sistema')
|
||||
qty_contado = fields.Float(string='Contado')
|
||||
difference = fields.Float(string='Diferencia', compute='_compute_difference', store=True)
|
||||
cost = fields.Float(string='Costo unitario')
|
||||
value_diff = fields.Float(string='Diferencia valorizada', compute='_compute_difference', store=True)
|
||||
move_id = fields.Many2one('skeen.inventario.move', string='Ajuste generado', readonly=True)
|
||||
|
||||
@api.depends('qty_sistema', 'qty_contado', 'cost')
|
||||
def _compute_difference(self):
|
||||
for line in self:
|
||||
line.difference = (line.qty_contado or 0.0) - (line.qty_sistema or 0.0)
|
||||
line.value_diff = line.difference * (line.cost or 0.0)
|
||||
|
||||
Reference in New Issue
Block a user