Files
tryton/stock.py
T

210 lines
7.2 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 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'