trytond-stock_unit_load/shipment.py

264 lines
8.9 KiB
Python

# The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
import datetime
from dateutil.relativedelta import relativedelta
from trytond.model import fields
from trytond.pool import PoolMeta, Pool
from trytond.pyson import Eval, Not, Bool, And
__all__ = ['ShipmentOut', 'ShipmentInternal', 'ShipmentOutReturn',
'ShipmentInReturn']
class ShipmentUnitLoadMixin(object):
unit_loads = fields.Function(
fields.One2Many('stock.unit_load', None, 'Unit loads',
states={
'readonly': Eval('state') != 'draft',
'invisible': And(
Eval('state') != 'draft',
Not(Bool(Eval('unit_loads', []))))
},
depends=['state']),
'get_unit_loads', setter='set_unit_loads',
searcher='search_unit_loads')
@classmethod
def __setup__(cls):
super().__setup__()
for _field_name in cls._get_ul_readonly_fields_name():
_field = getattr(cls, _field_name)
if _field.states.get('readonly'):
_field.states['readonly'] |= Eval('unit_loads')
else:
_field.states['readonly'] = Eval('unit_loads')
if 'unit_loads' not in _field.depends:
_field.depends.append('unit_loads')
def get_unit_loads(self, name=None):
pass
@classmethod
def set_unit_loads(cls, records, name, value):
pass
@classmethod
def search_unit_loads(cls, name, clause):
_field = 'moves.unit_load'
if '.' in name:
_field += '.%s' % name[10:]
return [(_field, ) + tuple(clause[1:])]
@classmethod
def _get_ul_readonly_fields_name(cls):
return []
class ShipmentOut(ShipmentUnitLoadMixin, metaclass=PoolMeta):
__name__ = 'stock.shipment.out'
@classmethod
def __setup__(cls):
super().__setup__()
cls.unit_loads.states['readonly'] |= Not(Bool(Eval('warehouse')))
cls.unit_loads.depends.append('warehouse')
def get_unit_loads(self, name=None):
if not self.outgoing_moves:
return []
uls = set(m.unit_load.id for m in self.outgoing_moves if m.unit_load)
return list(uls)
@fields.depends('unit_loads', 'outgoing_moves', 'warehouse_output',
'customer_location', 'end_date')
def on_change_unit_loads(self):
moves = []
ul_ids = [ul.id for ul in self.unit_loads]
if self.outgoing_moves:
moves.extend([
m for m in self.outgoing_moves
if m.unit_load and m.unit_load.id in ul_ids])
for ul in self.unit_loads:
if ul.id in [m.unit_load.id for m in moves]:
continue
new_moves = ul._move(self.customer_location,
self.end_date,
from_location=self.warehouse_output)
moves.extend(new_moves)
self.outgoing_moves = moves
def _get_inventory_move(self, move):
res = super(ShipmentOut, self)._get_inventory_move(move)
if res and move.unit_load:
location = move.unit_load.get_location([move.unit_load],
type='storage')[move.unit_load.id]
res.from_location = location
res.unit_load = move.unit_load
return res
@classmethod
def _get_ul_readonly_fields_name(cls):
return ['inventory_moves', 'outgoing_moves']
@classmethod
def _sync_inventory_to_outgoing(cls, shipments, quantity=True):
shipments = [s for s in shipments if not s.unit_loads]
super()._sync_inventory_to_outgoing(shipments, quantity=quantity)
class ShipmentInternal(ShipmentUnitLoadMixin, metaclass=PoolMeta):
__name__ = 'stock.shipment.internal'
ul_quantity = fields.Function(
fields.Float('ULs', digits=(16, 0)), 'get_ul_quantity')
def get_ul_quantity(self, name=None):
if not self.unit_loads:
return None
return len(self.unit_loads)
def get_unit_loads(self, name=None):
if not self.moves:
return []
uls = set(m.unit_load.id for m in self.moves if m.unit_load)
return list(uls)
@fields.depends('unit_loads', 'moves', 'date_time_', 'from_location',
'to_location')
def on_change_unit_loads(self):
moves = []
ul_ids = [ul.id for ul in self.unit_loads]
if self.moves:
moves.extend(
[m for m in self.moves if m.unit_load.id in ul_ids])
for ul in self.unit_loads:
if ul.id in [m.unit_load.id for m in moves]:
continue
new_moves = ul._move(self.to_location,
self.date_time_,
from_location=self.from_location)
moves.extend(new_moves)
self.moves = moves
@classmethod
def _get_ul_readonly_fields_name(cls):
return ['moves', 'outgoing_moves', 'incoming_moves']
@classmethod
def wait(cls, records, moves=None):
pool = Pool()
Move = pool.get('stock.move')
UnitLoad = pool.get('stock.unit_load')
# fix from location in UL moves
ul_moves = []
for record in records:
if record.state != 'draft':
continue
ul_locations = {}
if record.unit_loads:
# get current location one microsecond before this shipment
ul_locations.update(UnitLoad.get_location(
list(record.unit_loads),
at_date=(record.date_time_
- relativedelta(microseconds=1)),
type='storage'))
for move in record.moves:
if not move.unit_load:
continue
move.from_location = ul_locations[move.unit_load.id]
ul_moves.append(move)
if ul_moves:
Move.save(ul_moves)
super().wait(records, moves=moves)
class ShipmentOutReturn(ShipmentUnitLoadMixin, metaclass=PoolMeta):
__name__ = 'stock.shipment.out.return'
@classmethod
def __setup__(cls):
super().__setup__()
cls.unit_loads.states['readonly'] |= Not(Bool(Eval('warehouse')))
cls.unit_loads.depends.append('warehouse')
def get_unit_loads(self, name=None):
if not self.incoming_moves:
return []
uls = set(m.unit_load.id for m in self.incoming_moves if m.unit_load)
return list(uls)
@fields.depends('unit_loads', 'incoming_moves', 'effective_date',
'planned_date', 'start_time', 'warehouse_input', 'customer_location')
def on_change_unit_loads(self):
moves = []
ul_ids = [ul.id for ul in self.unit_loads]
date_time_ = datetime.datetime.combine(self.effective_date or
self.planned_date or datetime.date.today(), self.start_time or
datetime.datetime.now().time())
if self.incoming_moves:
moves.extend(
[m for m in self.incoming_moves if m.unit_load and
m.unit_load.id in ul_ids])
for ul in self.unit_loads:
if ul.id in [m.unit_load.id for m in moves]:
continue
new_moves = ul._move(self.warehouse_input, date_time_,
from_location=self.customer_location)
moves.extend(new_moves)
self.incoming_moves = moves
def _get_inventory_move(self, incoming_move):
move = super()._get_inventory_move(incoming_move)
if move and incoming_move.unit_load:
move.unit_load = incoming_move.unit_load
return move
@classmethod
def _get_ul_readonly_fields_name(cls):
return ['inventory_moves', 'incoming_moves']
class ShipmentInReturn(ShipmentUnitLoadMixin, metaclass=PoolMeta):
__name__ = 'stock.shipment.in.return'
def get_unit_loads(self, name=None):
if not self.moves:
return []
uls = set(m.unit_load.id for m in self.moves if m.unit_load)
return list(uls)
@fields.depends('unit_loads', 'moves', 'from_location', 'to_location',
'end_date')
def on_change_unit_loads(self):
moves = []
ul_ids = [ul.id for ul in self.unit_loads]
if self.moves:
moves.extend(
[m for m in self.moves if m.unit_load.id in ul_ids])
for ul in self.unit_loads:
if ul.id in [m.unit_load.id for m in moves]:
continue
new_moves = ul._move(self.to_location,
self.end_date,
from_location=self.from_location)
moves.extend(new_moves)
self.moves = moves
@classmethod
def _get_ul_readonly_fields_name(cls):
return ['moves']
class Assign(metaclass=PoolMeta):
__name__ = 'stock.shipment.assign'
def transition_start(self):
if self.record.unit_loads:
# if has unit loads, locations may not change
self.record.assign_force()
return 'end'
return super().transition_start()