# 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 trytond.model import ModelSQL, ModelView, Unique, fields from trytond.pool import Pool, PoolMeta from trytond.pyson import Eval, If class ProductLocationPlace(ModelSQL, ModelView): __name__ = 'stock.product.location.place' _rec_name = 'place' template = fields.Many2One( 'product.template', "Product", required=True, ondelete='CASCADE', domain=[ If(Eval('product'), ('products', '=', Eval('product', -1)), ()), ]) product = fields.Many2One( 'product.product', "Variant", ondelete='CASCADE', domain=[ If(Eval('template'), ('template', '=', Eval('template', -1)), ()), ]) location = fields.Many2One( 'stock.location', "Storage Location", required=True, ondelete='CASCADE', domain=[ ('type', '=', 'storage'), ]) place = fields.Char( "Place", required=True, help="The place where the product is always stored in the location.") @classmethod def __setup__(cls): super().__setup__() t = cls.__table__() cls._sql_constraints += [ ('template_product_location_unique', Unique(t, t.template, t.product, t.location), 'stock_product_location_place.' 'msg_stock_product_location_unique'), ] @fields.depends('product', '_parent_product.template') def on_change_product(self): if self.product: self.template = self.product.template @classmethod def default_location(cls): pool = Pool() Location = pool.get('stock.location') warehouse = Location.get_default_warehouse() if warehouse: warehouse = Location(warehouse) if (warehouse.storage_location and warehouse.storage_location.type == 'storage'): return warehouse.storage_location.id elif (warehouse.picking_location and warehouse.picking_location.type == 'storage'): return warehouse.picking_location.id class Move(metaclass=PoolMeta): __name__ = 'stock.move' from_place = fields.Many2One( 'stock.product.location.place', "From Place", readonly=True, domain=[ If(~Eval('state').in_(['done', 'cancelled']), ['OR', ('template.products', '=', Eval('product', -1)), ('product', '=', Eval('product', -1)), ], ('location', '=', Eval('from_location', -1)), ), ]) to_place = fields.Many2One( 'stock.product.location.place', "To Place", readonly=True, domain=[ If(~Eval('state').in_(['done', 'cancelled']), ['OR', ('template.products', '=', Eval('product', -1)), ('product', '=', Eval('product', -1)), ], ('location', '=', Eval('to_location', -1)), ), ]) @classmethod def __setup__(cls): super().__setup__() cls._allow_modify_closed_period |= {'from_place', 'to_place'} @fields.depends('from_location', 'product') def on_change_with_from_place(self): if self.product and self.from_location: return self.product.get_place(self.from_location) @fields.depends('to_location', 'product') def on_change_with_to_place(self): if self.product and self.to_location: return self.product.get_place(self.to_location) @fields.depends('from_place') def on_change_with_from_location_name(self, name=None): name = super().on_change_with_from_location_name(name=name) if self.from_place: name = ' @ '.join( filter(None, [name, self.from_place.rec_name])).strip() return name @fields.depends('to_place') def on_change_with_to_location_name(self, name=None): name = super().on_change_with_to_location_name(name=name) if self.to_place: name = ' @ '.join( filter(None, [name, self.to_place.rec_name])).strip() return name def compute_fields(self, field_names=None): cls = self.__class__ values = super().compute_fields(field_names=field_names) if getattr(self, 'state', None) not in {'done', 'cancelled'}: if (field_names is None or cls.from_place.on_change_with & field_names): from_place = self.on_change_with_from_place() if getattr(self, 'from_place', None) != from_place: values['from_place'] = from_place if (field_names is None or cls.to_place.on_change_with & field_names): to_place = self.on_change_with_to_place() if getattr(self, 'to_place', None) != to_place: values['to_place'] = to_place return values @classmethod def write(cls, *args): # clean places as they maybe no more valid actions = iter(args) args = [] for moves, values in zip(actions, actions): if {'product', 'from_location', 'to_location'} & values.keys(): values = values.copy() values.setdefault('from_place') values.setdefault('to_place') args.extend((moves, values)) super().write(*args) class ShipmentIn(metaclass=PoolMeta): __name__ = 'stock.shipment.in' @classmethod def __setup__(cls): super().__setup__() i = cls.inventory_moves.order.index(('to_location', 'ASC')) cls.inventory_moves.order.insert(i + 1, ('to_place', 'ASC')) class ShipmentInReturn(metaclass=PoolMeta): __name__ = 'stock.shipment.in.return' @classmethod def __setup__(cls): super().__setup__() i = cls.moves.order.index(('from_location', 'ASC')) cls.moves.order.insert(i + 1, ('from_place', 'ASC')) class ShipmentOut(metaclass=PoolMeta): __name__ = 'stock.shipment.out' @classmethod def __setup__(cls): super().__setup__() i = cls.inventory_moves.order.index(('from_location', 'ASC')) cls.inventory_moves.order.insert(i + 1, ('from_place', 'ASC')) class ShipmentOutReturn(metaclass=PoolMeta): __name__ = 'stock.shipment.out.return' @classmethod def __setup__(cls): super().__setup__() i = cls.inventory_moves.order.index(('to_location', 'ASC')) cls.inventory_moves.order.insert(i + 1, ('to_place', 'ASC')) class ShipmentInternal(metaclass=PoolMeta): __name__ = 'stock.shipment.internal' @classmethod def __setup__(cls): super().__setup__() i = cls.moves.order.index(('from_location', 'ASC')) cls.moves.order.insert(i + 1, ('from_place', 'ASC')) i = cls.outgoing_moves.order.index(('from_location', 'ASC')) cls.outgoing_moves.order.insert(i + 1, ('from_place', 'ASC')) i = cls.incoming_moves.order.index(('to_location', 'ASC')) cls.incoming_moves.order.insert(i + 1, ('to_place', 'ASC')) class InventoryLine(metaclass=PoolMeta): __name__ = 'stock.inventory.line' place = fields.Many2One( 'stock.product.location.place', "Place", domain=['OR', ('template.products', '=', Eval('product', -1)), ('product', '=', Eval('product', -1)), ]) @fields.depends( 'inventory_location', 'product', methods=['on_change_with_inventory_location']) def on_change_with_place(self): location = (self.inventory_location or self.on_change_with_inventory_location()) if self.product and location: return self.product.get_place(location) @fields.depends(methods=['on_change_with_place']) def update_for_complete(self, quantity): super().update_for_complete(quantity) self.place = self.on_change_with_place()