2015-10-09 14:06:06 +02:00
|
|
|
# The COPYRIGHT file at the top level of
|
|
|
|
# this repository contains the full copyright notices and license terms.
|
2016-01-28 23:37:01 +01:00
|
|
|
from functools import partial
|
|
|
|
from itertools import groupby
|
|
|
|
|
2016-02-08 17:21:20 +01:00
|
|
|
from decimal import Decimal
|
2016-01-28 23:37:01 +01:00
|
|
|
from trytond.model import fields, ModelView, Workflow, Model
|
2015-10-09 14:06:06 +02:00
|
|
|
from trytond.pool import PoolMeta, Pool
|
2016-01-28 23:37:01 +01:00
|
|
|
from trytond.pyson import Eval, Bool, Not
|
2015-10-09 14:06:06 +02:00
|
|
|
from trytond.transaction import Transaction
|
|
|
|
from trytond.wizard import Wizard, StateTransition, StateView, Button
|
|
|
|
|
2015-11-03 17:15:48 +01:00
|
|
|
__all__ = ['Configuration', 'Load', 'LoadOrder', 'LoadOrderLine',
|
2015-11-30 13:42:21 +01:00
|
|
|
'LoadUnitLoad', 'LoadUnitLoadOrder', 'LoadUnitLoadData',
|
2016-04-05 12:10:42 +02:00
|
|
|
'LoadSheet', 'CMR', 'RoadTransportNote', 'EditLoadOrderLine',
|
|
|
|
'EditLoadOrderLineData']
|
2015-10-13 16:47:59 +02:00
|
|
|
|
|
|
|
|
|
|
|
class Configuration:
|
|
|
|
__name__ = 'carrier.configuration'
|
2016-08-23 09:10:49 +02:00
|
|
|
__metaclass__ = PoolMeta
|
2015-10-13 16:47:59 +02:00
|
|
|
|
|
|
|
ul_origin_restrict = fields.Boolean('Restrict UL origin',
|
|
|
|
help='Restricts origin of UL when loading in a Load order.')
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def default_ul_origin_restrict(cls):
|
|
|
|
return True
|
2015-10-09 14:06:06 +02:00
|
|
|
|
|
|
|
|
2015-11-03 17:15:48 +01:00
|
|
|
class Load:
|
|
|
|
__name__ = 'carrier.load'
|
2016-08-23 09:10:49 +02:00
|
|
|
__metaclass__ = PoolMeta
|
2015-11-03 17:15:48 +01:00
|
|
|
|
|
|
|
unit_loads = fields.Function(
|
|
|
|
fields.One2Many('stock.unit_load', None, 'Unit loads'),
|
|
|
|
'get_unit_loads', searcher='search_unit_loads')
|
|
|
|
|
|
|
|
def get_unit_loads(self, name=None):
|
|
|
|
if not self.orders:
|
|
|
|
return []
|
|
|
|
return [ul.id for l in self.orders for ul in l.unit_loads if l.unit_loads]
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def search_unit_loads(cls, name, clause):
|
|
|
|
return [('orders.unit_loads', ) + tuple(clause[1:])]
|
|
|
|
|
|
|
|
|
2015-10-09 14:06:06 +02:00
|
|
|
class LoadOrder:
|
|
|
|
__name__ = 'carrier.load.order'
|
2016-08-23 09:10:49 +02:00
|
|
|
__metaclass__ = PoolMeta
|
2015-10-09 14:06:06 +02:00
|
|
|
|
|
|
|
unit_loads = fields.Function(
|
2015-10-26 20:39:11 +01:00
|
|
|
fields.One2Many('stock.unit_load', None, 'Unit loads',
|
|
|
|
states={'readonly': Eval('state').in_(['cancel', 'done'])},
|
|
|
|
depends=['state']),
|
2015-10-23 18:55:28 +02:00
|
|
|
'get_unit_loads', setter='set_unit_loads', searcher='search_unit_loads')
|
2015-10-13 16:47:59 +02:00
|
|
|
ul_origin_restrict = fields.Boolean('Restrict UL origin',
|
2016-01-20 13:09:48 +01:00
|
|
|
states={'readonly': Eval('state').in_(['cancel', 'done'])},
|
2015-10-13 16:47:59 +02:00
|
|
|
depends=['state'])
|
2016-01-28 23:37:01 +01:00
|
|
|
ul_quantity = fields.Function(fields.Float('ULs', digits=(16, 0)),
|
|
|
|
'get_ul_quantity')
|
2015-10-09 14:06:06 +02:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
|
|
|
super(LoadOrder, cls).__setup__()
|
|
|
|
cls._buttons.update({
|
|
|
|
'run_try': {'icon': 'tryton-go-next',
|
2015-10-20 19:27:57 +02:00
|
|
|
'invisible': ~Eval('state').in_(['waiting', 'running'])},
|
2015-10-09 14:06:06 +02:00
|
|
|
})
|
|
|
|
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.',
|
2016-01-24 21:05:16 +01:00
|
|
|
'ul_data': 'Product data of UL "%s" does not match with any line of load order "%s".',
|
2016-01-28 23:37:01 +01:00
|
|
|
'draft_ul': 'Cannot change state to Draft for load order "%s" because it has loaded ULs.',
|
|
|
|
'pending_uls': 'You have loaded less ULs (%s) than expected (%s).',
|
2016-04-05 12:10:42 +02:00
|
|
|
'sale_confirmed': 'Cannot force loading ULs because sale "%s" is confirmed.',
|
2016-04-13 08:55:20 +02:00
|
|
|
'unload_cancel': 'Cannot force unloading all ULs. Load order "%s" must be cancelled completely.',
|
|
|
|
'no_uls': 'Load order "%s" must have some UL to be finished.'
|
2016-01-28 23:37:01 +01:00
|
|
|
})
|
2015-10-09 14:06:06 +02:00
|
|
|
|
2015-10-13 16:47:59 +02:00
|
|
|
@classmethod
|
|
|
|
def default_ul_origin_restrict(cls):
|
|
|
|
pool = Pool()
|
|
|
|
Configuration = pool.get('carrier.configuration')
|
|
|
|
|
|
|
|
conf = Configuration(1)
|
|
|
|
return conf.ul_origin_restrict
|
|
|
|
|
2015-10-09 14:06:06 +02:00
|
|
|
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]
|
|
|
|
|
2015-10-23 18:55:28 +02:00
|
|
|
@classmethod
|
|
|
|
def set_unit_loads(cls, records, name, value):
|
|
|
|
pass
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def search_unit_loads(cls, name, clause):
|
|
|
|
return [('lines.unit_loads', ) + tuple(clause[1:])]
|
|
|
|
|
2015-11-03 17:15:48 +01:00
|
|
|
def get_carrier_amount(self, name=None):
|
|
|
|
if not self.load.unit_price:
|
|
|
|
return 0
|
|
|
|
return self.load.currency.round(
|
2016-02-08 17:21:20 +01:00
|
|
|
(Decimal(len(self.unit_loads)) / Decimal(len(self.load.unit_loads))) * self.load.unit_price)
|
2015-11-03 17:15:48 +01:00
|
|
|
|
2015-10-09 14:06:06 +02:00
|
|
|
@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)
|
|
|
|
|
2015-10-20 19:27:57 +02:00
|
|
|
def _get_load_sale(self, Sale):
|
|
|
|
res = super(LoadOrder, self)._get_load_sale(Sale)
|
|
|
|
res.shipment_method = 'manual'
|
|
|
|
return res
|
|
|
|
|
2015-10-23 18:55:28 +02:00
|
|
|
def _get_load_sale_line(self, key, grouped_items):
|
|
|
|
res = super(LoadOrder, self)._get_load_sale_line(key, grouped_items)
|
|
|
|
res.ul_quantity = len(grouped_items)
|
|
|
|
return res
|
|
|
|
|
2015-11-23 12:44:52 +01:00
|
|
|
def _get_shipment_sale(self, sale):
|
|
|
|
res = super(LoadOrder, self)._get_shipment_sale(sale)
|
|
|
|
res.start_date = self.start_date
|
|
|
|
res.on_change_start_date()
|
|
|
|
res.end_date = self.end_date
|
|
|
|
return res
|
|
|
|
|
2015-10-20 19:27:57 +02:00
|
|
|
def _get_shipment_moves(self, sale_line, grouped_items):
|
2015-10-26 20:39:11 +01:00
|
|
|
pool = Pool()
|
|
|
|
Move = pool.get('stock.move')
|
|
|
|
|
2015-10-20 19:27:57 +02:00
|
|
|
moves = []
|
2015-10-26 20:39:11 +01:00
|
|
|
other_moves = []
|
2015-10-20 19:27:57 +02:00
|
|
|
for item in grouped_items:
|
|
|
|
new_moves = item._get_new_moves({'from_location': sale_line.from_location.id,
|
|
|
|
'to_location': sale_line.to_location.id,
|
2016-01-28 23:37:01 +01:00
|
|
|
'start_date': self.end_date,
|
|
|
|
'end_date': self.end_date,
|
|
|
|
'state': 'draft'})
|
2015-10-20 19:27:57 +02:00
|
|
|
move, = [m for m in new_moves if m.product == item.product]
|
2015-10-23 18:55:28 +02:00
|
|
|
move.origin = sale_line
|
2015-10-26 20:39:11 +01:00
|
|
|
moves.append(move)
|
|
|
|
|
|
|
|
for new_move in new_moves:
|
|
|
|
if new_move.product.id == item.product.id:
|
|
|
|
continue
|
|
|
|
new_move.origin = item.load_line
|
|
|
|
other_moves.append(new_move)
|
|
|
|
|
|
|
|
if other_moves:
|
|
|
|
Move.save(other_moves)
|
2015-10-20 19:27:57 +02:00
|
|
|
return moves
|
|
|
|
|
2016-04-05 12:10:42 +02:00
|
|
|
def _update_sale(self, uls):
|
2016-01-28 23:37:01 +01:00
|
|
|
pool = Pool()
|
|
|
|
Move = pool.get('stock.move')
|
2016-04-05 12:10:42 +02:00
|
|
|
Saleline = pool.get('sale.line')
|
|
|
|
|
|
|
|
assert (all(ul in self.unit_loads for ul in uls) or
|
|
|
|
all(ul not in self.unit_loads for ul in uls))
|
|
|
|
_add = uls[0] not in self.unit_loads
|
2016-01-28 23:37:01 +01:00
|
|
|
|
|
|
|
if not self.sale:
|
|
|
|
return
|
|
|
|
if self.sale.state not in ('draft', 'quotation'):
|
|
|
|
self.raise_user_error('sale_confirmed', self.sale.rec_name)
|
2016-04-05 12:10:42 +02:00
|
|
|
if not _add and len(uls) == len(self.unit_loads):
|
|
|
|
self.raise_user_error('unload_cancel', self.rec_name)
|
2016-01-28 23:37:01 +01:00
|
|
|
|
2016-04-05 12:10:42 +02:00
|
|
|
keyfunc = partial(self._group_sale_line_key, uls)
|
|
|
|
items = sorted(uls, key=keyfunc)
|
2016-01-28 23:37:01 +01:00
|
|
|
for key, grouped_items in groupby(items, key=keyfunc):
|
|
|
|
_groupitems = list(grouped_items)
|
|
|
|
key_dict = dict(key)
|
|
|
|
_fields = key_dict.keys()
|
|
|
|
|
|
|
|
def get_line_values(line):
|
|
|
|
line_values = []
|
|
|
|
for _field in _fields:
|
|
|
|
value = getattr(line, _field, None)
|
|
|
|
if isinstance(value, Model):
|
|
|
|
value = int(value)
|
|
|
|
line_values.append(value)
|
|
|
|
return line_values
|
|
|
|
|
|
|
|
sale_line = [l for l in self.sale.lines
|
|
|
|
if get_line_values(l) == key_dict.values()]
|
|
|
|
|
|
|
|
if not sale_line:
|
2016-04-05 12:10:42 +02:00
|
|
|
if not _add:
|
|
|
|
continue
|
2016-01-28 23:37:01 +01:00
|
|
|
sale_line = self._get_load_sale_line(key, _groupitems)
|
|
|
|
sale_line.sale = self.sale
|
|
|
|
else:
|
|
|
|
sale_line, = sale_line
|
2016-04-05 12:10:42 +02:00
|
|
|
self._update_sale_line(sale_line, _groupitems, _add)
|
2016-01-28 23:37:01 +01:00
|
|
|
sale_line.save()
|
|
|
|
|
|
|
|
shipment = sale_line.sale.shipments[0]
|
|
|
|
|
2016-04-05 12:10:42 +02:00
|
|
|
if _add:
|
|
|
|
outgoing_moves = self._get_shipment_moves(sale_line, _groupitems)
|
|
|
|
inventory_moves = []
|
|
|
|
for move in outgoing_moves:
|
|
|
|
move.shipment = shipment
|
|
|
|
_inventory = shipment._get_inventory_move(move)
|
|
|
|
_inventory.start_date = self.start_date
|
|
|
|
inventory_moves.append(_inventory)
|
|
|
|
Move.save(outgoing_moves + inventory_moves)
|
|
|
|
Move.assign(outgoing_moves)
|
|
|
|
Move.do(inventory_moves)
|
|
|
|
else:
|
|
|
|
shipment_moves = [m for m in shipment.moves if m.unit_load in _groupitems]
|
|
|
|
load_moves = [m for m in self.inventory_moves if m.unit_load in _groupitems]
|
|
|
|
load_moves += [m for m in self.outgoing_moves if m.unit_load in _groupitems]
|
|
|
|
with Transaction().set_context(check_origin=False):
|
|
|
|
Move.cancel(shipment_moves + load_moves)
|
|
|
|
Move.delete(shipment_moves + load_moves)
|
|
|
|
if not sale_line.quantity:
|
|
|
|
Saleline.delete([sale_line])
|
2016-01-28 23:37:01 +01:00
|
|
|
|
|
|
|
to_do = []
|
|
|
|
for move in self.outgoing_moves:
|
|
|
|
if move.state == 'draft':
|
|
|
|
_new_move = self._get_inventory_move(move)
|
|
|
|
_new_move.save()
|
|
|
|
to_do.extend([move, _new_move])
|
|
|
|
if to_do:
|
|
|
|
Move.do(to_do)
|
|
|
|
|
2016-04-05 12:10:42 +02:00
|
|
|
def _update_sale_line(self, sale_line, items, _add=True):
|
|
|
|
_sign = 1 if _add else -1
|
|
|
|
sale_line.ul_quantity += _sign * len(items)
|
|
|
|
sale_line.quantity += _sign * sum(sale_line.unit.compute_qty(
|
2016-01-28 23:37:01 +01:00
|
|
|
item.uom, item.quantity, sale_line.unit) for item in items)
|
|
|
|
|
2015-10-20 19:27:57 +02:00
|
|
|
def _get_items(self):
|
|
|
|
return self.unit_loads
|
|
|
|
|
2016-01-28 23:37:01 +01:00
|
|
|
def get_ul_quantity(self, name=None):
|
|
|
|
if not self.lines:
|
|
|
|
return 0
|
|
|
|
return sum(l.ul_quantity or 0 for l in self.lines)
|
|
|
|
|
2015-10-26 20:39:11 +01:00
|
|
|
@classmethod
|
|
|
|
def do(cls, records):
|
|
|
|
pool = Pool()
|
|
|
|
Move = pool.get('stock.move')
|
|
|
|
|
|
|
|
super(LoadOrder, cls).do(records)
|
2016-01-28 23:37:01 +01:00
|
|
|
cls._check_loaded_quantity(records)
|
2015-10-26 20:39:11 +01:00
|
|
|
moves = []
|
|
|
|
for record in records:
|
|
|
|
moves.extend(
|
|
|
|
[record._get_inventory_move(m) for m in record.outgoing_moves])
|
|
|
|
if moves:
|
|
|
|
Move.save(moves)
|
|
|
|
Move.do([m for r in records for l in r.lines for m in l.moves])
|
|
|
|
|
2016-01-28 23:37:01 +01:00
|
|
|
@classmethod
|
|
|
|
def _check_loaded_quantity(cls, records):
|
|
|
|
for record in records:
|
2016-04-13 08:55:20 +02:00
|
|
|
if not record.unit_loads:
|
|
|
|
cls.raise_user_error('no_uls', record.rec_name)
|
2016-01-28 23:37:01 +01:00
|
|
|
if record.ul_quantity > len(record.unit_loads):
|
|
|
|
cls.raise_user_warning('pending_uls_%s' % record.id, 'pending_uls',
|
|
|
|
(len(record.unit_loads), int(record.ul_quantity)))
|
|
|
|
|
2016-01-24 21:05:16 +01:00
|
|
|
@classmethod
|
|
|
|
def draft(cls, records):
|
|
|
|
for record in records:
|
|
|
|
if record.state != 'waiting':
|
|
|
|
continue
|
|
|
|
if record.unit_loads:
|
|
|
|
cls.raise_user_error('draft_ul', record.rec_name)
|
|
|
|
super(LoadOrder, cls).draft(records)
|
|
|
|
|
2015-10-26 20:39:11 +01:00
|
|
|
def _get_inventory_move(self, move):
|
|
|
|
pool = Pool()
|
|
|
|
Move = pool.get('stock.move')
|
|
|
|
|
|
|
|
location = move.unit_load.get_location(
|
|
|
|
[move.unit_load], product_id=move.product.id, type='storage')[move.unit_load.id]
|
|
|
|
return Move(
|
|
|
|
from_location=location,
|
|
|
|
to_location=move.from_location,
|
|
|
|
product=move.product,
|
|
|
|
uom=move.uom,
|
|
|
|
quantity=move.quantity,
|
2016-01-07 18:57:44 +01:00
|
|
|
start_date=self.start_date,
|
|
|
|
end_date=self.end_date,
|
2015-10-26 20:39:11 +01:00
|
|
|
company=move.company,
|
|
|
|
currency=move.currency,
|
|
|
|
unit_price=move.unit_price,
|
|
|
|
unit_load=move.unit_load,
|
|
|
|
origin=move.origin,
|
|
|
|
lot=getattr(move, 'lot', None)
|
|
|
|
)
|
|
|
|
|
2015-10-09 14:06:06 +02:00
|
|
|
@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
|
2015-10-13 16:47:59 +02:00
|
|
|
lines = [l for l in self.lines if l.origin and unit_load in l.origin.unit_loads]
|
2015-10-09 14:06:06 +02:00
|
|
|
if not lines:
|
2015-10-13 16:47:59 +02:00
|
|
|
if self.ul_origin_restrict:
|
|
|
|
self.raise_user_error('ul_origin', unit_load.rec_name)
|
|
|
|
self.raise_user_warning('loading_ul_origin_%s' % unit_load.id, 'ul_origin',
|
|
|
|
unit_load.rec_name)
|
|
|
|
lines = self.lines
|
2015-10-09 14:06:06 +02:00
|
|
|
|
|
|
|
# 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
|
2015-10-13 16:47:59 +02:00
|
|
|
line = None
|
2015-10-09 14:06:06 +02:00
|
|
|
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
|
|
|
|
|
2016-01-28 23:37:01 +01:00
|
|
|
self._update_sale(unit_loads)
|
|
|
|
|
2016-04-05 12:10:42 +02:00
|
|
|
UL.save(unit_loads)
|
|
|
|
|
2015-10-09 14:06:06 +02:00
|
|
|
def check_ul_data_match(self, lines, unit_load):
|
|
|
|
valid_lines = []
|
|
|
|
for line in lines:
|
2015-10-20 19:27:57 +02:00
|
|
|
product = getattr(line.origin, 'product', None)
|
|
|
|
if not line.origin or not product:
|
2015-10-09 14:06:06 +02:00
|
|
|
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'
|
2016-08-23 09:10:49 +02:00
|
|
|
__metaclass__ = PoolMeta
|
2015-10-09 14:06:06 +02:00
|
|
|
|
2016-04-05 12:10:42 +02:00
|
|
|
ul_quantity = fields.Float('ULs', digits=(16, 0),
|
|
|
|
domain=[('ul_quantity', '>=', Eval('loaded_uls'))],
|
|
|
|
depends=['loaded_uls'])
|
2015-10-09 14:06:06 +02:00
|
|
|
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')
|
2015-10-13 16:47:59 +02:00
|
|
|
unit_loads = fields.One2Many('stock.unit_load', 'load_line', 'Unit loads',
|
2015-10-09 14:06:06 +02:00
|
|
|
readonly=True)
|
2016-04-05 12:10:42 +02:00
|
|
|
loaded_uls = fields.Function(fields.Float('Loaded ULs', digits=(16, 0)),
|
|
|
|
'get_loaded_uls')
|
2015-10-09 14:06:06 +02:00
|
|
|
|
|
|
|
@fields.depends('quantity', 'ul_quantity', 'uom')
|
|
|
|
def on_change_with_quantity_per_ul(self, name=None):
|
|
|
|
if self.quantity and self.ul_quantity:
|
2016-08-23 09:10:49 +02:00
|
|
|
return self.uom.round(self.quantity / self.ul_quantity)
|
2015-10-09 14:06:06 +02:00
|
|
|
return None
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _get_quantity_field(cls):
|
|
|
|
return 'ul_quantity'
|
|
|
|
|
2016-04-05 12:10:42 +02:00
|
|
|
def get_loaded_uls(self, name=None):
|
|
|
|
if not self.unit_loads:
|
|
|
|
return 0
|
|
|
|
return len(self.unit_loads)
|
|
|
|
|
2015-10-09 14:06:06 +02:00
|
|
|
|
|
|
|
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
|
2015-10-13 16:47:59 +02:00
|
|
|
# to read it with barcode scanner
|
2015-10-09 14:06:06 +02:00
|
|
|
class LoadUnitLoadData(ModelView):
|
|
|
|
"""Carrier load unit load"""
|
|
|
|
__name__ = 'carrier.load_uls.data'
|
|
|
|
|
|
|
|
load_order = fields.Many2One('carrier.load.order', 'Order',
|
2016-01-28 23:37:01 +01:00
|
|
|
readonly=True,
|
|
|
|
depends=['standalone', 'order_state'])
|
|
|
|
order_state = fields.Char('State', readonly=True)
|
|
|
|
standalone = fields.Boolean('Standalone', readonly=True)
|
2015-10-09 14:06:06 +02:00
|
|
|
ul_code = fields.Char('UL')
|
|
|
|
uls_to_load = fields.One2Many('stock.unit_load', None, 'ULs to load',
|
2016-02-13 08:25:22 +01:00
|
|
|
domain=[('state', '=', 'done'),
|
|
|
|
('available', '=', True)])
|
2015-10-23 18:55:28 +02:00
|
|
|
uls_loaded = fields.One2Many('stock.unit_load', None, 'Loaded ULs',
|
|
|
|
domain=[('load_order', '=', Eval('load_order'))],
|
|
|
|
context={'ul_loading': True},
|
|
|
|
depends=['load_order'])
|
2015-10-09 14:06:06 +02:00
|
|
|
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'),
|
2016-04-05 12:10:42 +02:00
|
|
|
Button('Unload ULs', 'unload_', 'tryton-clear'),
|
2016-01-28 23:37:01 +01:00
|
|
|
Button('Do', 'do_', 'tryton-ok',
|
|
|
|
states={'readonly': Eval('ul_code') | Eval('uls_to_load'),
|
2016-04-05 12:10:42 +02:00
|
|
|
'invisible': (Eval('order_state') == 'done')}),
|
2015-10-23 18:55:28 +02:00
|
|
|
Button('Load', 'load_', 'tryton-list-add', default=True)])
|
|
|
|
load_ = StateTransition()
|
|
|
|
unload_ = StateTransition()
|
2015-10-09 14:06:06 +02:00
|
|
|
do_ = StateTransition()
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def __setup__(cls):
|
|
|
|
super(LoadUnitLoad, cls).__setup__()
|
|
|
|
cls._error_messages.update({
|
|
|
|
'invalid_ul': 'Cannot find Unit load "%s".',
|
2015-10-23 18:55:28 +02:00
|
|
|
'ul_required': 'Must define an UL to load.',
|
|
|
|
'unload_any': 'Must select some Unit load from loaded ULs list in order to unload them.'})
|
2015-10-09 14:06:06 +02:00
|
|
|
|
|
|
|
def transition_start(self):
|
2016-02-01 12:53:27 +01:00
|
|
|
pool = Pool()
|
|
|
|
Loadorder = pool.get('carrier.load.order')
|
|
|
|
|
2015-10-09 14:06:06 +02:00
|
|
|
if Transaction().context.get('active_model') != LoadOrder.__name__:
|
|
|
|
return 'order'
|
2016-02-01 12:53:27 +01:00
|
|
|
order = Loadorder(Transaction().context['active_id'])
|
2016-02-02 09:27:53 +01:00
|
|
|
if order.state == 'waiting' and len(order.unit_loads) > 0:
|
2016-02-01 12:53:27 +01:00
|
|
|
Loadorder.run([order])
|
|
|
|
return 'end'
|
2015-10-09 14:06:06 +02:00
|
|
|
return 'data'
|
|
|
|
|
|
|
|
def default_data(self, fields):
|
2016-01-28 23:37:01 +01:00
|
|
|
order, standalone = self._get_load_order()
|
2015-10-09 14:06:06 +02:00
|
|
|
res = {'load_order': order.id,
|
2016-01-28 23:37:01 +01:00
|
|
|
'loaded_uls': 0,
|
|
|
|
'standalone': standalone,
|
|
|
|
'order_state': order.state}
|
2015-10-09 14:06:06 +02:00
|
|
|
if order.unit_loads:
|
2015-10-23 18:55:28 +02:00
|
|
|
res['loaded_uls'] = len(order.unit_loads)
|
2015-10-09 14:06:06 +02:00
|
|
|
return res
|
|
|
|
|
2015-10-23 18:55:28 +02:00
|
|
|
def transition_load_(self):
|
2015-10-09 14:06:06 +02:00
|
|
|
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')
|
|
|
|
|
2016-01-28 23:37:01 +01:00
|
|
|
order, _ = self._get_load_order()
|
2015-10-09 14:06:06 +02:00
|
|
|
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)
|
2016-05-31 17:25:42 +02:00
|
|
|
if order.state in ('draft', 'waiting'):
|
2015-10-13 16:47:59 +02:00
|
|
|
order.run([order])
|
2015-10-09 14:06:06 +02:00
|
|
|
return 'data'
|
|
|
|
|
|
|
|
def transition_do_(self):
|
|
|
|
pool = Pool()
|
|
|
|
Order = pool.get('carrier.load.order')
|
|
|
|
|
2016-01-28 23:37:01 +01:00
|
|
|
order, _ = self._get_load_order()
|
|
|
|
Order.do([order])
|
2015-10-09 14:06:06 +02:00
|
|
|
# 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__:
|
2016-01-28 23:37:01 +01:00
|
|
|
return Order(Transaction().context.get('active_id')), False
|
|
|
|
return Order(self.order.load_order.id), True
|
2015-10-23 18:55:28 +02:00
|
|
|
|
|
|
|
def transition_unload_(self):
|
|
|
|
pool = Pool()
|
|
|
|
UnitLoad = pool.get('stock.unit_load')
|
|
|
|
|
|
|
|
if not self.data.uls_loaded:
|
|
|
|
self.raise_user_error('unload_any')
|
2016-04-05 12:10:42 +02:00
|
|
|
|
|
|
|
order, _ = self._get_load_order()
|
|
|
|
uls = list(self.data.uls_loaded)
|
|
|
|
order._update_sale(uls)
|
|
|
|
UnitLoad.unload(uls)
|
2015-10-23 18:55:28 +02:00
|
|
|
return 'data'
|
2015-11-30 13:42:21 +01:00
|
|
|
|
|
|
|
|
|
|
|
class LoadSheet:
|
|
|
|
__name__ = 'carrier.load.sheet'
|
2016-08-23 09:10:49 +02:00
|
|
|
__metaclass__ = PoolMeta
|
2015-11-30 13:42:21 +01:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def get_context(cls, records, data):
|
|
|
|
report_context = super(LoadSheet, cls).get_context(records, data)
|
|
|
|
|
|
|
|
report_context['product_ul_quantity'] = lambda order, product: \
|
|
|
|
cls.product_ul_quantity(order, product)
|
|
|
|
|
|
|
|
return report_context
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _get_products(cls, order):
|
|
|
|
return list(set([ul.product for ul in order.unit_loads]))
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def product_ul_quantity(cls, order, product):
|
|
|
|
"""Returns product UL quantity in load order"""
|
|
|
|
value = 0
|
|
|
|
for line in order.lines:
|
|
|
|
value += sum(1 for ul in line.unit_loads if ul.product.id == product.id) or 0
|
|
|
|
return value
|
|
|
|
|
|
|
|
|
|
|
|
class CMR:
|
|
|
|
__name__ = 'carrier.load.order.cmr'
|
2016-08-23 09:10:49 +02:00
|
|
|
__metaclass__ = PoolMeta
|
2015-11-30 13:42:21 +01:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _get_products(cls, order):
|
2015-11-30 19:23:56 +01:00
|
|
|
return list(set([ul.product for ul in order.unit_loads]))
|
|
|
|
|
2016-01-25 13:52:45 +01:00
|
|
|
@classmethod
|
|
|
|
def product_weight(cls, order, product, language):
|
|
|
|
pool = Pool()
|
|
|
|
Uom = pool.get('product.uom')
|
|
|
|
Modeldata = pool.get('ir.model.data')
|
|
|
|
|
|
|
|
cat_weight = Modeldata.get_id('product', 'uom_cat_weight')
|
|
|
|
kg_uom = Uom(Modeldata.get_id('product', 'uom_kilogram'))
|
|
|
|
|
|
|
|
if product.default_uom.category.id != cat_weight:
|
|
|
|
return None
|
|
|
|
res = sum(Uom.compute_qty(
|
2016-01-25 15:41:37 +01:00
|
|
|
ul.uom, ul.quantity, kg_uom) or 0
|
2016-01-25 13:52:45 +01:00
|
|
|
for ul in order.unit_loads if ul.product.id == product.id) or None
|
|
|
|
if not res:
|
|
|
|
return super(CMR, cls).product_weight(order, product, language)
|
|
|
|
return res
|
|
|
|
|
2015-11-30 19:23:56 +01:00
|
|
|
|
|
|
|
class RoadTransportNote:
|
|
|
|
__name__ = 'carrier.load.order.road_note'
|
2016-08-23 09:10:49 +02:00
|
|
|
__metaclass__ = PoolMeta
|
2015-11-30 19:23:56 +01:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def _get_products(cls, order):
|
|
|
|
return list(set([ul.product for ul in order.unit_loads]))
|
2016-01-25 13:52:45 +01:00
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def product_weight(cls, order, product, language):
|
|
|
|
pool = Pool()
|
|
|
|
Uom = pool.get('product.uom')
|
|
|
|
Modeldata = pool.get('ir.model.data')
|
|
|
|
|
|
|
|
cat_weight = Modeldata.get_id('product', 'uom_cat_weight')
|
|
|
|
kg_uom = Uom(Modeldata.get_id('product', 'uom_kilogram'))
|
|
|
|
|
|
|
|
if product.default_uom.category.id != cat_weight:
|
|
|
|
return None
|
|
|
|
res = sum(Uom.compute_qty(
|
2016-01-25 15:41:37 +01:00
|
|
|
ul.uom, ul.quantity, kg_uom) or 0
|
2016-01-25 13:52:45 +01:00
|
|
|
for ul in order.unit_loads if ul.product.id == product.id) or None
|
|
|
|
if not res:
|
|
|
|
return super(RoadTransportNote, cls).product_weight(order, product, language)
|
2016-01-28 23:37:01 +01:00
|
|
|
return res
|
2016-04-05 12:10:42 +02:00
|
|
|
|
|
|
|
|
|
|
|
class EditLoadOrderLineData(ModelView):
|
|
|
|
"""Edit load order line data"""
|
|
|
|
__name__ = 'carrier.load.order.line.edit_qty.data'
|
|
|
|
|
|
|
|
lines = fields.One2Many('carrier.load.order.line', None, 'Lines')
|
|
|
|
|
|
|
|
|
|
|
|
class EditLoadOrderLine(Wizard):
|
|
|
|
"""Edit load order line"""
|
|
|
|
__name__ = 'carrier.load.order.line.edit_qty'
|
|
|
|
|
|
|
|
start = StateTransition()
|
|
|
|
data = StateView('carrier.load.order.line.edit_qty.data',
|
|
|
|
'carrier_load_ul.load_order_line_edit_qty_data_view_form',
|
|
|
|
[Button('Cancel', 'end', 'tryton-cancel'),
|
|
|
|
Button('OK', 'edit_', 'tryton-ok', default=True)])
|
|
|
|
edit_ = StateTransition()
|
|
|
|
|
|
|
|
def transition_start(self):
|
|
|
|
return 'data'
|
|
|
|
|
|
|
|
def default_data(self, fields):
|
|
|
|
Order = Pool().get('carrier.load.order')
|
|
|
|
|
|
|
|
order = Order(Transaction().context['active_id'])
|
|
|
|
return {'lines': map(int, order.lines)}
|
|
|
|
|
|
|
|
def transition_edit_(self):
|
|
|
|
if self.data.lines:
|
|
|
|
for line in self.data.lines:
|
|
|
|
line.save()
|
|
|
|
return 'end'
|