213 lines
7.1 KiB
Python
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']
|