Files
tradon/modules/stock_lot_unit/stock.py
2026-03-14 09:42:12 +00:00

213 lines
7.1 KiB
Python

# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from collections import defaultdict
from trytond.i18n import gettext
from trytond.model import ModelView, Workflow, fields
from trytond.pool import Pool, PoolMeta
from trytond.pyson import Bool, Eval
from .exceptions import LotUnitQuantityError
class LotUnitMixin:
__slots__ = ()
unit = fields.Many2One(
'product.uom', "Unit",
domain=[
('category', '=', Eval('product_default_uom_category', -1)),
],
help="The biggest unit for the lot.")
unit_quantity = fields.Float(
"Unit Quantity", digits='unit',
states={
'required': Bool(Eval('unit')),
'invisible': ~Eval('unit'),
},
help="The maximal quantity for the lot.")
product_default_uom_category = fields.Function(
fields.Many2One(
'product.uom.category', "Default Product UoM Category"),
'on_change_with_product_default_uom_category')
@fields.depends('product', methods=['on_change_unit'])
def on_change_product(self):
try:
super().on_change_product()
except AttributeError:
pass
if self.product and self.product.lot_uom:
self.unit = self.product.lot_uom
self.on_change_unit()
@fields.depends('unit')
def on_change_unit(self):
if self.unit:
self.unit_quantity = self.unit.rounding
@fields.depends('product')
def on_change_with_product_default_uom_category(self, name=None):
if self.product:
return self.product.default_uom_category
class Lot(LotUnitMixin, metaclass=PoolMeta):
__name__ = 'stock.lot'
@classmethod
def __setup__(cls):
super().__setup__()
cls._modify_no_move += [
('unit', 'done', 'stock_lot_unit.msg_change_unit'),
('unit_quantity', 'done',
'stock_lot_unit.msg_change_unit_quantity'),
]
class MoveAddLotsStartLot(LotUnitMixin, metaclass=PoolMeta):
__name__ = 'stock.move.add.lots.start.lot'
@fields.depends(
'unit', 'unit_quantity', 'quantity', 'parent', '_parent_parent.unit')
def _set_lot_values(self, lot):
pool = Pool()
Uom = pool.get('product.uom')
super()._set_lot_values(lot)
self.unit = lot.unit
self.unit_quantity = lot.unit_quantity
if self.parent.unit:
maximum_quantity = Uom.compute_qty(
lot.unit, lot.unit_quantity, self.parent.unit, round=False)
if self.quantity > maximum_quantity:
self.quantity = self.parent.unit.round(maximum_quantity)
def _get_lot_values(self, move):
values = super()._get_lot_values(move)
values['unit'] = self.unit
values['unit_quantity'] = self.unit_quantity
return values
class Move(metaclass=PoolMeta):
__name__ = 'stock.move'
def check_lot(self):
pool = Pool()
UoM = pool.get('product.uom')
super().check_lot()
if (self.state == 'done'
and self.lot
and self.lot.unit):
quantity = UoM.compute_qty(
self.unit, self.quantity,
self.lot.unit, round=False)
if quantity > self.lot.unit_quantity:
raise LotUnitQuantityError(
gettext('stock_lot_unit.msg_lot_unit_quantity_greater',
quantity=self.lot.unit_quantity,
unit=self.lot.unit.symbol,
lot=self.lot.rec_name,
name=self.rec_name))
class Inventory(metaclass=PoolMeta):
__name__ = 'stock.inventory'
@classmethod
@ModelView.button
@Workflow.transition('done')
def confirm(cls, inventories):
for inventory in inventories:
for line in inventory.lines:
if (line.quantity is not None
and line.lot
and line.lot.unit
and line.quantity > line.lot.unit_quantity):
raise LotUnitQuantityError(
gettext('stock_lot_unit.msg_lot_unit_quantity_greater',
quantity=line.lot.unit_quantity,
unit=line.lot.unit.symbol,
lot=line.lot.rec_name,
name=line.rec_name))
super().confirm(inventories)
class InventoryCount(metaclass=PoolMeta):
__name__ = 'stock.inventory.count'
def default_quantity(self, fields):
pool = Pool()
InventoryLine = pool.get('stock.inventory.line')
UoM = pool.get('product.uom')
values = super().default_quantity(fields)
line = InventoryLine(values['line'])
if line.lot and line.lot.unit:
values['quantity'] = UoM.compute_qty(
line.lot.unit, line.lot.unit_quantity,
line.unit)
return values
class LotUnitMixin(object):
__slots__ = ()
_lot_unit_moves = None
@classmethod
def validate(cls, shipments):
pool = Pool()
UoM = pool.get('product.uom')
super().validate(shipments)
for shipment in shipments:
for move_attribute in cls._lot_unit_moves:
lot_quantities = defaultdict(int)
for move in getattr(shipment, move_attribute):
if move.state != 'done':
continue
if not move.lot or not move.lot.unit:
continue
lot_quantities[move.lot] += UoM.compute_qty(
move.unit, move.quantity,
move.lot.unit, round=False)
for lot, quantity in lot_quantities.items():
if quantity > lot.unit_quantity:
raise LotUnitQuantityError(
gettext('stock_lot_unit'
'.msg_lot_unit_quantity_greater',
quantity=lot.unit_quantity,
unit=lot.unit.symbol,
lot=lot.rec_name,
name=shipment.rec_name))
class ShipmentIn(LotUnitMixin, metaclass=PoolMeta):
__name__ = 'stock.shipment.in'
_lot_unit_moves = ['incoming_moves', 'inventory_moves']
class ShipmentInReturn(LotUnitMixin, metaclass=PoolMeta):
__name__ = 'stock.shipment.in.return'
_lot_unit_moves = ['moves']
class ShipmentOut(LotUnitMixin, metaclass=PoolMeta):
__name__ = 'stock.shipment.out'
_lot_unit_moves = ['outgoing_moves', 'inventory_moves']
class ShipmentOutReturn(LotUnitMixin, metaclass=PoolMeta):
__name__ = 'stock.shipment.out.return'
_lot_unit_moves = ['incoming_moves', 'inventory_moves']
class ShipmentInternal(LotUnitMixin, metaclass=PoolMeta):
__name__ = 'stock.shipment.internal'
_lot_unit_moves = ['incoming_moves', 'outgoing_moves']
class Production(LotUnitMixin, metaclass=PoolMeta):
__name__ = 'production'
_lot_unit_moves = ['inputs', 'outputs']