# 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 Workflow, ModelView, fields from trytond.pool import Pool, PoolMeta from trytond.pyson import Eval, Bool, Id from trytond.wizard import Wizard, StateTransition __all__ = ['PackageType', 'Package', 'ShipmentOut', 'CreateShipping'] class PackageType: __metaclass__ = PoolMeta __name__ = 'stock.package.type' length = fields.Float('Length', digits=(16, Eval('length_digits', 2)), depends=['length_digits']) length_uom = fields.Many2One('product.uom', 'Length Unit', domain=[ ('category', '=', Id('product', 'uom_cat_length')), ], states={ 'required': Bool(Eval('length')), }, depends=['length', 'length_digits']) length_digits = fields.Function(fields.Integer('Length Digits'), 'on_change_with_length_digits') height = fields.Float('Height', digits=(16, Eval('height_digits', 2)), depends=['height_digits']) height_uom = fields.Many2One('product.uom', 'Height Unit', domain=[ ('category', '=', Id('product', 'uom_cat_length')), ], states={ 'required': Bool(Eval('height')), }, depends=['height', 'height_digits']) height_digits = fields.Function(fields.Integer('Height Digits'), 'on_change_with_height_digits') width = fields.Float('Width', digits=(16, Eval('width_digits', 2)), depends=['width_digits']) width_uom = fields.Many2One('product.uom', 'Width Unit', domain=[ ('category', '=', Id('product', 'uom_cat_length')), ], states={ 'required': Bool(Eval('width')), }, depends=['width', 'width_digits']) width_digits = fields.Function(fields.Integer('Width Digits'), 'on_change_with_width_digits') @fields.depends('length_uom') def on_change_with_length_digits(self, name=None): return (self.length_uom.digits if self.length_uom else self.default_length_digits()) @fields.depends('height_uom') def on_change_with_height_digits(self, name=None): return (self.height_uom.digits if self.height_uom else self.default_height_digits()) @fields.depends('width_uom') def on_change_with_width_digits(self, name=None): return (self.width_uom.digits if self.width_uom else self.default_width_digits()) @classmethod def default_length_digits(cls): return 2 @classmethod def default_height_digits(cls): return 2 @classmethod def default_width_digits(cls): return 2 class Package: __metaclass__ = PoolMeta __name__ = 'stock.package' shipping_reference = fields.Char('Shipping Reference', states={ 'readonly': Eval('_parent_shipment', {}).get('carrier', False), }) shipping_label = fields.Binary('Shipping Label', readonly=True) weight = fields.Function( fields.Float('Weight', digits=None, help="The total weight of the package's moves in kg."), 'get_weight') @classmethod def search_rec_name(cls, name, clause): domain = super(Package, cls).search_rec_name(name, clause) return ['OR', domain, ('shipping_reference',) + tuple(clause[:1]), ] def get_weight(self, name): pool = Pool() UoM = pool.get('product.uom') UoMCategory = pool.get('product.uom.category') ModelData = pool.get('ir.model.data') weight_category = UoMCategory( ModelData.get_id('product', 'uom_cat_weight')) kg = UoM(ModelData.get_id('product', 'uom_kilogram')) weight = 0 for move in self.moves: # Use first the weight from product_measurements as it could # include some handling weight if move.product.weight is not None: weight += UoM.compute_qty( move.product.weight_uom, move.internal_quantity * move.product.weight, kg, round=False) elif move.product.default_uom.category == weight_category: weight += UoM.compute_qty( move.product.default_uom, move.internal_quantity, kg, round=False) else: weight = None break return weight class ShipmentOut: __metaclass__ = PoolMeta __name__ = 'stock.shipment.out' shipping_description = fields.Char('Shipping Description', states={ 'readonly': Eval('state').in_(['done', 'packed']) }, depends=['state']) @classmethod def __setup__(cls): super(ShipmentOut, cls).__setup__() # The shipment reference will be set by the shipping service cls.reference.readonly = True cls._buttons.update({ 'create_shipping': { 'invisible': (Eval('reference', False) | ~Eval('carrier', False)), 'readonly': (Eval('reference', False) | ~Eval('root_packages', False) | ~Eval('carrier', False) | (Eval('state') != 'packed')), }, }) cls._error_messages.update({ 'shipment_not_packed': ('The shipment "%(shipment)s" must be' ' packed before creating the shipping labels'), 'shipment_without_carrier': ('The shipment "%(shipment)s"' ' does not have a carrier set'), }) @classmethod def search_rec_name(cls, name, clause): domain = super(ShipmentOut, cls).search_rec_name(name, clause) return ['OR', domain, ('reference',) + tuple(clause[:1]), ] @classmethod @Workflow.transition('packed') def pack(cls, shipments): super(ShipmentOut, cls).pack(shipments) for shipment in shipments: if shipment.carrier and shipment.carrier.shipping_service: method_name = ('validate_packing_%s' % shipment.carrier.shipping_service) validator = getattr(shipment, method_name) validator() elif not shipment.carrier: cls.raise_user_warning( 'shipment_out_no_carrier_%s' % shipment.id, 'shipment_without_carrier', { 'shipment': shipment.rec_name, }) @classmethod @ModelView.button_action( 'stock_package_shipping.act_create_shipping_wizard') def create_shipping(cls, shipments): for shipment in shipments: if shipment.state != 'packed': cls.raise_user_error('shipment_not_packed', { 'shipment': shipment.rec_name, }) # TODO Implement ShipmentInReturn class CreateShipping(Wizard): 'Create Shipping' __name__ = 'stock.shipment.create_shipping' start = StateTransition() def transition_start(self): return 'end'