93 lines
3.9 KiB
Python
93 lines
3.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""Campos de costo/margen que Compras agrega al artículo de inventario.
|
|
|
|
`costo_promedio` y `ultimo_costo` son NUEVOS: `cost` (Costo unitario) se queda
|
|
como estaba porque lo usan el valor de inventario y los reportes existentes, de
|
|
modo que una instancia con `compras.activo=false` conserva sus cifras idénticas.
|
|
El margen se calcula contra `costo_promedio` (con respaldo en `cost`) y el
|
|
precio de venta ya sin impuesto.
|
|
"""
|
|
|
|
from odoo import models, fields, api
|
|
|
|
|
|
class SkeenInventarioItem(models.Model):
|
|
_inherit = 'skeen.inventario.item'
|
|
|
|
costo_promedio = fields.Float(
|
|
string='Costo promedio', default=0.0,
|
|
help='Costo promedio ponderado de las compras recibidas. Editable por '
|
|
'administración como ajuste de inventario.')
|
|
ultimo_costo = fields.Float(
|
|
string='Último costo', default=0.0,
|
|
help='Costo unitario de la última recepción de compra.')
|
|
margen = fields.Float(
|
|
string='Margen', compute='_compute_margen',
|
|
help='Precio de venta sin impuesto menos costo promedio.')
|
|
margen_pct = fields.Float(
|
|
string='Margen (%)', compute='_compute_margen')
|
|
|
|
@api.depends('price', 'cost', 'costo_promedio', 'tax_rate',
|
|
'tax_use_default')
|
|
def _compute_margen(self):
|
|
from odoo.addons.skeen_citas.clinic_settings import get_impuestos
|
|
try:
|
|
imp = get_impuestos(self.env)
|
|
except Exception:
|
|
imp = {'activo': False, 'tasa_default': 0.0,
|
|
'precios_incluyen_impuesto': False}
|
|
for it in self:
|
|
precio = it.price or 0.0
|
|
if imp.get('activo'):
|
|
tasa = (imp.get('tasa_default') or 0.0) if it.tax_use_default \
|
|
else (it.tax_rate or 0.0)
|
|
if imp.get('precios_incluyen_impuesto') and tasa:
|
|
precio = precio / (1.0 + tasa / 100.0)
|
|
costo = it.costo_promedio or it.cost or 0.0
|
|
it.margen = precio - costo
|
|
it.margen_pct = (it.margen / precio * 100.0) if precio else 0.0
|
|
|
|
def _actualizar_costo_recepcion(self, cantidad, costo_unitario,
|
|
actualizar_promedio=True):
|
|
"""Aplica el costo de una recepción sobre el artículo.
|
|
|
|
Fórmula ponderada: (stock_actual*costo_actual + cantidad*costo_recibido)
|
|
/ (stock_actual + cantidad). Si el stock actual es <= 0 (cero o negativo)
|
|
el promedio se reinicia al costo recibido, porque mezclar un costo viejo
|
|
con existencias negativas daría un número sin sentido.
|
|
|
|
Debe llamarse ANTES de crear el movimiento de entrada, para que
|
|
`stock_actual` sea la existencia previa a la recepción.
|
|
"""
|
|
self.ensure_one()
|
|
stock_actual = self.qty or 0.0
|
|
cantidad = float(cantidad or 0.0)
|
|
costo_unitario = float(costo_unitario or 0.0)
|
|
vals = {'ultimo_costo': costo_unitario}
|
|
if actualizar_promedio and cantidad > 0:
|
|
if stock_actual > 0:
|
|
# Sin historial de compras el promedio arranca del costo unitario
|
|
# ya cargado (`cost`); si no, mezclar contra 0 daría un promedio
|
|
# artificialmente bajo.
|
|
costo_actual = self.costo_promedio or self.cost or 0.0
|
|
nuevo = ((stock_actual * costo_actual)
|
|
+ (cantidad * costo_unitario)) / (stock_actual + cantidad)
|
|
else:
|
|
nuevo = costo_unitario
|
|
vals['costo_promedio'] = nuevo
|
|
self.write(vals)
|
|
return self.costo_promedio
|
|
|
|
|
|
class SkeenInventarioMove(models.Model):
|
|
"""Lote y caducidad de la mercancía recibida.
|
|
|
|
Columnas nuevas y opcionales: el kardex actual no cambia. Las recepciones de
|
|
compra las llenan; los demás movimientos las dejan vacías.
|
|
"""
|
|
_inherit = 'skeen.inventario.move'
|
|
|
|
lote = fields.Char(string='Lote')
|
|
fecha_caducidad = fields.Date(string='Caducidad')
|
|
|