trytond-carrier_load_ul/load.py

1009 lines
35 KiB
Python
Raw Normal View History

# 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
from decimal import Decimal
2017-01-18 08:36:19 +01:00
from trytond.rpc import RPC
from sql import Null
from sql.operators import Concat
from trytond.model import fields, ModelView, Model
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval, Bool, Id
from trytond.transaction import Transaction
from trytond.wizard import Wizard, StateTransition, StateView, Button, \
StateAction
2019-10-17 16:14:15 +02:00
try:
import phonenumbers
from phonenumbers import PhoneNumberFormat, NumberParseException
except ImportError:
phonenumbers = None
2017-01-18 08:36:19 +01:00
__all__ = ['Configuration', 'Load', 'LoadOrder', 'LoadOrderLine',
'LoadUnitLoad', 'LoadUnitLoadOrder', 'LoadUnitLoadData',
2018-01-18 18:58:14 +01:00
'LoadSheet', 'CMR', 'RoadTransportNote', 'CreateLoadDataMixin',
'CreateLoadDataLineMixin', 'LoadUnitLoadFailed']
2019-02-26 11:03:17 +01:00
class Configuration(metaclass=PoolMeta):
__name__ = 'carrier.configuration'
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
2019-02-26 11:03:17 +01:00
class Load(metaclass=PoolMeta):
__name__ = 'carrier.load'
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:])]
@property
def ul_quantity(self):
return sum(o.ul_quantity for o in self.orders)
2019-02-26 11:03:17 +01:00
class LoadOrder(metaclass=PoolMeta):
__name__ = 'carrier.load.order'
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']),
'get_unit_loads', setter='set_unit_loads',
searcher='search_unit_loads')
ul_origin_restrict = fields.Boolean('Restrict UL origin',
states={'readonly': Eval('state').in_(['cancel', 'done'])},
depends=['state'])
2016-01-28 23:37:01 +01:00
ul_quantity = fields.Function(fields.Float('ULs', digits=(16, 0)),
'get_ul_quantity')
@classmethod
def __setup__(cls):
super(LoadOrder, cls).__setup__()
cls._buttons.update({
'run_try': {
2019-02-26 11:03:17 +01:00
'icon': 'tryton-forward',
2018-07-19 10:06:27 +02:00
'invisible': ~Eval('state').in_(['waiting', 'running']),
'depends': ['state']},
})
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.',
'draft_ul': 'Cannot change state to Draft for load order "%s" '
'because it has loaded ULs.',
2016-01-28 23:37:01 +01:00
'pending_uls': 'You have loaded less ULs (%s) than expected (%s).',
'sale_confirmed': 'Cannot force loading ULs because sale "%s" is '
'confirmed.',
'unload_cancel': 'Cannot force unloading all ULs. Load order "%s" '
'must be cancelled completely.',
2016-04-13 08:55:20 +02:00
'no_uls': 'Load order "%s" must have some UL to be finished.'
2016-01-28 23:37:01 +01:00
})
@classmethod
def __register__(cls, module_name):
pool = Pool()
Sale = pool.get('sale.sale')
sale = Sale.__table__()
sql_table = cls.__table__()
cursor = Transaction().connection.cursor()
super(LoadOrder, cls).__register__(module_name)
# Migration from 4.0: set sale
cursor.execute(*sql_table.join(sale,
condition=Concat(
cls.__name__ + ',', sql_table.id) == sale.origin
).select(sql_table.id, sale.id,
where=(sql_table.sale == Null) &
(sql_table.type == 'out') &
(sql_table.state == 'done'),
group_by=[sql_table.id, sale.id])
)
for order_id, sale_id in cursor.fetchall():
cursor.execute(*sql_table.update([sql_table.sale], [sale_id],
where=sql_table.id == order_id))
@classmethod
def default_ul_origin_restrict(cls):
pool = Pool()
Configuration = pool.get('carrier.configuration')
conf = Configuration(1)
return conf.ul_origin_restrict
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:])]
def get_carrier_amount(self, name=None):
if (not self.load.unit_price or not self.ul_quantity or
not self.load.ul_quantity):
return 0
return self.load.currency.round(
2020-03-12 15:31:54 +01:00
(Decimal(self.ul_quantity) / Decimal(
self.load.ul_quantity)) * self.load.unit_price)
@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
def _get_load_sale_line(self, sale, key, grouped_items):
res = super(LoadOrder, self)._get_load_sale_line(
sale, key, grouped_items)
pool = Pool()
Modeldata = pool.get('ir.model.data')
Uom = pool.get('product.uom')
uom = Uom(Modeldata.get_id('product', 'uom_unit'))
2015-10-23 18:55:28 +02:00
res.ul_quantity = len(grouped_items)
res.ul_cases_quantity = (uom.round(
sum(item.cases_quantity
for item in grouped_items) / res.ul_quantity))
2015-10-23 18:55:28 +02:00
return res
def _get_shipment_out(self, sale):
res = super(LoadOrder, self)._get_shipment_out(sale)
res.start_date = self.start_date
res.on_change_start_date()
res.end_date = self.end_date
return res
def _get_shipment_moves(self, origin, 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 = []
if self.type == 'out':
from_location = origin.from_location.id
to_location = origin.to_location.id
elif self.type == 'internal':
to_location = self.to_location.id
2015-10-20 19:27:57 +02:00
for item in grouped_items:
if self.type == 'internal':
from_location = item.location.id
new_moves = item._get_new_moves({
'from_location': from_location,
'to_location': to_location,
'start_date': self.end_date,
'end_date': self.end_date,
'state': 'draft'})
if self.type == 'out':
move, = [m for m in new_moves if m.product == item.product]
move.origin = origin
moves.append(move)
out_location = self._get_outgoing_moves_location(origin)
for new_move in new_moves:
if new_move.product.id == item.product.id:
continue
if out_location and out_location != new_move.to_location:
new_move.to_location = out_location
new_move.origin = item.load_line
other_moves.append(new_move)
elif self.type == 'internal':
moves.extend(new_moves)
2015-10-26 20:39:11 +01:00
if other_moves:
Move.save(other_moves)
2015-10-20 19:27:57 +02:00
return moves
def _get_outgoing_moves_location(self, origin=None):
if origin:
return origin.to_location
if self.party:
return self.party.customer_location
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.shipment:
2016-01-28 23:37:01 +01:00
return
if self.type == 'out':
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
keyfunc = partial(self._group_sale_line_key, uls)
2016-04-05 12:10:42 +02:00
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)
2019-02-26 11:03:17 +01:00
_fields = list(key_dict.keys())
2016-01-28 23:37:01 +01:00
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 = None
if self.sale:
sale_line = [l for l in self.sale.lines
if get_line_values(l) == list(key_dict.values())]
2016-01-28 23:37:01 +01:00
if self.type == 'out':
if not sale_line:
if not _add:
continue
2019-11-26 11:26:02 +01:00
sale_line = self._get_load_sale_line(self.sale, key,
_groupitems)
sale_line.sale = self.sale
else:
sale_line, = sale_line
self._update_sale_line(sale_line, _groupitems, _add)
sale_line.save()
2016-01-28 23:37:01 +01:00
shipment = self.shipment
2016-01-28 23:37:01 +01:00
2016-04-05 12:10:42 +02:00
if _add:
outgoing_moves = self._get_shipment_moves(sale_line,
_groupitems)
2016-04-05 12:10:42 +02:00
inventory_moves = []
for move in outgoing_moves:
move.shipment = shipment
if self.type == 'out':
_inventory = shipment._get_inventory_move(move)
_inventory.start_date = self.start_date
inventory_moves.append(_inventory)
2016-04-05 12:10:42 +02:00
Move.save(outgoing_moves + inventory_moves)
Move.assign(outgoing_moves)
if self.type == 'out':
Move.do(inventory_moves)
2016-04-05 12:10:42 +02:00
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,
check_shipment=False):
2016-04-05 12:10:42 +02:00
Move.cancel(shipment_moves + load_moves)
Move.delete(shipment_moves + load_moves)
if sale_line and not sale_line.quantity:
2016-04-05 12:10:42 +02:00
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')
2016-01-28 23:37:01 +01:00
cls._check_loaded_quantity(records)
super(LoadOrder, cls).do(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-28 23:37:01 +01:00
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]
2015-10-26 20:39:11 +01:00
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)
)
@classmethod
@ModelView.button_action('carrier_load_ul.wizard_load_ul')
def run_try(cls, records):
pass
def get_failed_uls(self, unit_loads):
failed_uls = []
for unit_load in unit_loads:
lines = [l for l in self.lines if l.origin and
unit_load in l.origin.unit_loads]
if not self.check_ul_data_match(lines, unit_load):
failed_uls.append(unit_load)
return failed_uls
def add_ul(self, unit_loads, origin_restrict=None,
origin_restrict_warn=True, force=False):
pool = Pool()
UL = pool.get('stock.unit_load')
2017-01-18 08:36:19 +01:00
if origin_restrict is None:
origin_restrict = self.ul_origin_restrict
order_lines = {}
failed_uls = []
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 = location.warehouse
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 l.origin and
unit_load in getattr(l.origin, 'unit_loads', [])]
if not lines:
lines = self.check_origin_restrict(unit_load, origin_restrict,
origin_restrict_warn)
# check data matches
matched_lines = self.check_ul_data_match(lines, unit_load)
if not matched_lines:
if not force:
failed_uls.append(unit_load)
else:
matched_lines = lines
# check overload line qty
line = self._choose_matched_line(matched_lines, order_lines,
unit_load)
if line:
# load UL
unit_load.load_line = line
elif unit_load not in failed_uls:
self.raise_user_error('ul_overload', self.rec_name)
if failed_uls:
return failed_uls
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)
def _choose_matched_line(self, lines, values, unit_load):
line = None
for _line in lines:
if _line.id not in values:
values.setdefault(_line.id, set(
ul for ul in _line.unit_loads))
if _line.ul_quantity - len(values[_line.id]) > 0:
line = _line
break
if line:
values[line.id].add(unit_load)
return line
def check_origin_restrict(self, unit_load, origin_restrict,
origin_restrict_warn):
lines = []
warn = False
for line in self.lines:
if not line.origin:
lines.append(line)
elif line.origin.__name__ in self.valid_origin_restrict():
lines.append(line)
else:
if origin_restrict:
self.raise_user_error('ul_origin', unit_load.rec_name)
if origin_restrict_warn:
warn = True
lines.append(line)
if warn:
self.raise_user_warning('loading_ul_origin_%s' %
unit_load.id, 'ul_origin', unit_load.rec_name)
return lines
@classmethod
def valid_origin_restrict(cls):
return ['sale.line']
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:
valid_lines.append(line)
continue
if product.id == unit_load.product.id:
valid_lines.append(line)
return valid_lines
@classmethod
def _group_line_key(cls, items, item):
res = super(LoadOrder, cls)._group_line_key(items, item)
if (item.load_line.origin and
item.load_line.origin.__name__ == 'sale.line'):
return res + (('id', item.load_line.origin.id), )
return res
@classmethod
def _group_sale_line_key(cls, items, item):
if getattr(item, 'sale_line', None):
return (('id', item.sale_line.id), )
return cls._group_line_key(items, item)
def unload_ul(self, unit_loads):
UnitLoad = Pool().get('stock.unit_load')
self._update_sale(unit_loads)
UnitLoad.unload(unit_loads)
2019-02-26 11:03:17 +01:00
class LoadOrderLine(metaclass=PoolMeta):
__name__ = 'carrier.load.order.line'
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'])
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', 'Unit loads',
readonly=True)
2016-04-05 12:10:42 +02:00
loaded_uls = fields.Function(fields.Float('Loaded ULs', digits=(16, 0)),
'get_loaded_uls')
@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)
return None
@classmethod
def _get_quantity_field(cls):
return 'ul_quantity'
2019-09-19 20:02:47 +02:00
@classmethod
def default_loaded_uls(cls):
return 0
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)
@classmethod
def _get_origin(cls):
res = super(LoadOrderLine, cls)._get_origin()
res.append('sale.line')
return res
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: configure ul_code reading by a string pattern (ex: P${code})
# in carrier.configuration to read it with barcode scanner
class LoadUnitLoadData(ModelView):
"""Carrier load unit load"""
__name__ = 'carrier.load_uls.data'
load_order = fields.Many2One('carrier.load.order', 'Order',
readonly=True,
depends=['standalone', 'order_state'])
2016-01-28 23:37:01 +01:00
order_state = fields.Char('State', readonly=True)
standalone = fields.Boolean('Standalone', readonly=True)
ul_code = fields.Char('UL')
uls_to_load = fields.One2Many('stock.unit_load', None, 'ULs to load',
domain=[('state', '=', 'done'),
('available', '=', True)])
uls_loaded = fields.Many2Many('stock.unit_load', None, None,
'Loaded ULs',
domain=[('id', 'in', Eval('uls_loaded_domain'))],
context={'ul_loading': True},
depends=['load_order', 'uls_loaded_domain'])
uls_loaded_domain = fields.One2Many('stock.unit_load', None,
'ULs loaded Domain')
loaded_uls = fields.Float('Loaded ULs', digits=(16, 0),
readonly=True)
class LoadUnitLoadFailed(ModelView):
"""Carrier load unit load failed"""
__name__ = 'carrier.load_uls.failed'
failed_uls = fields.One2Many('stock.unit_load', None, 'Failed ULs',
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', 'post_order', 'tryton-ok', default=True)])
post_order = StateTransition()
data = StateView('carrier.load_uls.data',
'carrier_load_ul.load_uls_data_view_form', [
Button('Exit', 'end', 'tryton-cancel'),
2019-02-26 11:03:17 +01:00
Button('Unload ULs', 'unload_', 'tryton-undo'),
Button('Do', 'do_', 'tryton-ok', states={
'readonly': Eval('ul_code') | Eval('uls_to_load'),
'invisible': (Eval('order_state') == 'done')}),
Button('Load', 'load_', 'tryton-add', default=True)])
failed = StateView('carrier.load_uls.failed',
'carrier_load_ul.load_uls_failed_view_form', [
Button('Force load', 'force', 'tryton-forward',
states={
'readonly': ~Id('carrier_load_ul',
'group_force_load').in_(
Eval('context', {}).get('groups', [])),
}),
Button('Accept', 'data', 'tryton-ok', True)
])
force = StateTransition()
2015-10-23 18:55:28 +02:00
load_ = StateTransition()
unload_ = StateTransition()
do_ = StateTransition()
open_ = StateAction('carrier_load.act_load_order')
@classmethod
def __setup__(cls):
super(LoadUnitLoad, cls).__setup__()
2017-01-18 08:36:19 +01:00
cls.__rpc__.update({
'load': RPC(readonly=False),
'unload': RPC(readonly=False),
})
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.'})
def transition_start(self):
2016-02-01 12:53:27 +01:00
pool = Pool()
Loadorder = pool.get('carrier.load.order')
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'
return 'data'
def transition_post_order(self):
return 'data'
def default_data(self, fields):
2016-01-28 23:37:01 +01:00
order, standalone = self._get_load_order()
res = {'load_order': order.id,
2016-01-28 23:37:01 +01:00
'loaded_uls': 0,
'standalone': standalone,
2019-02-26 11:03:17 +01:00
'uls_loaded_domain': list(map(int, order.unit_loads)) or [],
2016-01-28 23:37:01 +01:00
'order_state': order.state}
if order.unit_loads:
2015-10-23 18:55:28 +02:00
res['loaded_uls'] = len(order.unit_loads)
return res
2017-01-18 08:36:19 +01:00
@classmethod
def load(cls, order_id, ul_code, uls=[], origin_restrict=True,
origin_restrict_warn=True):
pool = Pool()
2017-01-18 08:36:19 +01:00
Order = pool.get('carrier.load.order')
2017-07-28 18:55:55 +02:00
if uls is not None:
uls = list(uls)
2017-01-18 08:36:19 +01:00
if not ul_code and not uls:
cls.raise_user_error('ul_required')
if isinstance(order_id, int):
order = Order(order_id)
else:
order = order_id
if ul_code:
ul = cls._get_unit_load_by_code(ul_code)
uls.append(ul[0])
if uls:
failed_uls = order.add_ul(uls,
origin_restrict_warn=origin_restrict_warn)
if failed_uls:
return failed_uls
2016-05-31 17:25:42 +02:00
if order.state in ('draft', 'waiting'):
order.run([order])
2017-01-18 08:36:19 +01:00
def transition_load_(self, order_id=None, code=None):
order, _ = self._get_load_order()
failed_uls = self.load(order, self.data.ul_code, self.data.uls_to_load)
if failed_uls:
return 'failed'
return 'data'
def default_failed(self, fields):
order, _ = self._get_load_order()
if self.data.ul_code:
uls = self._get_unit_load_by_code(self.data.ul_code)
else:
uls = self.data.uls_to_load
failed_uls = order.get_failed_uls(uls)
return {
'failed_uls': [f.id for f in failed_uls]
}
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])
# TODO: print reports
if Transaction().context['active_model'] != 'carrier.load.order':
return 'open_'
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
2017-01-18 08:36:19 +01:00
@classmethod
def unload(cls, order_id, uls=[]):
2015-10-23 18:55:28 +02:00
pool = Pool()
UnitLoad = pool.get('stock.unit_load')
2017-01-18 08:36:19 +01:00
Order = pool.get('carrier.load.order')
2015-10-23 18:55:28 +02:00
2017-01-18 08:36:19 +01:00
if not uls:
cls.raise_user_error('unload_any')
if isinstance(order_id, int):
order = Order(order_id)
else:
order = order_id
if isinstance(uls[0], int):
uls = UnitLoad.browse(uls)
order.unload_ul(uls)
2017-01-18 08:36:19 +01:00
def transition_unload_(self):
order, _ = self._get_load_order()
self.unload(order, list(self.data.uls_loaded))
2015-10-23 18:55:28 +02:00
return 'data'
def transition_force(self):
uls = self.failed.failed_uls
order, _ = self._get_load_order()
order.add_ul(uls, origin_restrict_warn=False, force=True)
return 'data'
@classmethod
def _get_unit_load_by_code(cls, code):
UL = Pool().get('stock.unit_load')
uls = UL.search([('code', '=', code)])
if not uls:
cls.raise_user_error('invalid_ul', code)
return uls
def do_open_(self, action):
order, _ = self._get_load_order()
action['views'].reverse()
return action, {'res_id': [order.id]}
2019-02-26 11:03:17 +01:00
class LoadSheet(metaclass=PoolMeta):
__name__ = 'carrier.load.sheet'
@classmethod
def _get_lines(cls, order):
res = {}
if not order.unit_loads:
return super()._get_lines(order)
for ul in order.unit_loads:
res.setdefault(ul.product.id, cls.get_line_dict(ul.product))
res[ul.product.id]['ul_quantity'] += 1
res[ul.product.id]['cases_quantity'] += ul.cases_quantity
return res
@classmethod
def get_line_dict(cls, item):
res = super().get_line_dict(item)
res.update({
'ul_quantity': 0,
'cases_quantity': 0
})
return res
2017-10-06 09:48:01 +02:00
class TransportReportMixin(object):
@classmethod
def _get_product_origins(cls, order):
if order.unit_loads:
return order.unit_loads
return super()._get_product_origins(order)
2016-01-25 13:52:45 +01:00
@classmethod
def product_weight(cls, product_key, origins, language):
2016-01-25 13:52:45 +01:00
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_key[0].default_uom.category.id != cat_weight:
2016-01-25 13:52:45 +01:00
return None
if origins[0].__name__ == 'stock.unit_load':
res = sum(Uom.compute_qty(
ul.uom, ul.quantity, kg_uom) or 0 for ul in origins)
return res
return super().product_weight(product_key, origins, language)
2016-01-25 13:52:45 +01:00
@classmethod
def product_packages(cls, product_key, origins, language):
if origins[0].__name__ == 'stock.unit_load':
return sum(ul.cases_quantity for ul in origins) or None
return super().product_packages(product_key, origins, language)
2016-01-25 13:52:45 +01:00
2019-02-26 11:03:17 +01:00
class CMR(TransportReportMixin, metaclass=PoolMeta):
2017-10-06 09:48:01 +02:00
__name__ = 'carrier.load.order.cmr'
2016-01-25 13:52:45 +01:00
2017-10-06 09:48:01 +02:00
2019-02-26 11:03:17 +01:00
class RoadTransportNote(TransportReportMixin, metaclass=PoolMeta):
2017-10-06 09:48:01 +02:00
__name__ = 'carrier.load.order.road_note'
2016-04-05 12:10:42 +02:00
class CreateLoadDataMixin(object):
load_order = fields.Many2One('carrier.load.order', 'Load order',
domain=[
('type', '=', 'out'),
('state', 'in', ['draft', 'waiting', 'running']),
('lines.origin', 'like', 'agro.production.order,%')])
warehouse = fields.Many2One('stock.location', 'Warehouse',
readonly=True,
domain=[('type', '=', 'warehouse')],
states={'invisible': Bool(Eval('load_order'))},
depends=['load_order'])
dock = fields.Many2One('stock.location.dock', 'Dock',
domain=[('location', '=', Eval('warehouse', 0))],
states={
2019-10-17 16:14:15 +02:00
'invisible': Bool(Eval('load_order'))
},
depends=['warehouse', 'load_order'])
carrier = fields.Many2One('carrier', 'Carrier',
states={
'required': ~Eval('load_order') & Bool(Eval('load_purchasable')),
'invisible': Bool(Eval('load_order'))
},
depends=['load_order', 'load_purchasable'])
vehicle_number = fields.Char('Vehicle reg. number',
states={
'required': ~Eval('load_order') & Bool(Eval('vehicle_required')),
'invisible': Bool(Eval('load_order'))
},
depends=['load_order', 'vehicle_required'])
vehicle_required = fields.Boolean('Vehicle required')
trailer_number = fields.Char('Trailer reg. number',
states={
'invisible': Bool(Eval('load_order')),
'required': ~Eval('load_order') & Bool(Eval('trailer_required')),
},
depends=['load_order', 'trailer_required'])
trailer_required = fields.Boolean('Trailer required')
load_purchasable = fields.Boolean('Load purchasable',
states={'invisible': Bool(Eval('load_order'))},
depends=['load_order'])
driver = fields.Char('Driver',
states={
'invisible': Bool(Eval('load_order'))
2019-10-17 16:14:15 +02:00
}, depends=['load_order'])
driver_identifier = fields.Char('Driver identifier',
states={
'required': Bool(Eval('driver')),
'invisible': Bool(Eval('load_order'))},
depends=['driver', 'load_order'])
carrier_info = fields.Text('Carrier information',
states={
'invisible': Bool(Eval('load_purchasable')) | Bool(Eval('carrier'))
},
depends=['load_purchasable', 'carrier'])
2019-10-17 16:14:15 +02:00
driver_phone = fields.Char('Driver Phone',
states={
'invisible': Bool(Eval('load_order'))
}, depends=['load_order'])
@classmethod
def __setup__(cls):
super().__setup__()
cls._error_messages.update({
'invalid_phonenumber': ('The phone number "%(phone)s" '
'is not valid.'),
})
@classmethod
def default_load_purchasable(cls):
pool = Pool()
Configuration = pool.get('carrier.configuration')
conf = Configuration(1)
return conf.load_purchasable
@fields.depends('carrier', 'vehicle_number')
def autocomplete_vehicle_number(self):
Load = Pool().get('carrier.load')
return Load._autocomplete_registration_numbers(self.carrier,
'vehicle_number', self.vehicle_number)
@fields.depends('carrier', 'trailer_number')
def autocomplete_trailer_number(self):
Load = Pool().get('carrier.load')
return Load._autocomplete_registration_numbers(self.carrier,
'trailer_number', self.trailer_number)
@fields.depends('carrier', 'driver')
def autocomplete_driver(self):
Load = Pool().get('carrier.load')
return Load._autocomplete_registration_numbers(self.carrier,
'driver', self.driver)
@fields.depends('carrier', 'driver_identifier')
def autocomplete_driver_identifier(self):
Load = Pool().get('carrier.load')
return Load._autocomplete_registration_numbers(self.carrier,
'driver_identifier', self.driver_identifier)
2019-10-17 16:14:15 +02:00
@fields.depends('driver_phone')
def on_change_driver_phone(self):
self.driver_phone = self.format_phone(self.driver_phone)
@classmethod
def format_phone(cls, value=None):
if phonenumbers:
try:
phonenumber = phonenumbers.parse(value)
except NumberParseException:
pass
else:
value = phonenumbers.format_number(
phonenumber, PhoneNumberFormat.INTERNATIONAL)
return value
class CreateLoadDataLineMixin(object):
available_ul_quantity = fields.Float('Available ULs', digits=(16, 0),
readonly=True)
ul_quantity = fields.Float('ULs', digits=(16, 0),
domain=[
('ul_quantity', '<=', Eval('available_ul_quantity')),
('ul_quantity', '>=', 0)],
depends=['available_ul_quantity'])
class LoadOrderGrouping(metaclass=PoolMeta):
__name__ = 'carrier.load.order'
@classmethod
def _get_load_group_key(cls, items, item):
res = super()._get_load_group_key(items, item)
if (item.load_line and
item.load_line.order.party.load_grouping_method == 'unit_load'):
if res is None:
res = {}
res['unit_load'] = item.id
return res