Files
tryton/shipment.py
T

2600 lines
95 KiB
Python
Raw Normal View History

2014-12-02 12:56:04 +01:00
# This file is part of Tryton. The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
2015-01-20 18:49:56 +01:00
import functools
import datetime
from collections import defaultdict
2018-09-01 01:02:04 +02:00
from functools import partial
2018-08-09 20:43:51 +02:00
from sql import Null
2018-12-29 14:20:29 +01:00
from trytond.i18n import gettext
2012-03-25 00:18:19 +01:00
from trytond.model import Workflow, ModelView, ModelSQL, fields
2018-12-29 14:20:29 +01:00
from trytond.model.exceptions import AccessError
2009-10-02 15:12:04 +02:00
from trytond.modules.company import CompanyReport
from trytond.wizard import Wizard, StateTransition, StateView, Button
from trytond.pyson import Eval, If, Id, Bool
2010-08-19 22:22:44 +02:00
from trytond.transaction import Transaction
2012-09-24 22:18:17 +02:00
from trytond.pool import Pool, PoolMeta
2008-04-08 14:27:53 +02:00
2012-09-24 22:18:17 +02:00
__all__ = ['ShipmentIn', 'ShipmentInReturn',
'ShipmentOut', 'ShipmentOutReturn',
'AssignShipmentOutAssignFailed', 'AssignShipmentOut',
'ShipmentInternal',
'Address',
'AssignShipmentInternalAssignFailed', 'AssignShipmentInternal',
'AssignShipmentInReturnAssignFailed', 'AssignShipmentInReturn',
'DeliveryNote', 'PickingList',
'SupplierRestockingList', 'CustomerReturnRestockingList',
'InteralShipmentReport']
2008-05-05 20:24:09 +02:00
2017-02-01 21:41:00 +01:00
def set_employee(field):
def decorator(func):
@functools.wraps(func)
def wrapper(cls, shipments, *args, **kwargs):
pool = Pool()
User = pool.get('res.user')
user = User(Transaction().user)
result = func(cls, shipments, *args, **kwargs)
employee = user.employee
if employee:
company = employee.company
cls.write(
[s for s in shipments
if not getattr(s, field) and s.company == company], {
field: employee.id,
})
return result
return wrapper
return decorator
def employee_field(string):
return fields.Many2One(
'company.employee', string,
domain=[('company', '=', Eval('company', -1))],
states={
'readonly': Eval('state').in_(['done', 'cancel']),
},
depends=['company', 'state'])
2019-09-20 11:47:15 +02:00
class ShipmentMixin:
@classmethod
def view_attributes(cls):
return [
('/tree', 'visual', If(Eval('state') == 'cancel', 'muted', '')),
]
class ShipmentIn(ShipmentMixin, Workflow, ModelSQL, ModelView):
2009-03-20 12:14:13 +01:00
"Supplier Shipment"
2012-09-24 22:18:17 +02:00
__name__ = 'stock.shipment.in'
_rec_name = 'number'
effective_date = fields.Date('Effective Date',
states={
'readonly': Eval('state').in_(['cancel', 'done']),
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="When the stock was actually received.")
2010-01-20 11:04:28 +01:00
planned_date = fields.Date('Planned Date', states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2018-12-04 12:25:45 +01:00
}, depends=['state'],
help="When the stock is expected to be received.")
2011-10-04 18:17:41 +02:00
company = fields.Many2One('company.company', 'Company', required=True,
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2011-10-04 18:17:41 +02:00
},
domain=[
2015-06-28 23:45:31 +02:00
('id', If(Eval('context', {}).contains('company'), '=', '!='),
2013-10-09 18:25:53 +02:00
Eval('context', {}).get('company', -1)),
2011-10-04 18:17:41 +02:00
],
2018-12-04 12:25:45 +01:00
depends=['state'],
help="The company the shipment is associated with.")
2012-03-05 10:53:08 +01:00
reference = fields.Char("Reference", size=None, select=True,
2011-08-04 16:57:42 +02:00
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2018-12-04 12:25:45 +01:00
}, depends=['state'],
help="The supplier's identifier for the shipment.")
2008-10-22 20:13:23 +02:00
supplier = fields.Many2One('party.party', 'Supplier',
2011-08-04 16:57:42 +02:00
states={
2015-06-28 23:45:31 +02:00
'readonly': (((Eval('state') != 'draft')
| Eval('incoming_moves', [0]))
& Eval('supplier')),
2014-01-26 14:29:37 +01:00
}, required=True,
2018-12-04 12:25:45 +01:00
depends=['state', 'supplier'],
help="The party that supplied the stock.")
supplier_location = fields.Function(fields.Many2One('stock.location',
2014-01-26 14:29:37 +01:00
'Supplier Location'),
2012-09-24 22:18:17 +02:00
'on_change_with_supplier_location')
2008-10-22 20:13:23 +02:00
contact_address = fields.Many2One('party.address', 'Contact Address',
2011-08-04 16:57:42 +02:00
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2011-08-04 16:57:42 +02:00
}, domain=[('party', '=', Eval('supplier'))],
2018-12-04 12:25:45 +01:00
depends=['state', 'supplier'],
help="The address at which the supplier can be contacted.")
warehouse = fields.Many2One('stock.location', "Warehouse",
2011-08-04 16:57:42 +02:00
required=True, domain=[('type', '=', 'warehouse')],
states={
2015-06-28 23:45:31 +02:00
'readonly': (Eval('state').in_(['cancel', 'done'])
| Eval('incoming_moves', [0]) | Eval('inventory_moves', [0])),
2018-12-04 12:25:45 +01:00
}, depends=['state'],
help="Where the stock is received.")
warehouse_input = fields.Function(fields.Many2One('stock.location',
2014-01-26 14:29:37 +01:00
'Warehouse Input'),
2012-09-24 22:18:17 +02:00
'on_change_with_warehouse_input')
warehouse_storage = fields.Function(fields.Many2One('stock.location',
2014-01-26 14:29:37 +01:00
'Warehouse Storage'),
2012-09-24 22:18:17 +02:00
'on_change_with_warehouse_storage')
2013-05-27 18:15:02 +02:00
incoming_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
'Incoming Moves',
add_remove=[
2013-03-23 15:42:36 +01:00
('shipment', '=', None),
('from_location', '=', Eval('supplier_location')),
('state', '=', 'draft'),
2018-03-24 12:01:58 +01:00
If(Eval('warehouse_input') == Eval('warehouse_storage'),
('to_location', 'child_of',
[Eval('warehouse_input', -1)], 'parent'),
('to_location', '=', Eval('warehouse_input'))),
],
domain=[
('from_location', '=', Eval('supplier_location')),
2018-03-24 12:01:58 +01:00
If(Eval('warehouse_input') == Eval('warehouse_storage'),
('to_location', 'child_of',
[Eval('warehouse_input', -1)], 'parent'),
('to_location', '=', Eval('warehouse_input'))),
('company', '=', Eval('company')),
],
states={
'readonly': (Eval('state').in_(['received', 'done', 'cancel'])
| ~Eval('warehouse') | ~Eval('supplier')),
},
depends=['state', 'warehouse', 'supplier_location',
2018-12-04 12:25:45 +01:00
'warehouse_input', 'warehouse_storage', 'company'],
help="The moves that bring the stock into the warehouse."),
2011-08-04 16:57:42 +02:00
'get_incoming_moves', setter='set_incoming_moves')
2013-05-27 18:15:02 +02:00
inventory_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
'Inventory Moves',
domain=[
('from_location', '=', Eval('warehouse_input')),
If(~Eval('state').in_(['done', 'cancel']),
('to_location', 'child_of',
[Eval('warehouse_storage', -1)], 'parent'),
(),),
('company', '=', Eval('company')),
],
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state').in_(['draft', 'done', 'cancel']),
2018-03-24 12:01:58 +01:00
'invisible': (
Eval('warehouse_input') == Eval('warehouse_storage')),
},
depends=['state', 'warehouse', 'warehouse_input',
2018-12-04 12:25:45 +01:00
'warehouse_storage', 'company'],
help="The moves that put the stock away into the storage area."),
2011-08-04 16:57:42 +02:00
'get_inventory_moves', setter='set_inventory_moves')
2013-03-23 15:42:36 +01:00
moves = fields.One2Many('stock.move', 'shipment', 'Moves',
2011-10-04 18:17:41 +02:00
domain=[('company', '=', Eval('company'))], readonly=True,
depends=['company'])
2013-09-19 17:34:23 +02:00
origins = fields.Function(fields.Char('Origins'), 'get_origins')
2018-12-04 12:25:45 +01:00
number = fields.Char('Number', size=None, select=True, readonly=True,
help="The main identifier for the shipment.")
2017-02-01 21:41:00 +01:00
received_by = employee_field("Received By")
done_by = employee_field("Done By")
state = fields.Selection([
('draft', 'Draft'),
('done', 'Done'),
2009-03-20 13:09:19 +01:00
('cancel', 'Canceled'),
('received', 'Received'),
2018-12-04 12:25:45 +01:00
], 'State', readonly=True,
help="The current state of the shipment.")
2008-04-08 14:27:53 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def __setup__(cls):
super(ShipmentIn, cls).__setup__()
cls._order[0] = ('id', 'DESC')
cls._transitions |= set((
2012-03-25 00:18:19 +01:00
('draft', 'received'),
('received', 'done'),
('draft', 'cancel'),
('received', 'cancel'),
('cancel', 'draft'),
2012-03-25 00:18:19 +01:00
))
2012-09-24 22:18:17 +02:00
cls._buttons.update({
2012-03-25 00:18:19 +01:00
'cancel': {
'invisible': Eval('state').in_(['cancel', 'done']),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'draft': {
'invisible': Eval('state') != 'cancel',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'receive': {
'invisible': Eval('state') != 'draft',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'done': {
'invisible': Eval('state') != 'received',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
})
2008-04-08 14:27:53 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def __register__(cls, module_name):
2018-07-24 22:01:08 +02:00
table = cls.__table_handler__(module_name)
2011-10-04 18:17:41 +02:00
# Migration from 3.8: rename code into number
if table.column_exist('code'):
table.column_rename('code', 'number')
2012-09-24 22:18:17 +02:00
super(ShipmentIn, cls).__register__(module_name)
# Add index on create_date
2018-07-24 22:01:08 +02:00
table = cls.__table_handler__(module_name)
table.index_action('create_date', action='add')
@staticmethod
def default_planned_date():
return Pool().get('ir.date').today()
2012-09-24 22:18:17 +02:00
@staticmethod
def default_state():
2008-04-08 14:27:53 +02:00
return 'draft'
2012-09-24 22:18:17 +02:00
@classmethod
def default_warehouse(cls):
Location = Pool().get('stock.location')
return Location.get_default_warehouse()
2008-11-11 23:31:30 +01:00
2012-09-24 22:18:17 +02:00
@staticmethod
def default_company():
2012-03-27 10:44:04 +02:00
return Transaction().context.get('company')
2011-10-04 18:17:41 +02:00
2014-01-26 14:29:37 +01:00
@fields.depends('supplier')
2012-09-24 22:18:17 +02:00
def on_change_supplier(self):
2014-10-29 20:40:25 +01:00
self.contact_address = None
2013-12-12 13:50:05 +01:00
if self.supplier:
2014-10-29 20:40:25 +01:00
self.contact_address = self.supplier.address_get()
2014-01-26 14:29:37 +01:00
@fields.depends('supplier')
2012-09-24 22:18:17 +02:00
def on_change_with_supplier_location(self, name=None):
if self.supplier:
return self.supplier.supplier_location.id
2012-09-24 22:18:17 +02:00
@classmethod
def default_warehouse_input(cls):
warehouse = cls.default_warehouse()
if warehouse:
2012-09-24 22:18:17 +02:00
return cls(warehouse=warehouse).on_change_with_warehouse_input()
2014-01-26 14:29:37 +01:00
@fields.depends('warehouse')
2012-09-24 22:18:17 +02:00
def on_change_with_warehouse_input(self, name=None):
if self.warehouse:
return self.warehouse.input_location.id
2012-09-24 22:18:17 +02:00
@classmethod
def default_warehouse_storage(cls):
warehouse = cls.default_warehouse()
if warehouse:
2012-09-24 22:18:17 +02:00
return cls(warehouse=warehouse).on_change_with_warehouse_storage()
2014-01-26 14:29:37 +01:00
@fields.depends('warehouse')
2012-09-24 22:18:17 +02:00
def on_change_with_warehouse_storage(self, name=None):
if self.warehouse:
return self.warehouse.storage_location.id
2012-09-24 22:18:17 +02:00
def get_incoming_moves(self, name):
moves = []
2018-03-24 12:01:58 +01:00
if self.warehouse_input == self.warehouse_storage:
return [m.id for m in self.moves]
2012-09-24 22:18:17 +02:00
for move in self.moves:
if move.to_location == self.warehouse_input:
2012-09-24 22:18:17 +02:00
moves.append(move.id)
return moves
2012-09-24 22:18:17 +02:00
@classmethod
def set_incoming_moves(cls, shipments, name, value):
2008-12-04 18:34:30 +01:00
if not value:
return
2012-09-24 22:18:17 +02:00
cls.write(shipments, {
'moves': value,
})
2012-09-24 22:18:17 +02:00
def get_inventory_moves(self, name):
moves = []
for move in self.moves:
if move.from_location == self.warehouse_input:
2012-09-24 22:18:17 +02:00
moves.append(move.id)
return moves
2012-09-24 22:18:17 +02:00
@classmethod
def set_inventory_moves(cls, shipments, name, value):
2008-12-04 18:34:30 +01:00
if not value:
return
2012-09-24 22:18:17 +02:00
cls.write(shipments, {
'moves': value,
})
2012-09-24 22:18:17 +02:00
@property
def _move_planned_date(self):
'''
Return the planned date for incoming moves and inventory_moves
'''
2012-09-24 22:18:17 +02:00
return self.planned_date, self.planned_date
2012-09-24 22:18:17 +02:00
@classmethod
def _set_move_planned_date(cls, shipments):
'''
Set planned date of moves for the shipments
'''
2012-09-24 22:18:17 +02:00
Move = Pool().get('stock.move')
for shipment in shipments:
dates = shipment._move_planned_date
incoming_date, inventory_date = dates
# Update planned_date only for later to not be too optimistic if
# the shipment is not directly received.
2012-09-24 22:18:17 +02:00
Move.write([m for m in shipment.incoming_moves
2012-11-28 22:35:33 +01:00
if (m.state not in ('assigned', 'done', 'cancel')
and ((m.planned_date or datetime.date.max)
< (incoming_date or datetime.date.max)))], {
'planned_date': incoming_date,
})
2012-09-24 22:18:17 +02:00
Move.write([m for m in shipment.inventory_moves
2012-11-28 22:35:33 +01:00
if (m.state not in ('assigned', 'done', 'cancel')
and ((m.planned_date or datetime.date.max)
< (inventory_date or datetime.date.max)))], {
'planned_date': inventory_date,
})
2014-09-17 21:45:58 +02:00
def get_origins(self, name):
2018-06-26 19:11:55 +02:00
return ', '.join(set(filter(None,
(m.origin_name for m in self.incoming_moves))))
2013-09-19 17:34:23 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2012-09-24 22:18:17 +02:00
pool = Pool()
Sequence = pool.get('ir.sequence')
Config = pool.get('stock.configuration')
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
2012-09-24 22:18:17 +02:00
config = Config(1)
2013-01-06 22:18:13 +01:00
for values in vlist:
if values.get('number') is None:
values['number'] = Sequence.get_id(config.shipment_in_sequence)
2013-01-06 22:18:13 +01:00
shipments = super(ShipmentIn, cls).create(vlist)
cls._set_move_planned_date(shipments)
return shipments
2012-09-24 22:18:17 +02:00
@classmethod
2013-12-19 14:45:31 +01:00
def write(cls, *args):
super(ShipmentIn, cls).write(*args)
cls._set_move_planned_date(sum(args[::2], []))
2008-04-17 02:31:55 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def copy(cls, shipments, default=None):
2008-11-04 23:32:11 +01:00
if default is None:
default = {}
2018-09-01 01:02:04 +02:00
else:
default = default.copy()
default.setdefault('inventory_moves', None)
default.setdefault('incoming_moves', None)
default.setdefault('number', None)
default.setdefault('received_by', None)
default.setdefault('done_by', None)
2012-09-24 22:18:17 +02:00
return super(ShipmentIn, cls).copy(shipments, default=default)
2008-11-04 23:32:11 +01:00
2019-04-11 18:54:55 +02:00
def _get_inventory_move(self, incoming_move):
'Return inventory move for the incoming move'
2013-03-12 10:35:12 +01:00
pool = Pool()
Move = pool.get('stock.move')
if incoming_move.quantity <= 0.0:
return None
2013-03-12 10:35:12 +01:00
move = Move()
move.product = incoming_move.product
move.uom = incoming_move.uom
move.quantity = incoming_move.quantity
move.from_location = incoming_move.to_location
2019-04-11 18:54:55 +02:00
move.to_location = self.warehouse_storage
2013-03-12 10:35:12 +01:00
move.state = Move.default_state()
# Product will be considered in stock only when the inventory
# move will be made:
2013-03-12 10:35:12 +01:00
move.planned_date = None
move.company = incoming_move.company
2019-07-08 17:18:14 +02:00
move.origin = incoming_move
2013-03-12 10:35:12 +01:00
return move
2012-09-24 22:18:17 +02:00
@classmethod
def create_inventory_moves(cls, shipments):
2012-03-25 00:18:19 +01:00
for shipment in shipments:
2018-03-24 12:01:58 +01:00
if shipment.warehouse_storage == shipment.warehouse_input:
# Do not create inventory moves
continue
2013-03-12 10:35:12 +01:00
# Use moves instead of inventory_moves because save reset before
# adding new records and as set_inventory_moves is just a proxy to
# moves, it will reset also the incoming_moves
moves = list(shipment.moves)
2012-03-25 00:18:19 +01:00
for incoming_move in shipment.incoming_moves:
2019-04-11 18:54:55 +02:00
move = shipment._get_inventory_move(incoming_move)
2013-03-12 10:35:12 +01:00
if move:
moves.append(move)
shipment.moves = moves
shipment.save()
2008-04-08 14:27:53 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def delete(cls, shipments):
2013-03-23 15:42:36 +01:00
Move = Pool().get('stock.move')
# Cancel before delete
2012-09-24 22:18:17 +02:00
cls.cancel(shipments)
for shipment in shipments:
if shipment.state != 'cancel':
2018-12-29 14:20:29 +01:00
raise AccessError(
gettext('stock.msg_shipment_delete_cancel',
shipment=shipment.rec_name))
2013-03-23 15:42:36 +01:00
Move.delete([m for s in shipments for m in s.moves])
2012-09-24 22:18:17 +02:00
super(ShipmentIn, cls).delete(shipments)
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('cancel')
2012-09-24 22:18:17 +02:00
def cancel(cls, shipments):
Move = Pool().get('stock.move')
2012-12-27 21:53:07 +01:00
Move.cancel([m for s in shipments
for m in s.incoming_moves + s.inventory_moves])
2012-03-25 00:18:19 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('draft')
2012-09-24 22:18:17 +02:00
def draft(cls, shipments):
Move = Pool().get('stock.move')
2015-10-21 16:48:02 +02:00
Move.draft([m for s in shipments for m in s.incoming_moves
if m.state != 'staging'])
2012-09-24 22:18:17 +02:00
Move.delete([m for s in shipments for m in s.inventory_moves
if m.state in ('draft', 'cancel')])
2012-03-25 00:18:19 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('received')
2017-02-01 21:41:00 +01:00
@set_employee('received_by')
2012-09-24 22:18:17 +02:00
def receive(cls, shipments):
Move = Pool().get('stock.move')
2012-12-27 21:53:07 +01:00
Move.do([m for s in shipments for m in s.incoming_moves])
2012-09-24 22:18:17 +02:00
cls.create_inventory_moves(shipments)
2018-03-24 12:01:58 +01:00
# Set received state to allow done transition
cls.write(shipments, {'state': 'received'})
to_do = [s for s in shipments
if s.warehouse_storage == s.warehouse_input]
if to_do:
cls.done(to_do)
2012-03-25 00:18:19 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('done')
2017-02-01 21:41:00 +01:00
@set_employee('done_by')
2012-09-24 22:18:17 +02:00
def done(cls, shipments):
pool = Pool()
Move = pool.get('stock.move')
Date = pool.get('ir.date')
2012-12-27 21:53:07 +01:00
Move.do([m for s in shipments for m in s.inventory_moves])
cls.write([s for s in shipments if not s.effective_date], {
2012-09-24 22:18:17 +02:00
'effective_date': Date.today(),
2012-03-25 00:18:19 +01:00
})
2008-05-08 17:24:49 +02:00
2008-04-08 14:27:53 +02:00
2019-09-20 11:47:15 +02:00
class ShipmentInReturn(ShipmentMixin, Workflow, ModelSQL, ModelView):
2009-03-20 12:14:13 +01:00
"Supplier Return Shipment"
2012-09-24 22:18:17 +02:00
__name__ = 'stock.shipment.in.return'
_rec_name = 'number'
effective_date = fields.Date('Effective Date',
states={
'readonly': Eval('state').in_(['cancel', 'done']),
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="When the stock was actually returned.")
2010-01-20 11:04:28 +01:00
planned_date = fields.Date('Planned Date',
2011-08-04 16:57:42 +02:00
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2018-12-04 12:25:45 +01:00
}, depends=['state'],
help="When the stock is expected to be returned.")
2011-10-04 18:17:41 +02:00
company = fields.Many2One('company.company', 'Company', required=True,
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2011-10-04 18:17:41 +02:00
},
domain=[
2015-06-28 23:45:31 +02:00
('id', If(Eval('context', {}).contains('company'), '=', '!='),
2013-10-09 18:25:53 +02:00
Eval('context', {}).get('company', -1)),
2011-10-04 18:17:41 +02:00
],
2018-12-04 12:25:45 +01:00
depends=['state'],
help="The company the shipment is associated with.")
number = fields.Char('Number', size=None, select=True, readonly=True,
help="The main identifier for the shipment.")
2012-03-05 10:53:08 +01:00
reference = fields.Char("Reference", size=None, select=True,
2011-08-04 16:57:42 +02:00
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2018-12-04 12:25:45 +01:00
}, depends=['state'],
help="The supplier's identifier for the shipment.")
supplier = fields.Many2One('party.party', 'Supplier',
states={
'readonly': (((Eval('state') != 'draft')
| Eval('moves', [0]))
& Eval('supplier', 0)),
}, required=True,
2018-12-04 12:25:45 +01:00
depends=['state', 'supplier'],
help="The party that supplied the stock.")
delivery_address = fields.Many2One('party.address', 'Delivery Address',
states={
'readonly': Eval('state') != 'draft',
},
domain=[
('party', '=', Eval('supplier'))
],
2018-12-04 12:25:45 +01:00
depends=['state', 'supplier'],
help="Where the stock is sent to.")
2010-01-20 11:04:28 +01:00
from_location = fields.Many2One('stock.location', "From Location",
2011-08-04 16:57:42 +02:00
required=True, states={
2015-06-28 23:45:31 +02:00
'readonly': (Eval('state') != 'draft') | Eval('moves', [0]),
}, domain=[('type', 'in', ['storage', 'view'])],
2018-12-04 12:25:45 +01:00
depends=['state'],
help="Where the stock is moved from.")
2009-01-27 15:15:01 +01:00
to_location = fields.Many2One('stock.location', "To Location",
2011-08-04 16:57:42 +02:00
required=True, states={
2015-06-28 23:45:31 +02:00
'readonly': (Eval('state') != 'draft') | Eval('moves', [0]),
2011-08-04 16:57:42 +02:00
}, domain=[('type', '=', 'supplier')],
2018-12-04 12:25:45 +01:00
depends=['state'],
help="Where the stock is moved to.")
2013-03-23 15:42:36 +01:00
moves = fields.One2Many('stock.move', 'shipment', 'Moves',
2010-01-20 11:04:28 +01:00
states={
2015-06-28 23:45:31 +02:00
'readonly': (((Eval('state') != 'draft') | ~Eval('from_location'))
& Eval('to_location')),
2011-08-04 16:57:42 +02:00
},
domain=[
If(Eval('state') == 'draft', [
('from_location', '=', Eval('from_location')),
('to_location', '=', Eval('to_location')),
],
If(~Eval('state').in_(['done', 'cancel']), [
('from_location', 'child_of',
[Eval('from_location', -1)], 'parent'),
('to_location', 'child_of',
[Eval('to_location', -1)], 'parent'),
],
[])),
('company', '=', Eval('company')),
],
2018-12-04 12:25:45 +01:00
depends=['state', 'from_location', 'to_location', 'company'],
help="The moves that return the stock to the supplier.")
2013-09-19 17:34:23 +02:00
origins = fields.Function(fields.Char('Origins'), 'get_origins')
2017-02-01 21:41:00 +01:00
assigned_by = employee_field("Assigned By")
done_by = employee_field("Done By")
2009-01-27 15:15:01 +01:00
state = fields.Selection([
('draft', 'Draft'),
2009-03-20 13:09:19 +01:00
('cancel', 'Canceled'),
2009-01-27 15:15:01 +01:00
('assigned', 'Assigned'),
('waiting', 'Waiting'),
('done', 'Done'),
2018-12-04 12:25:45 +01:00
], 'State', readonly=True,
help="The current state of the shipment.")
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def __setup__(cls):
super(ShipmentInReturn, cls).__setup__()
cls._order[0] = ('id', 'DESC')
cls._transitions |= set((
2012-03-25 00:18:19 +01:00
('draft', 'waiting'),
('waiting', 'assigned'),
('waiting', 'draft'),
('assigned', 'done'),
('assigned', 'waiting'),
('draft', 'cancel'),
('waiting', 'cancel'),
('assigned', 'cancel'),
('cancel', 'draft'),
))
2012-09-24 22:18:17 +02:00
cls._buttons.update({
2012-03-25 00:18:19 +01:00
'cancel': {
'invisible': Eval('state').in_(['cancel', 'done']),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'draft': {
'invisible': ~Eval('state').in_(['waiting', 'cancel']),
2018-08-19 20:02:58 +02:00
'icon': If(Eval('state') == 'cancel',
'tryton-undo',
If(Eval('state') == 'waiting',
'tryton-back',
'tryton-forward')),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'wait': {
'invisible': ~Eval('state').in_(['assigned', 'draft']),
'icon': If(Eval('state') == 'assigned',
2018-08-19 20:02:58 +02:00
'tryton-back', 'tryton-forward'),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'done': {
'invisible': Eval('state') != 'assigned',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
2012-09-10 15:28:38 +02:00
'assign_wizard': {
'invisible': Eval('state') != 'waiting',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-09-10 15:28:38 +02:00
},
2012-03-25 00:18:19 +01:00
'assign_try': {},
'assign_force': {},
})
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def __register__(cls, module_name):
2018-07-24 22:01:08 +02:00
table = cls.__table_handler__(module_name)
2011-10-04 18:17:41 +02:00
# Migration from 3.8: rename code into number
if table.column_exist('code'):
table.column_rename('code', 'number')
2012-09-24 22:18:17 +02:00
super(ShipmentInReturn, cls).__register__(module_name)
# Add index on create_date
2018-07-24 22:01:08 +02:00
table = cls.__table_handler__(module_name)
table.index_action('create_date', action='add')
2012-09-24 22:18:17 +02:00
@staticmethod
def default_state():
2012-03-25 00:18:19 +01:00
return 'draft'
2012-09-24 22:18:17 +02:00
@staticmethod
def default_company():
2012-03-27 10:44:04 +02:00
return Transaction().context.get('company')
2009-01-27 15:15:01 +01:00
@fields.depends('supplier')
def on_change_supplier(self):
if self.supplier:
self.delivery_address = self.supplier.address_get('delivery')
self.to_location = self.supplier.supplier_location
2012-09-24 22:18:17 +02:00
@property
def _move_planned_date(self):
'''
Return the planned date for the moves
'''
2012-09-24 22:18:17 +02:00
return self.planned_date
2012-09-24 22:18:17 +02:00
@classmethod
def _set_move_planned_date(cls, shipments):
'''
Set planned date of moves for the shipments
'''
2012-09-24 22:18:17 +02:00
Move = Pool().get('stock.move')
for shipment in shipments:
Move.write([m for m in shipment.moves
2012-11-28 22:35:33 +01:00
if (m.state not in ('assigned', 'done', 'cancel')
and m.planned_date != shipment._move_planned_date)], {
2012-09-24 22:18:17 +02:00
'planned_date': shipment._move_planned_date,
})
2014-09-17 21:45:58 +02:00
def get_origins(self, name):
2018-06-26 19:11:55 +02:00
return ', '.join(set(filter(None,
2014-09-17 21:45:58 +02:00
(m.origin_name for m in self.moves))))
2013-09-19 17:34:23 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2012-09-24 22:18:17 +02:00
pool = Pool()
Sequence = pool.get('ir.sequence')
Config = pool.get('stock.configuration')
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
2012-09-24 22:18:17 +02:00
config = Config(1)
2013-01-06 22:18:13 +01:00
for values in vlist:
if values.get('number') is None:
values['number'] = Sequence.get_id(
config.shipment_in_return_sequence.id)
2013-01-06 22:18:13 +01:00
shipments = super(ShipmentInReturn, cls).create(vlist)
cls._set_move_planned_date(shipments)
return shipments
2012-09-24 22:18:17 +02:00
@classmethod
2013-12-19 14:45:31 +01:00
def write(cls, *args):
super(ShipmentInReturn, cls).write(*args)
cls._set_move_planned_date(sum(args[::2], []))
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def delete(cls, shipments):
2013-03-23 15:42:36 +01:00
Move = Pool().get('stock.move')
# Cancel before delete
2012-09-24 22:18:17 +02:00
cls.cancel(shipments)
for shipment in shipments:
if shipment.state != 'cancel':
2018-12-29 14:20:29 +01:00
raise AccessError(
gettext('stock.msg_shipment_delete_cancel',
shipment=shipment.rec_name))
2013-03-23 15:42:36 +01:00
Move.delete([m for s in shipments for m in s.moves])
2012-09-24 22:18:17 +02:00
super(ShipmentInReturn, cls).delete(shipments)
2017-01-25 23:41:54 +01:00
@classmethod
def copy(cls, shipments, default=None):
if default is None:
default = {}
2018-09-01 01:02:04 +02:00
else:
default = default.copy()
default.setdefault('number', None)
default.setdefault('assigned_by', None)
default.setdefault('done_by', None)
2017-01-25 23:41:54 +01:00
return super(ShipmentInReturn, cls).copy(shipments, default=default)
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('draft')
2012-09-24 22:18:17 +02:00
def draft(cls, shipments):
Move = Pool().get('stock.move')
2015-10-21 16:48:02 +02:00
Move.draft([m for s in shipments for m in s.moves
if m.state != 'staging'])
for shipment in shipments:
Move.write([m for m in shipment.moves
if m.state != 'done'], {
'from_location': shipment.from_location.id,
'to_location': shipment.to_location.id,
'planned_date': shipment.planned_date,
})
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('waiting')
2012-09-24 22:18:17 +02:00
def wait(cls, shipments):
Move = Pool().get('stock.move')
2012-03-25 00:18:19 +01:00
for shipment in shipments:
2012-12-27 21:53:07 +01:00
Move.draft([m for m in shipment.moves])
cls._set_move_planned_date(shipments)
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@Workflow.transition('assigned')
2017-02-01 21:41:00 +01:00
@set_employee('assigned_by')
2012-09-24 22:18:17 +02:00
def assign(cls, shipments):
Move = Pool().get('stock.move')
2012-12-27 21:53:07 +01:00
Move.assign([m for s in shipments for m in s.moves])
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('done')
2017-02-01 21:41:00 +01:00
@set_employee('done_by')
2012-09-24 22:18:17 +02:00
def done(cls, shipments):
pool = Pool()
Move = pool.get('stock.move')
Date = pool.get('ir.date')
2012-12-27 21:53:07 +01:00
Move.do([m for s in shipments for m in s.moves])
cls.write([s for s in shipments if not s.effective_date], {
2012-09-24 22:18:17 +02:00
'effective_date': Date.today(),
2012-03-25 00:18:19 +01:00
})
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('cancel')
2012-09-24 22:18:17 +02:00
def cancel(cls, shipments):
Move = Pool().get('stock.move')
2012-12-27 21:53:07 +01:00
Move.cancel([m for s in shipments for m in s.moves])
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-09-10 15:28:38 +02:00
@ModelView.button_action('stock.wizard_shipment_in_return_assign')
2012-09-24 22:18:17 +02:00
def assign_wizard(cls, shipments):
2012-09-10 15:28:38 +02:00
pass
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
def assign_try(cls, shipments, with_childs=None):
2011-07-03 13:19:14 +02:00
pool = Pool()
2012-09-24 22:18:17 +02:00
Move = pool.get('stock.move')
to_assign = defaultdict(list)
for shipment in shipments:
location_type = shipment.from_location.type
for move in shipment.moves:
if move.assignation_required:
to_assign[location_type].append(move)
success = True
2018-06-26 19:11:55 +02:00
for location_type, moves in to_assign.items():
if with_childs is None:
_with_childs = location_type == 'view'
elif not with_childs and location_type == 'view':
_with_childs = True
else:
_with_childs = with_childs
if not Move.assign_try(moves, with_childs=_with_childs):
success = False
if success:
cls.assign(shipments)
return success
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
2012-09-24 22:18:17 +02:00
def assign_force(cls, shipments):
cls.assign(shipments)
2009-01-27 15:15:01 +01:00
2019-09-20 11:47:15 +02:00
class ShipmentOut(ShipmentMixin, Workflow, ModelSQL, ModelView):
2009-03-20 12:14:13 +01:00
"Customer Shipment"
2012-09-24 22:18:17 +02:00
__name__ = 'stock.shipment.out'
_rec_name = 'number'
effective_date = fields.Date('Effective Date',
states={
'readonly': Eval('state').in_(['cancel', 'done']),
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="When the stock was actually sent.")
2008-11-14 01:14:16 +01:00
planned_date = fields.Date('Planned Date',
2011-08-04 16:57:42 +02:00
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2018-12-04 12:25:45 +01:00
}, depends=['state'],
help="When the stock is expected to be sent.")
2011-10-04 18:17:41 +02:00
company = fields.Many2One('company.company', 'Company', required=True,
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2011-10-04 18:17:41 +02:00
},
domain=[
2015-06-28 23:45:31 +02:00
('id', If(Eval('context', {}).contains('company'), '=', '!='),
2013-10-09 18:25:53 +02:00
Eval('context', {}).get('company', -1)),
2011-10-04 18:17:41 +02:00
],
2018-12-04 12:25:45 +01:00
depends=['state'],
help="The company the shipment is associated with.")
2008-10-22 20:13:23 +02:00
customer = fields.Many2One('party.party', 'Customer', required=True,
2011-08-04 16:57:42 +02:00
states={
2015-06-28 23:45:31 +02:00
'readonly': ((Eval('state') != 'draft')
| Eval('outgoing_moves', [0])),
2014-01-26 14:29:37 +01:00
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="The party that purchased the stock.")
customer_location = fields.Function(fields.Many2One('stock.location',
2014-01-26 14:29:37 +01:00
'Customer Location'), 'on_change_with_customer_location')
2011-08-04 16:57:42 +02:00
delivery_address = fields.Many2One('party.address',
'Delivery Address', required=True,
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2011-08-04 16:57:42 +02:00
}, domain=[('party', '=', Eval('customer'))],
2018-12-04 12:25:45 +01:00
depends=['state', 'customer'],
help="Where the stock is sent to.")
2012-03-05 10:53:08 +01:00
reference = fields.Char("Reference", size=None, select=True,
2011-08-04 16:57:42 +02:00
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2018-12-04 12:25:45 +01:00
}, depends=['state'],
help="The customer's identifier for the shipment.")
2011-08-04 16:57:42 +02:00
warehouse = fields.Many2One('stock.location', "Warehouse", required=True,
states={
2015-06-28 23:45:31 +02:00
'readonly': ((Eval('state') != 'draft')
| Eval('outgoing_moves', [0]) | Eval('inventory_moves', [0])),
2011-08-04 16:57:42 +02:00
}, domain=[('type', '=', 'warehouse')],
2018-12-04 12:25:45 +01:00
depends=['state'],
help="Where the stock is sent from.")
warehouse_storage = fields.Function(fields.Many2One('stock.location',
2014-01-26 14:29:37 +01:00
'Warehouse Storage'), 'on_change_with_warehouse_storage')
warehouse_output = fields.Function(fields.Many2One('stock.location',
2014-01-26 14:29:37 +01:00
'Warehouse Output'), 'on_change_with_warehouse_output')
2013-05-27 18:15:02 +02:00
outgoing_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
'Outgoing Moves',
domain=[
('from_location', '=', Eval('warehouse_output')),
('to_location', '=', Eval('customer_location')),
('company', '=', Eval('company')),
],
states={
'readonly': ((Eval('state').in_(
['waiting', 'packed', 'done', 'cancel']))
| ~Eval('warehouse') | ~Eval('customer')),
},
depends=['state', 'warehouse', 'customer', 'warehouse_output',
2018-12-04 12:25:45 +01:00
'customer_location', 'company'],
help="The moves that send the stock to the customer."),
2011-08-04 16:57:42 +02:00
'get_outgoing_moves', setter='set_outgoing_moves')
2013-05-27 18:15:02 +02:00
inventory_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
'Inventory Moves',
domain=[
If(Eval('state').in_(['waiting']),
('from_location', 'child_of',
[Eval('warehouse_storage', -1)], 'parent'),
()),
('to_location', '=', Eval('warehouse_output')),
('company', '=', Eval('company')),
],
states={
2012-03-28 18:00:35 +02:00
'readonly': Eval('state').in_(
['draft', 'assigned', 'packed', 'done', 'cancel']),
'invisible': (
Eval('warehouse_storage') == Eval('warehouse_output')),
},
depends=['state', 'warehouse', 'warehouse_storage',
2018-12-04 12:25:45 +01:00
'warehouse_output', 'company'],
help="The moves that pick the stock from the storage area."),
2011-08-04 16:57:42 +02:00
'get_inventory_moves', setter='set_inventory_moves')
2013-03-23 15:42:36 +01:00
moves = fields.One2Many('stock.move', 'shipment', 'Moves',
2011-10-04 18:17:41 +02:00
domain=[('company', '=', Eval('company'))], depends=['company'],
readonly=True)
2013-09-19 17:34:23 +02:00
origins = fields.Function(fields.Char('Origins'), 'get_origins')
2018-12-04 12:25:45 +01:00
number = fields.Char('Number', size=None, select=True, readonly=True,
help="The main identifier for the shipment.")
2017-02-01 21:41:00 +01:00
assigned_by = employee_field("Assigned By")
packed_by = employee_field("Packed By")
done_by = employee_field("Done By")
state = fields.Selection([
('draft', 'Draft'),
('done', 'Done'),
2009-03-20 13:09:19 +01:00
('cancel', 'Canceled'),
('assigned', 'Assigned'),
('packed', 'Packed'),
('waiting', 'Waiting'),
2018-12-04 12:25:45 +01:00
], 'State', readonly=True,
help="The current state of the shipment.")
2008-04-08 14:27:53 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def __setup__(cls):
super(ShipmentOut, cls).__setup__()
cls._order[0] = ('id', 'DESC')
cls._transitions |= set((
2012-03-25 00:18:19 +01:00
('draft', 'waiting'),
('waiting', 'assigned'),
('waiting', 'packed'),
2012-03-25 00:18:19 +01:00
('assigned', 'packed'),
('packed', 'done'),
('assigned', 'waiting'),
('waiting', 'waiting'),
('waiting', 'draft'),
('draft', 'cancel'),
('waiting', 'cancel'),
('assigned', 'cancel'),
('packed', 'cancel'),
('cancel', 'draft'),
2012-03-25 00:18:19 +01:00
))
2012-09-24 22:18:17 +02:00
cls._buttons.update({
2012-03-25 00:18:19 +01:00
'cancel': {
'invisible': Eval('state').in_(['cancel', 'done']),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'draft': {
'invisible': ~Eval('state').in_(['waiting', 'cancel']),
2018-08-19 20:02:58 +02:00
'icon': If(Eval('state') == 'cancel',
'tryton-undo',
If(Eval('state') == 'waiting',
'tryton-back',
'tryton-forward')),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'wait': {
'invisible': ~Eval('state').in_(['assigned', 'waiting',
'draft']),
'icon': If(Eval('state') == 'assigned',
2018-08-19 20:02:58 +02:00
'tryton-back',
2012-03-25 00:18:19 +01:00
If(Eval('state') == 'waiting',
'tryton-clear',
2018-08-19 20:02:58 +02:00
'tryton-forward')),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'pack': {
'invisible': If(Eval('warehouse_storage')
== Eval('warehouse_output'),
~Eval('state').in_(['assigned', 'waiting']),
Eval('state') != 'assigned'),
2018-02-19 11:41:53 +01:00
'depends': ['state', 'warehouse_storage',
'warehouse_output'],
2012-03-25 00:18:19 +01:00
},
'done': {
'invisible': Eval('state') != 'packed',
},
2012-09-10 15:28:38 +02:00
'assign_wizard': {
'invisible': ((Eval('state') != 'waiting')
| (Eval('warehouse_storage')
== Eval('warehouse_output'))),
2012-09-10 15:28:38 +02:00
},
2012-03-25 00:18:19 +01:00
'assign_try': {},
'assign_force': {},
})
2008-04-08 14:27:53 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def __register__(cls, module_name):
2018-07-24 22:01:08 +02:00
table = cls.__table_handler__(module_name)
2011-10-04 18:17:41 +02:00
# Migration from 3.8: rename code into number
if table.column_exist('code'):
table.column_rename('code', 'number')
2012-09-24 22:18:17 +02:00
super(ShipmentOut, cls).__register__(module_name)
2009-02-02 13:13:06 +01:00
# Add index on create_date
table.index_action('create_date', action='add')
2012-09-24 22:18:17 +02:00
@staticmethod
def default_state():
2008-04-08 14:27:53 +02:00
return 'draft'
2012-09-24 22:18:17 +02:00
@classmethod
def default_warehouse(cls):
Location = Pool().get('stock.location')
return Location.get_default_warehouse()
2008-11-11 23:31:30 +01:00
2012-09-24 22:18:17 +02:00
@staticmethod
def default_company():
2012-03-27 10:44:04 +02:00
return Transaction().context.get('company')
2011-10-04 18:17:41 +02:00
2014-01-26 14:29:37 +01:00
@fields.depends('customer')
2012-09-24 22:18:17 +02:00
def on_change_customer(self):
2014-10-29 20:40:25 +01:00
self.delivery_address = None
2013-12-12 13:50:05 +01:00
if self.customer:
2014-10-29 20:40:25 +01:00
self.delivery_address = self.customer.address_get(type='delivery')
2008-05-16 01:46:44 +02:00
2014-01-26 14:29:37 +01:00
@fields.depends('customer')
2012-09-24 22:18:17 +02:00
def on_change_with_customer_location(self, name=None):
if self.customer:
return self.customer.customer_location.id
2012-09-24 22:18:17 +02:00
@classmethod
def default_warehouse_storage(cls):
warehouse = cls.default_warehouse()
if warehouse:
2012-09-24 22:18:17 +02:00
return cls(warehouse=warehouse).on_change_with_warehouse_storage()
2014-01-26 14:29:37 +01:00
@fields.depends('warehouse')
2012-09-24 22:18:17 +02:00
def on_change_with_warehouse_storage(self, name=None):
if self.warehouse:
if self.warehouse.picking_location:
location = self.warehouse.picking_location
else:
location = self.warehouse.storage_location
return location.id
2012-09-24 22:18:17 +02:00
@classmethod
def default_warehouse_output(cls):
warehouse = cls.default_warehouse()
if warehouse:
2012-09-24 22:18:17 +02:00
return cls(warehouse=warehouse).on_change_with_warehouse_output()
2014-01-26 14:29:37 +01:00
@fields.depends('warehouse')
2012-09-24 22:18:17 +02:00
def on_change_with_warehouse_output(self, name=None):
if self.warehouse:
return self.warehouse.output_location.id
2012-09-24 22:18:17 +02:00
def get_outgoing_moves(self, name):
moves = []
for move in self.moves:
if move.from_location == self.warehouse_output:
2012-09-24 22:18:17 +02:00
moves.append(move.id)
return moves
2012-09-24 22:18:17 +02:00
@classmethod
def set_outgoing_moves(cls, shipments, name, value):
2008-12-04 18:34:30 +01:00
if not value:
return
2012-09-24 22:18:17 +02:00
cls.write(shipments, {
'moves': value,
})
2012-09-24 22:18:17 +02:00
def get_inventory_moves(self, name):
moves = []
for move in self.moves:
if move.to_location == self.warehouse_output:
2012-09-24 22:18:17 +02:00
moves.append(move.id)
return moves
2012-09-24 22:18:17 +02:00
@classmethod
def set_inventory_moves(cls, shipments, name, value):
2008-12-04 18:34:30 +01:00
if not value:
return
2012-09-24 22:18:17 +02:00
cls.write(shipments, {
'moves': value,
})
2014-09-17 21:45:58 +02:00
def get_origins(self, name):
2018-06-26 19:11:55 +02:00
return ', '.join(set(filter(None,
(m.origin_name for m in self.outgoing_moves))))
2014-12-02 12:56:04 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('draft')
2012-09-24 22:18:17 +02:00
def draft(cls, shipments):
Move = Pool().get('stock.move')
2012-12-27 21:53:07 +01:00
Move.draft([m for s in shipments
2015-10-21 16:48:02 +02:00
for m in s.inventory_moves + s.outgoing_moves
if m.state != 'staging'])
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('waiting')
2012-09-24 22:18:17 +02:00
def wait(cls, shipments):
2008-10-06 12:18:36 +02:00
"""
Complete inventory moves to match the products and quantities
that are in the outgoing moves.
"""
2012-09-24 22:18:17 +02:00
Move = Pool().get('stock.move')
2012-12-27 21:53:07 +01:00
Move.draft([m for s in shipments for m in s.inventory_moves])
2012-09-24 22:18:17 +02:00
Move.delete([m for s in shipments for m in s.inventory_moves
if m.state in ('draft', 'cancel')])
2013-01-06 22:18:13 +01:00
to_create = []
2012-03-25 00:18:19 +01:00
for shipment in shipments:
if shipment.warehouse_storage == shipment.warehouse_output:
# Do not create inventory moves
continue
2012-03-25 00:18:19 +01:00
for move in shipment.outgoing_moves:
if move.state in ('cancel', 'done'):
continue
to_create.append(shipment._get_inventory_move(move))
2013-01-06 22:18:13 +01:00
if to_create:
2014-11-01 11:54:45 +01:00
Move.save(to_create)
def _get_inventory_move(self, move):
'Return inventory move for the outgoing move'
pool = Pool()
Move = pool.get('stock.move')
return Move(
from_location=self.warehouse_storage,
to_location=move.from_location,
product=move.product,
uom=move.uom,
quantity=move.quantity,
shipment=self,
planned_date=move.planned_date,
company=move.company,
currency=move.currency,
unit_price=move.unit_price,
2019-07-08 17:18:14 +02:00
origin=move,
2015-01-02 15:40:11 +01:00
state='staging' if move.state == 'staging' else 'draft',
)
2012-03-25 00:18:19 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@Workflow.transition('assigned')
2017-02-01 21:41:00 +01:00
@set_employee('assigned_by')
2012-09-24 22:18:17 +02:00
def assign(cls, shipments):
2019-08-18 19:22:43 +02:00
cls._sync_inventory_to_outgoing(shipments, quantity=False)
2012-03-25 00:18:19 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('packed')
2017-02-01 21:41:00 +01:00
@set_employee('packed_by')
2012-09-24 22:18:17 +02:00
def pack(cls, shipments):
pool = Pool()
Move = pool.get('stock.move')
2012-12-27 21:53:07 +01:00
Move.do([m for s in shipments for m in s.inventory_moves])
2019-08-18 19:22:43 +02:00
cls._sync_inventory_to_outgoing(shipments, quantity=True)
Move.assign([m for s in shipments for m in s.outgoing_moves])
2012-03-25 00:18:19 +01:00
2019-08-18 19:22:43 +02:00
def _sync_move_key(self, move):
return (
('product', move.product),
('uom', move.uom),
)
2019-08-18 19:22:43 +02:00
def _sync_outgoing_move(self, template=None):
pool = Pool()
Move = pool.get('stock.move')
move = Move(
from_location=self.warehouse_output,
to_location=self.customer_location,
quantity=0,
shipment=self,
planned_date=self.planned_date,
company=self.company,
)
if template:
move.origin = template.origin
move.currency = template.currency
move.unit_price = template.unit_price
else:
move.currency = self.company.currency
move.unit_price = 0
return move
@classmethod
2019-08-18 19:22:43 +02:00
def _sync_inventory_to_outgoing(cls, shipments, quantity=True):
pool = Pool()
Move = pool.get('stock.move')
Uom = pool.get('product.uom')
2019-08-18 19:22:43 +02:00
def active(move):
return move.state != 'cancel'
moves = []
2012-03-25 00:18:19 +01:00
for shipment in shipments:
if shipment.warehouse_storage == shipment.warehouse_output:
2019-08-18 19:22:43 +02:00
# Do not have inventory moves
continue
2012-03-25 00:18:19 +01:00
2019-08-18 19:22:43 +02:00
outgoing_moves = {m: m for m in shipment.outgoing_moves}
inventory_qty = defaultdict(lambda: defaultdict(float))
for move in filter(active, shipment.outgoing_moves):
key = shipment._sync_move_key(move)
inventory_qty[move][key] = 0
for move in filter(active, shipment.inventory_moves):
key = shipment._sync_move_key(move)
outgoing_move = outgoing_moves.get(move.origin)
qty_default_uom = Uom.compute_qty(
move.uom, move.quantity,
2012-04-03 20:07:43 +02:00
move.product.default_uom, round=False)
2019-08-18 19:22:43 +02:00
inventory_qty[outgoing_move][key] += qty_default_uom
for outgoing_move in inventory_qty:
if outgoing_move:
outgoing_key = shipment._sync_move_key(outgoing_move)
for key, qty in inventory_qty[outgoing_move].items():
if not quantity and outgoing_move:
# Do not create outgoing move with origin
# to allow to reset to draft
2012-03-25 00:18:19 +01:00
continue
2019-08-18 19:22:43 +02:00
if outgoing_move and key == outgoing_key:
move = outgoing_move
2012-03-25 00:18:19 +01:00
else:
2019-08-18 19:22:43 +02:00
move = shipment._sync_outgoing_move(outgoing_move)
for name, value in key:
setattr(move, name, value)
qty = Uom.compute_qty(
move.product.default_uom, qty,
move.uom)
if quantity and move.quantity != qty:
move.quantity = qty
moves.append(move)
Move.save(moves)
2012-03-25 00:18:19 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('done')
2017-02-01 21:41:00 +01:00
@set_employee('done_by')
2012-09-24 22:18:17 +02:00
def done(cls, shipments):
pool = Pool()
Move = pool.get('stock.move')
Date = pool.get('ir.date')
2012-03-25 00:18:19 +01:00
2012-12-27 21:53:07 +01:00
Move.do([m for s in shipments for m in s.outgoing_moves])
cls.write([s for s in shipments if not s.effective_date], {
2012-09-24 22:18:17 +02:00
'effective_date': Date.today(),
2012-03-25 00:18:19 +01:00
})
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('cancel')
2012-09-24 22:18:17 +02:00
def cancel(cls, shipments):
Move = Pool().get('stock.move')
2012-12-27 21:53:07 +01:00
Move.cancel([m for s in shipments
for m in s.outgoing_moves + s.inventory_moves])
2008-04-17 02:31:55 +02:00
2012-09-24 22:18:17 +02:00
@property
def _move_planned_date(self):
'''
Return the planned date for outgoing moves and inventory moves
'''
2012-09-24 22:18:17 +02:00
return self.planned_date, self.planned_date
2012-09-24 22:18:17 +02:00
@classmethod
2018-09-19 00:15:59 +02:00
def _set_move_planned_date(cls, shipments):
'''
Set planned date of moves for the shipments
'''
2012-09-24 22:18:17 +02:00
Move = Pool().get('stock.move')
to_write = []
2012-09-24 22:18:17 +02:00
for shipment in shipments:
outgoing_date, inventory_date = shipment._move_planned_date
out_moves_to_write = [x for x in shipment.outgoing_moves
2012-11-28 22:35:33 +01:00
if (x.state not in ('assigned', 'done', 'cancel')
and x.planned_date != outgoing_date)]
if out_moves_to_write:
to_write.extend((out_moves_to_write, {
'planned_date': outgoing_date,
}))
inv_moves_to_write = [x for x in shipment.inventory_moves
2012-11-28 22:35:33 +01:00
if (x.state not in ('assigned', 'done', 'cancel')
and x.planned_date != inventory_date)]
if inv_moves_to_write:
to_write.extend((inv_moves_to_write, {
'planned_date': inventory_date,
}))
if to_write:
Move.write(*to_write)
2012-09-24 22:18:17 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2012-09-24 22:18:17 +02:00
pool = Pool()
Sequence = pool.get('ir.sequence')
Config = pool.get('stock.configuration')
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
2012-09-24 22:18:17 +02:00
config = Config(1)
2013-01-06 22:18:13 +01:00
for values in vlist:
if values.get('number') is None:
values['number'] = Sequence.get_id(
config.shipment_out_sequence.id)
2013-01-06 22:18:13 +01:00
shipments = super(ShipmentOut, cls).create(vlist)
cls._set_move_planned_date(shipments)
return shipments
2012-09-24 22:18:17 +02:00
@classmethod
2013-12-19 14:45:31 +01:00
def write(cls, *args):
super(ShipmentOut, cls).write(*args)
cls._set_move_planned_date(sum(args[::2], []))
2008-12-05 13:54:13 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def copy(cls, shipments, default=None):
2008-11-04 23:32:11 +01:00
if default is None:
default = {}
2018-09-01 01:02:04 +02:00
else:
default = default.copy()
default.setdefault('inventory_moves', None)
default.setdefault('outgoing_moves', None)
default.setdefault('number', None)
default.setdefault('assigned_by', None)
default.setdefault('packed_by', None)
default.setdefault('done_by', None)
2012-09-24 22:18:17 +02:00
return super(ShipmentOut, cls).copy(shipments, default=default)
2008-11-04 23:32:11 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def delete(cls, shipments):
2013-03-23 15:42:36 +01:00
Move = Pool().get('stock.move')
# Cancel before delete
2012-09-24 22:18:17 +02:00
cls.cancel(shipments)
for shipment in shipments:
if shipment.state != 'cancel':
2018-12-29 14:20:29 +01:00
raise AccessError(
gettext('stock.msg_shipment_delete_cancel',
shipment=shipment.rec_name))
2013-03-23 15:42:36 +01:00
Move.delete([m for s in shipments for m in s.moves])
2012-09-24 22:18:17 +02:00
super(ShipmentOut, cls).delete(shipments)
2008-04-17 02:31:55 +02:00
2012-09-24 22:18:17 +02:00
@staticmethod
def _location_amount(target_uom, qty_uom, uom_index):
2008-04-22 01:03:40 +02:00
"""
Take a raw list of quantities and uom and convert it to
the target uom.
2008-04-22 01:03:40 +02:00
"""
2012-09-24 22:18:17 +02:00
Uom = Pool().get('product.uom')
res = 0
for uom, qty in qty_uom:
2012-09-24 22:18:17 +02:00
res += Uom.compute_qty(uom_index[uom], qty,
2010-08-19 22:22:44 +02:00
uom_index[target_uom])
2008-04-22 01:03:40 +02:00
return res
2012-09-24 22:18:17 +02:00
@classmethod
2012-09-10 15:28:38 +02:00
@ModelView.button_action('stock.wizard_shipment_out_assign')
2012-09-24 22:18:17 +02:00
def assign_wizard(cls, shipments):
2012-09-10 15:28:38 +02:00
pass
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
2012-09-24 22:18:17 +02:00
def assign_try(cls, shipments):
Move = Pool().get('stock.move')
2017-06-24 10:34:21 +02:00
to_assign = [m for s in shipments for m in s.inventory_moves
if m.assignation_required]
if Move.assign_try(to_assign):
2012-09-24 22:18:17 +02:00
cls.assign(shipments)
2012-03-25 00:18:19 +01:00
return True
else:
return False
2008-04-17 02:31:55 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
2012-09-24 22:18:17 +02:00
def assign_force(cls, shipments):
Move = Pool().get('stock.move')
2012-12-27 21:53:07 +01:00
Move.assign([m for s in shipments for m in s.inventory_moves])
2012-09-24 22:18:17 +02:00
cls.assign(shipments)
2008-05-08 12:49:46 +02:00
2008-11-14 11:12:38 +01:00
2019-09-20 11:47:15 +02:00
class ShipmentOutReturn(ShipmentMixin, Workflow, ModelSQL, ModelView):
2009-03-20 12:14:13 +01:00
"Customer Return Shipment"
2012-09-24 22:18:17 +02:00
__name__ = 'stock.shipment.out.return'
_rec_name = 'number'
effective_date = fields.Date('Effective Date',
states={
'readonly': Eval('state').in_(['cancel', 'done']),
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="When the stock was returned.")
2009-01-27 15:15:01 +01:00
planned_date = fields.Date('Planned Date',
2011-08-04 16:57:42 +02:00
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2018-12-04 12:25:45 +01:00
}, depends=['state'],
help="When the stock is expected to be returned.")
2011-10-04 18:17:41 +02:00
company = fields.Many2One('company.company', 'Company', required=True,
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2011-10-04 18:17:41 +02:00
},
domain=[
2015-06-28 23:45:31 +02:00
('id', If(Eval('context', {}).contains('company'), '=', '!='),
2013-10-09 18:25:53 +02:00
Eval('context', {}).get('company', -1)),
2011-10-04 18:17:41 +02:00
],
2018-12-04 12:25:45 +01:00
depends=['state'],
help="The company the shipment is associated with.")
2011-08-04 16:57:42 +02:00
customer = fields.Many2One('party.party', 'Customer', required=True,
states={
2015-06-28 23:45:31 +02:00
'readonly': ((Eval('state') != 'draft')
| Eval('incoming_moves', [0])),
2014-01-26 14:29:37 +01:00
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="The party that purchased the stock.")
customer_location = fields.Function(fields.Many2One('stock.location',
2014-01-26 14:29:37 +01:00
'Customer Location'), 'on_change_with_customer_location')
2011-08-04 16:57:42 +02:00
delivery_address = fields.Many2One('party.address',
'Delivery Address', required=True,
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2011-08-04 16:57:42 +02:00
}, domain=[('party', '=', Eval('customer'))],
2018-12-04 12:25:45 +01:00
depends=['state', 'customer'],
help="The address the customer can be contacted at.")
2012-03-05 10:53:08 +01:00
reference = fields.Char("Reference", size=None, select=True,
2011-08-04 16:57:42 +02:00
states={
2015-06-28 23:45:31 +02:00
'readonly': Eval('state') != 'draft',
2018-12-04 12:25:45 +01:00
}, depends=['state'],
help="The customer's identifier for the shipment.")
2011-08-04 16:57:42 +02:00
warehouse = fields.Many2One('stock.location', "Warehouse", required=True,
states={
2015-06-28 23:45:31 +02:00
'readonly': ((Eval('state') != 'draft')
| Eval('incoming_moves', [0]) | Eval('inventory_moves', [0])),
2011-08-04 16:57:42 +02:00
}, domain=[('type', '=', 'warehouse')],
2018-12-04 12:25:45 +01:00
depends=['state'],
help="Where the stock is returned.")
warehouse_storage = fields.Function(fields.Many2One('stock.location',
2014-01-26 14:29:37 +01:00
'Warehouse Storage'), 'on_change_with_warehouse_storage')
2012-03-29 12:15:36 +02:00
warehouse_input = fields.Function(fields.Many2One('stock.location',
2014-01-26 14:29:37 +01:00
'Warehouse Input'), 'on_change_with_warehouse_input')
2013-05-27 18:15:02 +02:00
incoming_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
'Incoming Moves',
domain=[
('from_location', '=', Eval('customer_location')),
If(Eval('warehouse_input') == Eval('warehouse_storage'),
('to_location', 'child_of',
[Eval('warehouse_input', -1)], 'parent'),
('to_location', '=', Eval('warehouse_input'))),
('company', '=', Eval('company')),
],
states={
'readonly': ((Eval('state') != 'draft')
| ~Eval('warehouse') | ~Eval('customer')),
},
depends=['state', 'warehouse', 'customer', 'customer_location',
2018-12-04 12:25:45 +01:00
'warehouse_input', 'warehouse_storage', 'company'],
help="The moves that bring the stock into the warehouse."),
2011-08-04 16:57:42 +02:00
'get_incoming_moves', setter='set_incoming_moves')
2013-05-27 18:15:02 +02:00
inventory_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
'Inventory Moves',
domain=[
2012-03-29 12:15:36 +02:00
('from_location', '=', Eval('warehouse_input')),
If(Eval('state').in_(['received']),
('to_location', 'child_of',
[Eval('warehouse_storage', -1)], 'parent'),
()),
('company', '=', Eval('company')),
],
states={
'readonly': Eval('state').in_(['draft', 'cancel', 'done']),
'invisible': (
Eval('warehouse_input') == Eval('warehouse_storage')),
},
2012-03-29 12:15:36 +02:00
depends=['state', 'warehouse', 'warehouse_input',
2018-12-04 12:25:45 +01:00
'warehouse_storage', 'warehouse_input', 'company'],
help="The moves that put the stock away into the storage area."),
2011-08-04 16:57:42 +02:00
'get_inventory_moves', setter='set_inventory_moves')
2013-03-23 15:42:36 +01:00
moves = fields.One2Many('stock.move', 'shipment', 'Moves',
2011-10-04 18:17:41 +02:00
domain=[('company', '=', Eval('company'))], depends=['company'],
readonly=True)
2013-09-19 17:34:23 +02:00
origins = fields.Function(fields.Char('Origins'), 'get_origins')
2018-12-04 12:25:45 +01:00
number = fields.Char('Number', size=None, select=True, readonly=True,
help="The main identifier for the shipment.")
2017-02-01 21:41:00 +01:00
received_by = employee_field("Received By")
done_by = employee_field("Done By")
2009-01-27 15:15:01 +01:00
state = fields.Selection([
('draft', 'Draft'),
('done', 'Done'),
2009-03-20 13:09:19 +01:00
('cancel', 'Canceled'),
2009-01-27 15:15:01 +01:00
('received', 'Received'),
2018-12-04 12:25:45 +01:00
], 'State', readonly=True,
help="The current state of the shipment.")
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def __setup__(cls):
super(ShipmentOutReturn, cls).__setup__()
cls._order[0] = ('id', 'DESC')
cls._transitions |= set((
2012-03-25 00:18:19 +01:00
('draft', 'received'),
('received', 'done'),
2014-08-11 17:12:07 +05:30
('received', 'draft'),
2012-03-25 00:18:19 +01:00
('draft', 'cancel'),
('received', 'cancel'),
2012-03-25 00:18:19 +01:00
('cancel', 'draft'),
))
2012-09-24 22:18:17 +02:00
cls._buttons.update({
2012-03-25 00:18:19 +01:00
'cancel': {
'invisible': Eval('state').in_(['cancel', 'done']),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'draft': {
'invisible': Eval('state') != 'cancel',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'receive': {
'invisible': Eval('state') != 'draft',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'done': {
'invisible': Eval('state') != 'received',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
})
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def __register__(cls, module_name):
2018-07-24 22:01:08 +02:00
table = cls.__table_handler__(module_name)
2011-10-04 18:17:41 +02:00
# Migration from 3.8: rename code into number
if table.column_exist('code'):
table.column_rename('code', 'number')
2012-09-24 22:18:17 +02:00
super(ShipmentOutReturn, cls).__register__(module_name)
2009-01-27 15:15:01 +01:00
# Add index on create_date
2018-07-24 22:01:08 +02:00
table = cls.__table_handler__(module_name)
table.index_action('create_date', action='add')
2012-09-24 22:18:17 +02:00
@staticmethod
def default_state():
2009-01-27 15:15:01 +01:00
return 'draft'
2012-09-24 22:18:17 +02:00
@classmethod
def default_warehouse(cls):
Location = Pool().get('stock.location')
return Location.get_default_warehouse()
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@staticmethod
def default_company():
2012-03-27 10:44:04 +02:00
return Transaction().context.get('company')
2011-10-04 18:17:41 +02:00
2014-01-26 14:29:37 +01:00
@fields.depends('customer')
2012-09-24 22:18:17 +02:00
def on_change_customer(self):
2014-10-29 20:40:25 +01:00
self.delivery_address = None
2013-12-12 13:50:05 +01:00
if self.customer:
2014-10-29 20:40:25 +01:00
self.delivery_address = self.customer.address_get(type='delivery')
2009-01-27 15:15:01 +01:00
2014-01-26 14:29:37 +01:00
@fields.depends('customer')
2012-09-24 22:18:17 +02:00
def on_change_with_customer_location(self, name=None):
if self.customer:
return self.customer.customer_location.id
2012-09-24 22:18:17 +02:00
@classmethod
def default_warehouse_storage(cls):
warehouse = cls.default_warehouse()
if warehouse:
2012-09-24 22:18:17 +02:00
return cls(warehouse=warehouse).on_change_with_warehouse_storage()
2014-01-26 14:29:37 +01:00
@fields.depends('warehouse')
2012-09-24 22:18:17 +02:00
def on_change_with_warehouse_storage(self, name=None):
if self.warehouse:
return self.warehouse.storage_location.id
2012-09-24 22:18:17 +02:00
@classmethod
def default_warehouse_input(cls):
warehouse = cls.default_warehouse()
if warehouse:
2012-09-24 22:18:17 +02:00
return cls(warehouse=warehouse).on_change_with_warehouse_input()
2014-01-26 14:29:37 +01:00
@fields.depends('warehouse')
2012-09-24 22:18:17 +02:00
def on_change_with_warehouse_input(self, name=None):
if self.warehouse:
return self.warehouse.input_location.id
2012-09-24 22:18:17 +02:00
def get_incoming_moves(self, name):
moves = []
if self.warehouse_input == self.warehouse_storage:
return [m.id for m in self.moves]
2012-09-24 22:18:17 +02:00
for move in self.moves:
if move.to_location == self.warehouse_input:
2012-09-24 22:18:17 +02:00
moves.append(move.id)
return moves
2012-09-24 22:18:17 +02:00
@classmethod
def set_incoming_moves(cls, shipments, name, value):
2009-01-27 15:15:01 +01:00
if not value:
return
2012-09-24 22:18:17 +02:00
cls.write(shipments, {
'moves': value,
})
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
def get_inventory_moves(self, name):
moves = []
for move in self.moves:
if move.from_location == self.warehouse_input:
2012-09-24 22:18:17 +02:00
moves.append(move.id)
return moves
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def set_inventory_moves(cls, shipments, name, value):
2009-01-27 15:15:01 +01:00
if not value:
return
2012-09-24 22:18:17 +02:00
cls.write(shipments, {
'moves': value,
})
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
def _get_move_planned_date(self):
'''
Return the planned date for incoming moves and inventory moves
'''
2012-09-24 22:18:17 +02:00
return self.planned_date, self.planned_date
2012-09-24 22:18:17 +02:00
@classmethod
def _set_move_planned_date(cls, shipments):
'''
Set planned date of moves for the shipments
'''
2012-09-24 22:18:17 +02:00
Move = Pool().get('stock.move')
for shipment in shipments:
dates = shipment._get_move_planned_date()
incoming_date, inventory_date = dates
2012-09-24 22:18:17 +02:00
Move.write([x for x in shipment.incoming_moves
2012-11-28 22:35:33 +01:00
if (x.state not in ('assigned', 'done', 'cancel')
and x.planned_date != incoming_date)], {
'planned_date': incoming_date,
})
2012-09-24 22:18:17 +02:00
Move.write([x for x in shipment.inventory_moves
2012-11-28 22:35:33 +01:00
if (x.state not in ('assigned', 'done', 'cancel')
and x.planned_date != inventory_date)], {
'planned_date': inventory_date,
})
2014-09-17 21:45:58 +02:00
def get_origins(self, name):
2018-06-26 19:11:55 +02:00
return ', '.join(set(filter(None,
(m.origin_name for m in self.incoming_moves))))
2013-09-19 17:34:23 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2012-09-24 22:18:17 +02:00
pool = Pool()
Sequence = pool.get('ir.sequence')
Config = pool.get('stock.configuration')
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
2012-09-24 22:18:17 +02:00
config = Config(1)
2013-01-06 22:18:13 +01:00
for values in vlist:
if values.get('number') is None:
values['number'] = Sequence.get_id(
config.shipment_out_return_sequence.id)
2013-01-06 22:18:13 +01:00
shipments = super(ShipmentOutReturn, cls).create(vlist)
cls._set_move_planned_date(shipments)
return shipments
2012-09-24 22:18:17 +02:00
@classmethod
2013-12-19 14:45:31 +01:00
def write(cls, *args):
super(ShipmentOutReturn, cls).write(*args)
cls._set_move_planned_date(sum(args[::2], []))
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def copy(cls, shipments, default=None):
2009-01-27 15:15:01 +01:00
if default is None:
default = {}
default = default.copy()
2018-09-01 01:02:04 +02:00
default.setdefault('inventory_moves', None)
default.setdefault('incoming_moves', None)
default.setdefault('number', None)
default.setdefault('received_by', None)
default.setdefault('done_by', None)
2012-09-24 22:18:17 +02:00
return super(ShipmentOutReturn, cls).copy(shipments, default=default)
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def delete(cls, shipments):
2013-03-23 15:42:36 +01:00
Move = Pool().get('stock.move')
# Cance before delete
2012-09-24 22:18:17 +02:00
cls.cancel(shipments)
for shipment in shipments:
if shipment.state != 'cancel':
2018-12-29 14:20:29 +01:00
raise AccessError(
gettext('stock.msg_shipment_delete_cancel',
shipment=shipment.rec_name))
2013-03-23 15:42:36 +01:00
Move.delete([m for s in shipments for m in s.moves])
2012-09-24 22:18:17 +02:00
super(ShipmentOutReturn, cls).delete(shipments)
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('draft')
2012-09-24 22:18:17 +02:00
def draft(cls, shipments):
Move = Pool().get('stock.move')
2015-10-21 16:48:02 +02:00
Move.draft([m for s in shipments for m in s.incoming_moves
if m.state != 'staging'])
2012-09-24 22:18:17 +02:00
Move.delete([m for s in shipments for m in s.inventory_moves
if m.state in ('draft', 'cancel')])
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('received')
2017-02-01 21:41:00 +01:00
@set_employee('received_by')
2012-09-24 22:18:17 +02:00
def receive(cls, shipments):
Move = Pool().get('stock.move')
2012-12-27 21:53:07 +01:00
Move.do([m for s in shipments for m in s.incoming_moves])
2012-09-24 22:18:17 +02:00
cls.create_inventory_moves(shipments)
# Set received state to allow done transition
cls.write(shipments, {'state': 'received'})
to_do = [s for s in shipments
if s.warehouse_storage == s.warehouse_input]
if to_do:
cls.done(to_do)
2012-03-25 00:18:19 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('done')
2017-02-01 21:41:00 +01:00
@set_employee('done_by')
2012-09-24 22:18:17 +02:00
def done(cls, shipments):
pool = Pool()
Move = pool.get('stock.move')
Date = pool.get('ir.date')
2012-12-27 21:53:07 +01:00
Move.do([m for s in shipments for m in s.inventory_moves])
cls.write([s for s in shipments if not s.effective_date], {
2012-09-24 22:18:17 +02:00
'effective_date': Date.today(),
2012-03-25 00:18:19 +01:00
})
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('cancel')
2012-09-24 22:18:17 +02:00
def cancel(cls, shipments):
Move = Pool().get('stock.move')
2012-12-27 21:53:07 +01:00
Move.cancel([m for s in shipments
for m in s.incoming_moves + s.inventory_moves])
2009-01-27 15:15:01 +01:00
2019-04-11 18:54:55 +02:00
def _get_inventory_move(self, incoming_move):
'Return inventory move for the incoming move'
2013-03-12 10:35:12 +01:00
pool = Pool()
Move = pool.get('stock.move')
2009-01-27 15:15:01 +01:00
if incoming_move.quantity <= 0.0:
2013-03-12 10:35:12 +01:00
return
move = Move()
move.product = incoming_move.product
move.uom = incoming_move.uom
move.quantity = incoming_move.quantity
move.from_location = incoming_move.to_location
2019-04-11 18:54:55 +02:00
move.to_location = self.warehouse_storage
2013-03-12 10:35:12 +01:00
move.state = Move.default_state()
# Product will be considered in stock only when the inventory
# move will be made:
2013-03-12 10:35:12 +01:00
move.planned_date = None
move.company = incoming_move.company
2019-07-08 17:18:14 +02:00
move.origin = incoming_move
2013-03-12 10:35:12 +01:00
return move
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@classmethod
def create_inventory_moves(cls, shipments):
2012-03-25 00:18:19 +01:00
for shipment in shipments:
if shipment.warehouse_storage == shipment.warehouse_input:
# Do not create inventory moves
continue
2013-03-12 10:35:12 +01:00
# Use moves instead of inventory_moves because save reset before
# adding new records and as set_inventory_moves is just a proxy to
# moves, it will reset also the incoming_moves
moves = list(shipment.moves)
2012-03-25 00:18:19 +01:00
for incoming_move in shipment.incoming_moves:
2019-04-11 18:54:55 +02:00
move = shipment._get_inventory_move(incoming_move)
2013-03-12 10:35:12 +01:00
if move:
moves.append(move)
shipment.moves = moves
shipment.save()
2009-01-27 15:15:01 +01:00
class AssignShipmentOutAssignFailed(ModelView):
'Assign Customer Shipment'
2012-09-24 22:18:17 +02:00
__name__ = 'stock.shipment.out.assign.failed'
2009-03-05 14:54:19 +01:00
inventory_moves = fields.Many2Many('stock.move', None, None,
2018-12-04 12:25:45 +01:00
'Inventory Moves', readonly=True,
help="The inventory moves that were not assigned.")
2012-09-24 22:18:17 +02:00
@staticmethod
def default_inventory_moves():
ShipmentOut = Pool().get('stock.shipment.out')
2012-01-02 18:07:56 +01:00
shipment_id = Transaction().context.get('active_id')
if not shipment_id:
return []
2012-09-24 22:18:17 +02:00
shipment = ShipmentOut(shipment_id)
2012-01-02 18:07:56 +01:00
return [x.id for x in shipment.inventory_moves if x.state == 'draft']
class AssignShipmentOut(Wizard):
'Assign Customer Shipment'
2012-09-24 22:18:17 +02:00
__name__ = 'stock.shipment.out.assign'
2012-01-02 18:07:56 +01:00
start = StateTransition()
failed = StateView('stock.shipment.out.assign.failed',
'stock.shipment_out_assign_failed_view_form', [
2018-08-19 20:02:58 +02:00
Button('Force Assign', 'force', 'tryton-forward',
2012-01-02 18:07:56 +01:00
states={
'invisible': ~Id('stock',
'group_stock_force_assignment').in_(
Eval('context', {}).get('groups', [])),
}),
2015-03-10 18:37:44 +01:00
Button('OK', 'end', 'tryton-ok', True),
2012-01-02 18:07:56 +01:00
])
force = StateTransition()
2012-09-24 22:18:17 +02:00
def transition_start(self):
2011-07-03 13:19:14 +02:00
pool = Pool()
2012-09-24 22:18:17 +02:00
Shipment = pool.get('stock.shipment.out')
2012-09-24 22:18:17 +02:00
if Shipment.assign_try([Shipment(Transaction().context['active_id'])]):
return 'end'
else:
2012-01-02 18:07:56 +01:00
return 'failed'
2012-09-24 22:18:17 +02:00
def transition_force(self):
Shipment = Pool().get('stock.shipment.out')
2009-02-11 14:56:49 +01:00
2012-09-24 22:18:17 +02:00
Shipment.assign_force([Shipment(Transaction().context['active_id'])])
2012-01-02 18:07:56 +01:00
return 'end'
2019-09-20 11:47:15 +02:00
class ShipmentInternal(ShipmentMixin, Workflow, ModelSQL, ModelView):
2009-03-20 12:14:13 +01:00
"Internal Shipment"
2012-09-24 22:18:17 +02:00
__name__ = 'stock.shipment.internal'
_rec_name = 'number'
effective_date = fields.Date('Effective Date',
states={
'readonly': Eval('state').in_(['cancel', 'done']),
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="When the shipment was actually completed.")
2010-01-20 11:04:28 +01:00
planned_date = fields.Date('Planned Date',
2011-08-04 16:57:42 +02:00
states={
2017-01-15 00:17:40 +01:00
'readonly': ~Eval('state').in_(['request', 'draft']),
2018-12-04 12:25:45 +01:00
}, depends=['state'],
help="When the shipment is expected to be completed.")
effective_start_date = fields.Date('Effective Start Date',
states={
'readonly': Eval('state').in_(['cancel', 'shipped', 'done']),
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="When the stock was actually sent.")
planned_start_date = fields.Date('Planned Start Date',
states={
2017-01-15 00:17:40 +01:00
'readonly': ~Eval('state').in_(['request', 'draft']),
'required': Bool(Eval('planned_date')),
},
2018-12-04 12:25:45 +01:00
depends=['state'],
help="When the stock is expected to be sent.")
2011-10-04 18:17:41 +02:00
company = fields.Many2One('company.company', 'Company', required=True,
states={
2017-01-15 00:17:40 +01:00
'readonly': ~Eval('state').in_(['request', 'draft']),
2011-10-04 18:17:41 +02:00
},
domain=[
2015-06-28 23:45:31 +02:00
('id', If(Eval('context', {}).contains('company'), '=', '!='),
2013-10-09 18:25:53 +02:00
Eval('context', {}).get('company', -1)),
2011-10-04 18:17:41 +02:00
],
2018-12-04 12:25:45 +01:00
depends=['state'],
help="The company the shipment is associated with.")
number = fields.Char('Number', size=None, select=True, readonly=True,
help="The main identifier for the shipment.")
2012-03-05 10:53:08 +01:00
reference = fields.Char("Reference", size=None, select=True,
2011-08-04 16:57:42 +02:00
states={
2017-01-15 00:17:40 +01:00
'readonly': ~Eval('state').in_(['request', 'draft']),
2018-12-04 12:25:45 +01:00
}, depends=['state'],
help="The external identifiers for the shipment.")
2010-01-20 11:04:28 +01:00
from_location = fields.Many2One('stock.location', "From Location",
2011-08-04 16:57:42 +02:00
required=True, states={
2017-01-15 00:17:40 +01:00
'readonly': (~Eval('state').in_(['request', 'draft'])
| Eval('moves', [0])),
2010-01-20 11:04:28 +01:00
},
2011-08-04 16:57:42 +02:00
domain=[
('type', 'in', ['view', 'storage', 'lost_found']),
2018-12-04 12:25:45 +01:00
], depends=['state'],
help="Where the stock is moved from.")
2008-10-06 12:20:19 +02:00
to_location = fields.Many2One('stock.location', "To Location",
2011-08-04 16:57:42 +02:00
required=True, states={
2017-01-15 00:17:40 +01:00
'readonly': (~Eval('state').in_(['request', 'draft'])
| Eval('moves', [0])),
2010-01-20 11:04:28 +01:00
}, domain=[
('type', 'in', ['view', 'storage', 'lost_found']),
2018-12-04 12:25:45 +01:00
], depends=['state'],
help="Where the stock is moved to.")
transit_location = fields.Function(fields.Many2One('stock.location',
2018-12-04 12:25:45 +01:00
'Transit Location',
help="Where the stock is located while it is in transit between "
"the warehouses."),
'on_change_with_transit_location')
2013-03-23 15:42:36 +01:00
moves = fields.One2Many('stock.move', 'shipment', 'Moves',
2010-01-20 11:04:28 +01:00
states={
'readonly': (Eval('state').in_(['cancel', 'assigned', 'done'])
| ~Eval('from_location') | ~Eval('to_location')),
'invisible': (Bool(Eval('transit_location'))
2017-01-15 00:17:40 +01:00
& ~Eval('state').in_(['request', 'draft'])),
2011-10-04 18:17:41 +02:00
},
domain=[
2017-01-15 00:17:40 +01:00
If(Eval('state').in_(['request', 'draft']), [
('from_location', '=', Eval('from_location')),
('to_location', '=', Eval('to_location')),
],
If(~Eval('state').in_(['done', 'cancel']),
If(~Eval('transit_location'),
[
('from_location', 'child_of',
[Eval('from_location', -1)], 'parent'),
('to_location', 'child_of',
[Eval('to_location', -1)], 'parent'),
],
['OR',
[
('from_location', 'child_of',
[Eval('from_location', -1)], 'parent'),
('to_location', '=', Eval('transit_location')),
],
[
('from_location', '=',
Eval('transit_location')),
('to_location', 'child_of',
[Eval('to_location', -1)], 'parent'),
],
]),
[])),
('company', '=', Eval('company')),
],
depends=['state', 'from_location', 'to_location', 'transit_location',
2018-12-04 12:25:45 +01:00
'company'],
help="The moves that perform the shipment.")
outgoing_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
'Outgoing Moves',
domain=[
If(Eval('state').in_(['request', 'draft']), [
('from_location', 'child_of',
[Eval('from_location', -1)], 'parent'),
If(~Eval('transit_location'),
('to_location', 'child_of',
[Eval('to_location', -1)], 'parent'),
('to_location', '=', Eval('transit_location'))),
],
[]),
],
states={
'readonly': Eval('state').in_(
['assigned', 'shipped', 'done', 'cancel']),
'invisible': (~Eval('transit_location')
2017-01-15 00:17:40 +01:00
| Eval('state').in_(['request', 'draft'])),
},
depends=['from_location', 'to_location', 'transit_location',
2018-12-04 12:25:45 +01:00
'state'],
help="The moves that send the stock out."),
'get_outgoing_moves', setter='set_moves')
incoming_moves = fields.Function(fields.One2Many('stock.move', 'shipment',
'Incoming Moves',
domain=[
If(~Eval('state').in_(['done', 'cancel']), [
If(~Eval('transit_location'),
('from_location', 'child_of',
[Eval('from_location', -1)], 'parent'),
('from_location', '=', Eval('transit_location'))),
('to_location', 'child_of',
[Eval('to_location', -1)], 'parent'),
],
[]),
],
states={
'readonly': Eval('state').in_(['done', 'cancel']),
'invisible': (~Eval('transit_location')
2017-01-15 00:17:40 +01:00
| Eval('state').in_(['request', 'draft'])),
},
depends=['from_location', 'to_location', 'transit_location',
2018-12-04 12:25:45 +01:00
'state'],
help="The moves that receive the stock in."),
'get_incoming_moves', setter='set_moves')
2017-02-01 21:41:00 +01:00
assigned_by = employee_field("Received By")
shipped_by = employee_field("Shipped By")
done_by = employee_field("Done By")
2008-10-06 12:20:19 +02:00
state = fields.Selection([
2017-01-15 00:17:40 +01:00
('request', 'Request'),
('draft', 'Draft'),
('cancel', 'Canceled'),
('waiting', 'Waiting'),
('assigned', 'Assigned'),
('shipped', 'Shipped'),
('done', 'Done'),
2018-12-04 12:25:45 +01:00
], 'State', readonly=True,
help="The current state of the shipment.")
2008-10-06 12:20:19 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
def __setup__(cls):
super(ShipmentInternal, cls).__setup__()
cls._order[0] = ('id', 'DESC')
cls._transitions |= set((
2017-01-15 00:17:40 +01:00
('request', 'draft'),
2012-03-25 00:18:19 +01:00
('draft', 'waiting'),
('waiting', 'waiting'),
('waiting', 'assigned'),
('assigned', 'shipped'),
2012-03-25 00:18:19 +01:00
('assigned', 'done'),
('shipped', 'done'),
2012-03-25 00:18:19 +01:00
('waiting', 'draft'),
('assigned', 'waiting'),
2017-01-15 00:17:40 +01:00
('request', 'cancel'),
2012-03-25 00:18:19 +01:00
('draft', 'cancel'),
('waiting', 'cancel'),
('assigned', 'cancel'),
('cancel', 'draft'),
2012-03-25 00:18:19 +01:00
))
2012-09-24 22:18:17 +02:00
cls._buttons.update({
2012-03-25 00:18:19 +01:00
'cancel': {
'invisible': Eval('state').in_(
['cancel', 'shipped', 'done']),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'draft': {
2017-01-15 00:17:40 +01:00
'invisible': ~Eval('state').in_(
['cancel', 'request', 'waiting']),
2012-03-25 00:18:19 +01:00
'icon': If(Eval('state') == 'cancel',
2018-08-19 20:02:58 +02:00
'tryton-undo',
2017-01-15 00:17:40 +01:00
If(Eval('state') == 'request',
2018-08-19 20:02:58 +02:00
'tryton-forward',
'tryton-back')),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'wait': {
'invisible': ~Eval('state').in_(['assigned', 'waiting',
'draft']),
'icon': If(Eval('state') == 'assigned',
2018-08-19 20:02:58 +02:00
'tryton-back',
2012-03-25 00:18:19 +01:00
If(Eval('state') == 'waiting',
'tryton-clear',
2018-08-19 20:02:58 +02:00
'tryton-forward')),
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-03-25 00:18:19 +01:00
},
'ship': {
'invisible': ((Eval('state') != 'assigned') |
~Eval('transit_location')),
2018-02-19 11:41:53 +01:00
'depends': ['state', 'transit_location'],
},
2012-03-25 00:18:19 +01:00
'done': {
'invisible': If(
~Eval('transit_location'),
Eval('state') != 'assigned',
Eval('state') != 'shipped'),
2018-02-19 11:41:53 +01:00
'depends': ['state', 'transit_location'],
2012-03-25 00:18:19 +01:00
},
2012-09-10 15:28:38 +02:00
'assign_wizard': {
'invisible': Eval('state') != 'waiting',
2018-02-19 11:41:53 +01:00
'depends': ['state'],
2012-09-10 15:28:38 +02:00
},
2012-03-25 00:18:19 +01:00
'assign_try': {},
'assign_force': {},
})
2012-09-24 22:18:17 +02:00
@classmethod
def __register__(cls, module_name):
cursor = Transaction().connection.cursor()
2013-09-06 22:34:48 +02:00
sql_table = cls.__table__()
2018-07-24 22:01:08 +02:00
table = cls.__table_handler__(module_name)
2011-10-04 18:17:41 +02:00
# Migration from 3.8:
if table.column_exist('code'):
table.column_rename('code', 'number')
2012-09-24 22:18:17 +02:00
super(ShipmentInternal, cls).__register__(module_name)
# Migration from 4.0: fill planned_start_date
cursor = Transaction().connection.cursor()
cursor.execute(*sql_table.update(
[sql_table.planned_start_date],
[sql_table.planned_date],
where=(sql_table.planned_start_date == Null)
& (sql_table.planned_date != Null)))
# Add index on create_date
2018-07-24 22:01:08 +02:00
table = cls.__table_handler__(module_name)
table.index_action('create_date', action='add')
2012-09-24 22:18:17 +02:00
@staticmethod
def default_state():
2008-10-06 12:20:19 +02:00
return 'draft'
2012-09-24 22:18:17 +02:00
@staticmethod
def default_company():
2012-03-27 10:44:04 +02:00
return Transaction().context.get('company')
2011-10-04 18:17:41 +02:00
@fields.depends('planned_date', 'planned_start_date')
def on_change_with_transit_location(self, name=None):
pool = Pool()
Config = pool.get('stock.configuration')
if self.planned_date != self.planned_start_date:
2017-04-01 00:38:23 +02:00
return Config(1).get_multivalue('shipment_internal_transit').id
@fields.depends('planned_date', 'from_location', 'to_location')
def on_change_with_planned_start_date(self, pattern=None):
pool = Pool()
LocationLeadTime = pool.get('stock.location.lead_time')
if self.planned_date:
if pattern is None:
pattern = {}
pattern.setdefault('warehouse_from',
self.from_location.warehouse.id
if self.from_location and self.from_location.warehouse
else None)
pattern.setdefault('warehouse_to',
self.to_location.warehouse.id
if self.to_location and self.to_location.warehouse
else None)
lead_time = LocationLeadTime.get_lead_time(pattern)
if lead_time:
return self.planned_date - lead_time
return self.planned_date
def get_outgoing_moves(self, name):
if not self.transit_location:
return [m.id for m in self.moves]
moves = []
for move in self.moves:
if move.to_location == self.transit_location:
moves.append(move.id)
return moves
def get_incoming_moves(self, name):
if not self.transit_location:
return [m.id for m in self.moves]
moves = []
for move in self.moves:
if move.from_location == self.transit_location:
moves.append(move.id)
return moves
@classmethod
def set_moves(cls, shipments, name, value):
if not value:
return
cls.write(shipments, {
'moves': value,
})
2012-09-24 22:18:17 +02:00
@classmethod
2013-01-06 22:18:13 +01:00
def create(cls, vlist):
2012-09-24 22:18:17 +02:00
pool = Pool()
Sequence = pool.get('ir.sequence')
Config = pool.get('stock.configuration')
2013-01-06 22:18:13 +01:00
vlist = [x.copy() for x in vlist]
2012-09-24 22:18:17 +02:00
config = Config(1)
2013-01-06 22:18:13 +01:00
for values in vlist:
if values.get('number') is None:
values['number'] = Sequence.get_id(
config.shipment_internal_sequence.id)
2013-01-06 22:18:13 +01:00
return super(ShipmentInternal, cls).create(vlist)
2012-09-24 22:18:17 +02:00
@classmethod
def delete(cls, shipments):
2013-03-23 15:42:36 +01:00
Move = Pool().get('stock.move')
# Cancel before delete
2012-09-24 22:18:17 +02:00
cls.cancel(shipments)
for shipment in shipments:
if shipment.state != 'cancel':
2018-12-29 14:20:29 +01:00
raise AccessError(
gettext('stock.msg_shipment_delete_cancel',
shipment=shipment.rec_name))
2013-03-23 15:42:36 +01:00
Move.delete([m for s in shipments for m in s.moves])
2012-09-24 22:18:17 +02:00
super(ShipmentInternal, cls).delete(shipments)
@classmethod
def copy(cls, shipments, default=None):
2018-09-01 01:02:04 +02:00
def shipment_field(data, name):
model, shipment_id = data['shipment'].split(',', 1)
assert model == cls.__name__
shipment_id = int(shipment_id)
shipment = id2shipments[shipment_id]
return getattr(shipment, name)
def outgoing_moves(data):
shipment = id2shipments[data['id']]
return shipment.outgoing_moves
id2shipments = {s.id: s for s in shipments}
if default is None:
default = {}
else:
default = default.copy()
2018-09-01 01:02:04 +02:00
default.setdefault('moves', outgoing_moves)
default.setdefault('moves.from_location', partial(
shipment_field, name='from_location'))
default.setdefault('moves.to_location', partial(
shipment_field, name='to_location'))
default.setdefault('moves.planned_date', partial(
shipment_field, name='planned_date'))
default.setdefault('number', None)
default.setdefault('assigned_by', None)
default.setdefault('shipped_by', None)
default.setdefault('done_by', None)
return super().copy(shipments, default=default)
2019-08-18 19:22:43 +02:00
def _sync_move_key(self, move):
return (
('product', move.product),
('uom', move.uom),
)
def _sync_incoming_move(self, template=None):
pool = Pool()
Move = pool.get('stock.move')
move = Move(
from_location=self.transit_location,
to_location=self.to_location,
quantity=0,
shipment=self,
planned_date=self.planned_date,
company=self.company,
)
if template:
move.origin = template.origin
move.currency = template.currency
move.unit_price = template.unit_price
else:
move.currency = self.company.currency
move.unit_price = 0
return move
@classmethod
def _sync_moves(cls, shipments):
pool = Pool()
Move = pool.get('stock.move')
Uom = pool.get('product.uom')
2019-08-18 19:22:43 +02:00
def active(move):
return move.state != 'cancel'
moves = []
for shipment in shipments:
2017-01-15 00:17:40 +01:00
if not shipment.transit_location:
continue
2019-08-18 19:22:43 +02:00
incoming_moves = {m: m for m in shipment.incoming_moves}
outgoing_qty = defaultdict(lambda: defaultdict(lambda: 0))
for move in filter(active, shipment.incoming_moves):
key = shipment._sync_move_key(move)
outgoing_qty[move][key] = 0
for move in filter(active, shipment.outgoing_moves):
key = shipment._sync_move_key(move)
incoming_move = incoming_moves.get(move.origin)
qty_default_uom = Uom.compute_qty(
move.uom, move.quantity,
move.product.default_uom, round=False)
outgoing_qty[incoming_move][key] += qty_default_uom
2019-08-18 19:22:43 +02:00
for incoming_move in outgoing_qty:
if incoming_move:
incoming_key = shipment._sync_move_key(incoming_move)
for key, qty in outgoing_qty[incoming_move].items():
if incoming_move and key == incoming_key:
move = incoming_move
else:
move = shipment._sync_incoming_move(incoming_move)
for name, value in key:
setattr(move, name, value)
qty = Uom.compute_qty(
move.product.default_uom, qty,
move.uom)
if move.quantity != qty:
move.quantity = qty
moves.append(move)
Move.save(moves)
2017-01-15 00:17:40 +01:00
@classmethod
def _set_transit(cls, shipments):
pool = Pool()
Move = pool.get('stock.move')
to_write = []
for shipment in shipments:
if not shipment.transit_location:
continue
moves = [m for m in shipment.moves
if m.state != 'done'
and m.from_location != shipment.transit_location
and m.to_location != shipment.transit_location]
Move.copy(moves, default={
2019-08-18 19:22:43 +02:00
'to_location': shipment.transit_location.id,
2019-09-16 23:58:02 +02:00
'planned_date': shipment.planned_start_date,
2019-08-18 19:22:43 +02:00
'origin': lambda data: '%s,%s' % (
Move.__name__, data['id']),
2017-01-15 00:17:40 +01:00
})
to_write.append(moves)
to_write.append({
2019-08-18 19:22:43 +02:00
'from_location': shipment.transit_location.id,
2019-09-16 23:58:02 +02:00
'planned_date': shipment.planned_date,
2017-01-15 00:17:40 +01:00
})
if to_write:
Move.write(*to_write)
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('draft')
2012-09-24 22:18:17 +02:00
def draft(cls, shipments):
Move = Pool().get('stock.move')
2012-03-25 00:18:19 +01:00
# First reset state to draft to allow update from and to location
2015-10-21 16:48:02 +02:00
Move.draft([m for s in shipments for m in s.moves
if m.state != 'staging'])
Move.delete([m for s in shipments for m in s.moves
if m.from_location == s.transit_location])
2012-03-25 00:18:19 +01:00
for shipment in shipments:
2012-09-24 22:18:17 +02:00
Move.write([m for m in shipment.moves
if m.state != 'done'], {
2013-02-24 16:18:17 +01:00
'from_location': shipment.from_location.id,
'to_location': shipment.to_location.id,
'planned_date': shipment.planned_date,
})
2008-10-06 12:20:19 +02:00
@classmethod
@ModelView.button
@Workflow.transition('waiting')
def wait(cls, shipments):
Move = Pool().get('stock.move')
Move.draft([m for s in shipments for m in s.moves])
moves = []
2017-01-15 00:17:40 +01:00
for shipment in shipments:
if shipment.transit_location:
continue
for move in shipment.moves:
if move.state != 'done':
move.planned_date = shipment.planned_date
moves.append(move)
Move.save(moves)
2017-01-15 00:17:40 +01:00
cls._set_transit(shipments)
cls._sync_moves(shipments)
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@Workflow.transition('assigned')
2017-02-01 21:41:00 +01:00
@set_employee('assigned_by')
2012-09-24 22:18:17 +02:00
def assign(cls, shipments):
2012-03-25 00:18:19 +01:00
pass
2008-10-06 12:20:19 +02:00
@classmethod
@ModelView.button
@Workflow.transition('shipped')
2017-02-01 21:41:00 +01:00
@set_employee('shipped_by')
def ship(cls, shipments):
pool = Pool()
Move = pool.get('stock.move')
Date = pool.get('ir.date')
Move.do([m for s in shipments for m in s.outgoing_moves])
cls._sync_moves(shipments)
cls.write([s for s in shipments if not s.effective_start_date], {
'effective_start_date': Date.today(),
})
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('done')
2017-02-01 21:41:00 +01:00
@set_employee('done_by')
2012-09-24 22:18:17 +02:00
def done(cls, shipments):
pool = Pool()
Move = pool.get('stock.move')
Date = pool.get('ir.date')
Move.do([m for s in shipments for m in s.incoming_moves])
cls.write([s for s in shipments if not s.effective_date], {
2012-09-24 22:18:17 +02:00
'effective_date': Date.today(),
2012-03-25 00:18:19 +01:00
})
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
@Workflow.transition('cancel')
2012-09-24 22:18:17 +02:00
def cancel(cls, shipments):
Move = Pool().get('stock.move')
2012-12-27 21:53:07 +01:00
Move.cancel([m for s in shipments for m in s.moves])
2008-10-06 12:20:19 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-09-10 15:28:38 +02:00
@ModelView.button_action('stock.wizard_shipment_internal_assign')
2012-09-24 22:18:17 +02:00
def assign_wizard(cls, shipments):
2012-09-10 15:28:38 +02:00
pass
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
2012-09-24 22:18:17 +02:00
def assign_try(cls, shipments):
Move = Pool().get('stock.move')
to_assign = [m for s in shipments for m in s.outgoing_moves
2017-06-24 10:34:21 +02:00
if m.assignation_required]
if not to_assign or Move.assign_try(to_assign):
2012-09-24 22:18:17 +02:00
cls.assign(shipments)
2012-03-25 00:18:19 +01:00
return True
else:
return False
2008-10-06 12:20:19 +02:00
2012-09-24 22:18:17 +02:00
@classmethod
2012-03-25 00:18:19 +01:00
@ModelView.button
2012-09-24 22:18:17 +02:00
def assign_force(cls, shipments):
Move = Pool().get('stock.move')
Move.assign([m for s in shipments for m in s.outgoing_moves])
2012-09-24 22:18:17 +02:00
cls.assign(shipments)
2008-05-08 12:49:46 +02:00
2008-10-22 20:13:23 +02:00
2018-06-26 19:11:55 +02:00
class Address(metaclass=PoolMeta):
2012-09-24 22:18:17 +02:00
__name__ = 'party.address'
2018-12-04 12:25:45 +01:00
delivery = fields.Boolean('Delivery',
help="Check to send deliveries to the address.")
class AssignShipmentInternalAssignFailed(ModelView):
2012-01-02 18:07:56 +01:00
'Assign Shipment Internal'
2012-09-24 22:18:17 +02:00
__name__ = 'stock.shipment.internal.assign.failed'
2009-03-05 14:54:19 +01:00
moves = fields.Many2Many('stock.move', None, None, 'Moves',
2018-12-04 12:25:45 +01:00
readonly=True,
help="The moves that were not assigned.")
2012-09-24 22:18:17 +02:00
@staticmethod
def default_moves():
ShipmentInternal = Pool().get('stock.shipment.internal')
2012-01-02 18:07:56 +01:00
shipment_id = Transaction().context.get('active_id')
if not shipment_id:
return []
2012-09-24 22:18:17 +02:00
shipment = ShipmentInternal(shipment_id)
return [x.id for x in shipment.outgoing_moves if x.state == 'draft']
2012-01-02 18:07:56 +01:00
class AssignShipmentInternal(Wizard):
2009-03-20 12:14:13 +01:00
'Assign Shipment Internal'
2012-09-24 22:18:17 +02:00
__name__ = 'stock.shipment.internal.assign'
2012-01-02 18:07:56 +01:00
start = StateTransition()
failed = StateView('stock.shipment.internal.assign.failed',
'stock.shipment_internal_assign_failed_view_form', [
2018-08-19 20:02:58 +02:00
Button('Force Assign', 'force', 'tryton-forward',
2012-01-02 18:07:56 +01:00
states={
'invisible': ~Id('stock',
'group_stock_force_assignment').in_(
Eval('context', {}).get('groups', [])),
}),
2015-03-10 18:37:44 +01:00
Button('OK', 'end', 'tryton-ok', True),
2012-01-02 18:07:56 +01:00
])
force = StateTransition()
2012-09-24 22:18:17 +02:00
def transition_start(self):
2011-07-03 13:19:14 +02:00
pool = Pool()
2012-09-24 22:18:17 +02:00
Shipment = pool.get('stock.shipment.internal')
2012-09-24 22:18:17 +02:00
if Shipment.assign_try([Shipment(Transaction().context['active_id'])]):
return 'end'
else:
2012-01-02 18:07:56 +01:00
return 'failed'
2012-09-24 22:18:17 +02:00
def transition_force(self):
Shipment = Pool().get('stock.shipment.internal')
2009-02-11 14:56:49 +01:00
2012-09-24 22:18:17 +02:00
Shipment.assign_force([Shipment(Transaction().context['active_id'])])
2012-01-02 18:07:56 +01:00
return 'end'
class AssignShipmentInReturnAssignFailed(ModelView):
2012-01-02 18:07:56 +01:00
'Assign Supplier Return Shipment'
2012-09-24 22:18:17 +02:00
__name__ = 'stock.shipment.in.return.assign.failed'
2009-03-05 14:54:19 +01:00
moves = fields.Many2Many('stock.move', None, None, 'Moves',
2018-12-04 12:25:45 +01:00
readonly=True,
help="The moves that were not assigned.")
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
@staticmethod
def default_moves():
ShipmentInternal = Pool().get('stock.shipment.in.return')
2012-01-02 18:07:56 +01:00
shipment_id = Transaction().context.get('active_id')
if not shipment_id:
return []
2012-09-24 22:18:17 +02:00
shipment = ShipmentInternal(shipment_id)
2012-01-02 18:07:56 +01:00
return [x.id for x in shipment.moves if x.state == 'draft']
2009-01-27 15:15:01 +01:00
class AssignShipmentInReturn(Wizard):
2009-03-20 12:14:13 +01:00
'Assign Supplier Return Shipment'
2012-09-24 22:18:17 +02:00
__name__ = 'stock.shipment.in.return.assign'
2012-01-02 18:07:56 +01:00
start = StateTransition()
failed = StateView('stock.shipment.in.return.assign.failed',
'stock.shipment_in_return_assign_failed_view_form', [
2018-08-19 20:02:58 +02:00
Button('Force Assign', 'force', 'tryton-forward',
2012-01-02 18:07:56 +01:00
states={
'invisible': ~Id('stock',
'group_stock_force_assignment').in_(
Eval('context', {}).get('groups', [])),
}),
2015-03-10 18:37:44 +01:00
Button('OK', 'end', 'tryton-ok', True),
2012-01-02 18:07:56 +01:00
])
force = StateTransition()
2012-09-24 22:18:17 +02:00
def transition_start(self):
2011-07-03 13:19:14 +02:00
pool = Pool()
2012-09-24 22:18:17 +02:00
Shipment = pool.get('stock.shipment.in.return')
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
if Shipment.assign_try([Shipment(Transaction().context['active_id'])]):
2009-01-27 15:15:01 +01:00
return 'end'
else:
2012-01-02 18:07:56 +01:00
return 'failed'
2009-01-27 15:15:01 +01:00
2012-09-24 22:18:17 +02:00
def transition_force(self):
Shipment = Pool().get('stock.shipment.in.return')
2009-02-11 14:56:49 +01:00
2012-09-24 22:18:17 +02:00
Shipment.assign_force([Shipment(Transaction().context['active_id'])])
2012-01-02 18:07:56 +01:00
return 'end'
2009-01-27 15:15:01 +01:00
class DeliveryNote(CompanyReport):
2012-09-24 22:18:17 +02:00
'Delivery Note'
__name__ = 'stock.shipment.out.delivery_note'
2008-05-16 01:53:15 +02:00
2016-09-06 09:25:36 +02:00
@classmethod
def execute(cls, ids, data):
with Transaction().set_context(address_with_party=True):
return super(DeliveryNote, cls).execute(ids, data)
2009-03-26 19:01:42 +01:00
2015-01-20 18:49:56 +01:00
class ShipmentReport(CompanyReport):
2016-09-06 09:25:36 +02:00
@classmethod
def execute(cls, ids, data):
with Transaction().set_context(address_with_party=True):
return super(ShipmentReport, cls).execute(ids, data)
2015-01-20 18:49:56 +01:00
@classmethod
def moves(cls, shipment):
raise NotImplementedError
2015-01-20 18:49:56 +01:00
@classmethod
def get_context(cls, shipments, data):
report_context = super().get_context(shipments, data)
compare_context = cls.get_compare_context(shipments, data)
2015-01-20 18:49:56 +01:00
sorted_moves = {}
for shipment in shipments:
2015-01-20 18:49:56 +01:00
sorted_moves[shipment.id] = sorted(
cls.moves(shipment),
2015-01-20 18:49:56 +01:00
key=functools.partial(cls.get_compare_key, compare_context))
report_context['moves'] = sorted_moves
return report_context
@classmethod
def get_compare_context(cls, shipments, data):
2015-01-20 18:49:56 +01:00
from_location_ids = set()
to_location_ids = set()
for shipment in shipments:
for move in cls.moves(shipment):
2015-01-20 18:49:56 +01:00
from_location_ids.add(move.from_location.id)
to_location_ids.add(move.to_location.id)
return {
'from_location_ids': list(from_location_ids),
'to_location_ids': list(to_location_ids),
}
@staticmethod
def get_compare_key(compare_context, move):
from_location_ids = compare_context['from_location_ids']
to_location_ids = compare_context['to_location_ids']
return [from_location_ids.index(move.from_location.id),
to_location_ids.index(move.to_location.id)]
class PickingList(ShipmentReport):
2012-09-24 22:18:17 +02:00
'Picking List'
__name__ = 'stock.shipment.out.picking_list'
2009-03-26 19:01:42 +01:00
@classmethod
def moves(cls, shipment):
if shipment.warehouse_storage == shipment.warehouse_output:
return shipment.outgoing_moves
else:
return shipment.inventory_moves
2009-03-27 16:08:07 +01:00
2015-01-20 18:49:56 +01:00
class SupplierRestockingList(ShipmentReport):
2012-09-24 22:18:17 +02:00
'Supplier Restocking List'
__name__ = 'stock.shipment.in.restocking_list'
2009-03-27 17:11:46 +01:00
@classmethod
def moves(cls, shipment):
if shipment.warehouse_input == shipment.warehouse_storage:
return shipment.incoming_moves
else:
return shipment.inventory_moves
2009-03-27 17:11:46 +01:00
2015-01-20 18:49:56 +01:00
class CustomerReturnRestockingList(ShipmentReport):
2012-09-24 22:18:17 +02:00
'Customer Return Restocking List'
__name__ = 'stock.shipment.out.return.restocking_list'
2009-03-27 17:11:46 +01:00
@classmethod
def moves(cls, shipment):
if shipment.warehouse_input == shipment.warehouse_storage:
return shipment.incoming_moves
else:
return shipment.inventory_moves
2009-03-27 17:11:46 +01:00
2015-01-20 18:49:56 +01:00
class InteralShipmentReport(ShipmentReport):
2012-09-24 22:18:17 +02:00
'Interal Shipment Report'
__name__ = 'stock.shipment.internal.report'
@classmethod
def moves(cls, shipment):
if shipment.transit_location:
if shipment.state == 'shipped':
return shipment.incoming_moves
else:
return shipment.outgoing_moves
else:
return shipment.moves