trytond-carrier_load_ul/load.py

249 lines
8.9 KiB
Python
Raw Normal View History

# The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from trytond.model import fields, ModelView, Workflow
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval
from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateTransition, StateView, Button
__metaclass__ = PoolMeta
__all__ = ['LoadOrder', 'LoadOrderLine', 'LoadUnitLoad',
'LoadUnitLoadOrder', 'LoadUnitLoadData']
class LoadOrder:
__name__ = 'carrier.load.order'
unit_loads = fields.Function(
fields.One2Many('stock.unit_load', None, 'ULs'),
'get_unit_loads')
@classmethod
def __setup__(cls):
super(LoadOrder, cls).__setup__()
cls._buttons.update({
'run_try': {'icon': 'tryton-go-next',
'invisible': Eval('state') != 'waiting'},
})
cls._error_messages.update({
'cancel_ul': 'Cannot cancel load orders with loaded ULs',
'ul_state': 'UL "%s" must be in Done state.',
'ul_loaded': 'UL "%s" is already loaded.',
'ul_location': 'UL "%s" must be in a storage location.',
'ul_warehouse': 'UL "%s" must be in warehouse "%s" to be loaded in order "%s".',
'ul_origin': 'UL "%s" does not belong to any origin of this load order.',
'ul_overload': 'All valid lines of load order "%s" are complete. Cannot load more ULs.',
'ul_data': 'Product data of UL "%s" does not match with any line of load order "%s".'})
def get_unit_loads(self, name=None):
if not self.lines:
return []
return [ul.id for l in self.lines for ul in l.unit_loads if l.unit_loads]
@classmethod
def cancel(cls, records):
if any(r.unit_loads for r in records):
cls.raise_user_error('cancel_ul')
super(LoadOrder, cls).cancel(records)
@classmethod
@ModelView.button_action('carrier_load_ul.wizard_load_ul')
def run_try(cls, records):
pass
def add_ul(self, unit_loads):
pool = Pool()
UL = pool.get('stock.unit_load')
order_lines = {}
for unit_load in unit_loads:
# check state
if unit_load.state != 'done':
self.raise_user_error('ul_state', unit_load.rec_name)
# check it is not loaded yet
if unit_load.load_line:
self.raise_user_error('ul_loaded', unit_load.rec_name)
location = unit_load.location
# check it is in storage location
if location.type != 'storage':
self.raise_user_error('ul_location', unit_load.rec_name)
# check it is in warehouse
wh = None
while not wh:
location = location.parent
if not location:
break
if location.type == 'warehouse':
wh = location
if not wh or wh.id != self.load.warehouse.id:
self.raise_user_error('ul_warehouse', (
unit_load.rec_name, self.load.warehouse.rec_name,
self.rec_name))
# check it is linked to origin lines
lines = [l for l in self.lines if unit_load in l.origin.unit_loads]
if not lines:
self.raise_user_error('ul_origin', unit_load.rec_name)
# check data matches
lines = self.check_ul_data_match(lines, unit_load)
if not lines:
self.raise_user_error('ul_data', (unit_load.rec_name, self.rec_name))
# check overload line qty
for _line in lines:
order_lines.setdefault(_line.id, _line.ul_quantity - len(_line.unit_loads))
if order_lines[_line.id] > 0:
line = _line
break
if not line:
self.raise_user_error('ul_overload', self.rec_name)
order_lines[line.id] -= 1
# load UL
unit_load.load_line = _line
UL.save(unit_loads)
def check_ul_data_match(self, lines, unit_load):
valid_lines = []
for line in lines:
product = getattr(line.origin, 'product', None)
if not product:
valid_lines.append(line)
continue
if product.id == unit_load.product.id:
valid_lines.append(line)
return valid_lines
class LoadOrderLine:
__name__ = 'carrier.load.order.line'
ul_quantity = fields.Float('ULs', digits=(16, 0))
quantity_per_ul = fields.Function(
fields.Float('Quantity per UL', digits=(16, Eval('unit_digits', 0)),
depends=['unit_digits']),
'on_change_with_quantity_per_ul')
unit_loads = fields.One2Many('stock.unit_load', 'load_line', 'ULs',
readonly=True)
@fields.depends('quantity', 'ul_quantity', 'uom')
def on_change_with_quantity_per_ul(self, name=None):
if self.quantity and self.ul_quantity:
return self.uom.round(self.quantity / self.ul_quantity, self.uom.rounding)
return None
@classmethod
def _get_quantity_field(cls):
return 'ul_quantity'
class LoadUnitLoadOrder(ModelView):
"""Carrier load unit load"""
__name__ = 'carrier.load_uls.order'
load_order = fields.Many2One('carrier.load.order', 'Order',
required=True,
domain=[('state', 'in', ['waiting', 'running'])])
# TODO: domain of uls_to_load: filter by in stock (storage location).
# It is verified later in add_ul, but would be fine to filter here
# TODO: configure ul_code reading by a string pattern (ex: P${code}) in carrier.configuration
class LoadUnitLoadData(ModelView):
"""Carrier load unit load"""
__name__ = 'carrier.load_uls.data'
load_order = fields.Many2One('carrier.load.order', 'Order',
readonly=True)
ul_code = fields.Char('UL')
uls_to_load = fields.One2Many('stock.unit_load', None, 'ULs to load',
domain=[('state', '=', 'done')])
uls_loaded = fields.One2Many('stock.unit_load', None, 'Loaded ULs')
loaded_uls = fields.Float('Loaded ULs', digits=(16, 0),
readonly=True)
class LoadUnitLoad(Wizard):
"""Carrier load unit load"""
__name__ = 'carrier.load_uls'
start = StateTransition()
order = StateView('carrier.load_uls.order',
'carrier_load_ul.load_uls_order_view_form',
[Button('Cancel', 'end', 'tryton-cancel'),
Button('OK', 'data', 'tryton-ok', default=True)])
data = StateView('carrier.load_uls.data',
'carrier_load_ul.load_uls_data_view_form',
[Button('Exit', 'end', 'tryton-cancel'),
Button('Do', 'do_', 'tryton-ok'),
Button('Add', 'add_', 'tryton-go-next', default=True)])
add_ = StateTransition()
do_ = StateTransition()
@classmethod
def __setup__(cls):
super(LoadUnitLoad, cls).__setup__()
cls._error_messages.update({
'invalid_ul': 'Cannot find Unit load "%s".',
'ul_required': 'Must define an UL to load.'})
def transition_start(self):
if Transaction().context.get('active_model') != LoadOrder.__name__:
return 'order'
return 'data'
def default_data(self, fields):
order = self._get_load_order()
res = {'load_order': order.id,
'uls_loaded': [],
'loaded_uls': 0}
if order.unit_loads:
res['uls_loaded'] = [ul.id for ul in order.unit_loads]
res['loaded_uls'] = len(res['uls_loaded'])
return res
def transition_add_(self):
pool = Pool()
UL = pool.get('stock.unit_load')
if not self.data.ul_code and not self.data.uls_to_load:
self.raise_user_error('ul_required')
order = self._get_load_order()
uls = []
if self.data.ul_code:
ul = UL.search([('code', '=', self.data.ul_code)])
if not ul:
self.raise_user_error('invalid_ul', self.data.ul_code)
uls.append(ul[0])
if self.data.uls_to_load:
uls.extend(self.data.uls_to_load)
if uls:
order.add_ul(uls)
if order.state != 'running':
order.run()
return 'data'
def transition_do_(self):
pool = Pool()
Order = pool.get('carrier.load.order')
# TODO: finish load order and create sale
Order.do(self._get_load_order())
# TODO: print reports
return 'end'
def _get_load_order(self):
pool = Pool()
Order = pool.get('carrier.load.order')
if Transaction().context.get('active_model') == LoadOrder.__name__:
return Order(Transaction().context.get('active_id'))
return Order(self.order.load_order.id)