[REF] stock: planned transfer: additional moves
Before this patch, move added in a planned transfer once it is ready are directly marked as assigned and the reservation is disabled on them. People found it hard to understand why the check availability button did not appear, plus the push rules were not applied. Now we chose to use the autoconfirm mechanism on the added move and we don't reserve them, so the check availability button reappear. task-2081844
This commit is contained in:
@@ -399,8 +399,9 @@ class StockMove(models.Model):
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
# We override the default_get to make stock moves created after the picking was confirmed
|
||||
# directly as available. This allows to create extra move lines in
|
||||
# the fp view.
|
||||
# directly as available in immediate transfer mode. This allows to create extra move lines
|
||||
# in the fp view. In planned transfer, the stock move are marked as `additional` and will be
|
||||
# auto-confirmed.
|
||||
defaults = super(StockMove, self).default_get(fields_list)
|
||||
if self.env.context.get('default_picking_id'):
|
||||
picking_id = self.env['stock.picking'].browse(self.env.context['default_picking_id'])
|
||||
@@ -408,10 +409,11 @@ class StockMove(models.Model):
|
||||
defaults['state'] = 'done'
|
||||
defaults['product_uom_qty'] = 0.0
|
||||
defaults['additional'] = True
|
||||
elif picking_id.state not in ['draft', 'confirmed']:
|
||||
defaults['state'] = 'assigned'
|
||||
elif picking_id.state not in ['cancel', 'draft', 'done']:
|
||||
if picking_id.immediate_transfer:
|
||||
defaults['state'] = 'assigned'
|
||||
defaults['product_uom_qty'] = 0.0
|
||||
defaults['additional'] = True
|
||||
defaults['additional'] = True # to trigger `_autoconfirm_picking`
|
||||
return defaults
|
||||
|
||||
def name_get(self):
|
||||
|
||||
@@ -473,6 +473,7 @@ class Picking(models.Model):
|
||||
for picking in self:
|
||||
picking.has_packages = picking.move_line_ids.filtered(lambda ml: ml.result_package_id)
|
||||
|
||||
@api.depends('immediate_transfer', 'is_locked', 'state')
|
||||
def _compute_show_check_availability(self):
|
||||
""" According to `picking.show_check_availability`, the "check availability" button will be
|
||||
displayed in the form view of a picking.
|
||||
@@ -589,18 +590,7 @@ class Picking(models.Model):
|
||||
if after_vals:
|
||||
self.mapped('move_lines').filtered(lambda move: not move.scrapped).write(after_vals)
|
||||
if vals.get('move_lines'):
|
||||
# Do not run autoconfirm if any of the moves has an initial demand. If an initial demand
|
||||
# is present in any of the moves, it means the picking was created through the "planned
|
||||
# transfer" mechanism.
|
||||
pickings_to_not_autoconfirm = self.env['stock.picking']
|
||||
for picking in self:
|
||||
if picking.state != 'draft':
|
||||
continue
|
||||
for move in picking.move_lines:
|
||||
if not float_is_zero(move.product_uom_qty, precision_rounding=move.product_uom.rounding):
|
||||
pickings_to_not_autoconfirm |= picking
|
||||
break
|
||||
(self - pickings_to_not_autoconfirm)._autoconfirm_picking()
|
||||
self._autoconfirm_picking()
|
||||
return res
|
||||
|
||||
def unlink(self):
|
||||
@@ -906,8 +896,17 @@ class Picking(models.Model):
|
||||
return immediate_pickings
|
||||
|
||||
def _autoconfirm_picking(self):
|
||||
for picking in self.filtered(lambda picking: picking.immediate_transfer and picking.state not in ('done', 'cancel') and (picking.move_lines or picking.package_level_ids)):
|
||||
picking.action_confirm()
|
||||
""" Automatically run `action_confirm` on `self` if the picking is an immediate transfer or
|
||||
if the picking is a planned transfer and one of its move was added after the initial
|
||||
call to `action_confirm`. Note that `action_confirm` will only work on draft moves.
|
||||
"""
|
||||
for picking in self:
|
||||
if picking.state in ('done', 'cancel'):
|
||||
continue
|
||||
if not picking.move_lines and not picking.package_level_ids:
|
||||
continue
|
||||
if picking.immediate_transfer or any(move.additional for move in picking.move_lines):
|
||||
picking.action_confirm()
|
||||
|
||||
def _create_backorder(self):
|
||||
""" This method is called when the user chose to create a backorder. It will create a new
|
||||
|
||||
@@ -1348,50 +1348,6 @@ class TestSinglePicking(TestStockCommon):
|
||||
self.assertEqual(move_lines[0].lot_id.id, serial1.id)
|
||||
self.assertEqual(move_lines[1].lot_id.id, serial2.id)
|
||||
|
||||
def test_add_move_when_picking_is_available_1(self):
|
||||
""" Check that any move added in a picking once it's assigned is directly considered as
|
||||
assigned and bypass the reservation.
|
||||
"""
|
||||
delivery_order = self.env['stock.picking'].create({
|
||||
'location_id': self.pack_location,
|
||||
'location_dest_id': self.customer_location,
|
||||
'picking_type_id': self.picking_type_out,
|
||||
})
|
||||
self.MoveObj.create({
|
||||
'name': self.productA.name,
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': self.productA.uom_id.id,
|
||||
'picking_id': delivery_order.id,
|
||||
'location_id': self.pack_location,
|
||||
'location_dest_id': self.customer_location,
|
||||
})
|
||||
|
||||
# make some stock
|
||||
pack_location = self.env['stock.location'].browse(self.pack_location)
|
||||
self.env['stock.quant']._update_available_quantity(self.productA, pack_location, 2)
|
||||
|
||||
# assign
|
||||
delivery_order.action_confirm()
|
||||
delivery_order.action_assign()
|
||||
self.assertEqual(delivery_order.state, 'assigned')
|
||||
|
||||
# add a move
|
||||
move2 = self.MoveObj\
|
||||
.with_context(default_picking_id=delivery_order.id)\
|
||||
.create({
|
||||
'name': self.productA.name,
|
||||
'product_id': self.productB.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': self.productA.uom_id.id,
|
||||
'picking_id': delivery_order.id,
|
||||
'location_id': self.pack_location,
|
||||
'location_dest_id': self.customer_location,
|
||||
})
|
||||
|
||||
self.assertEqual(move2.state, 'assigned')
|
||||
self.assertEqual(delivery_order.state, 'assigned')
|
||||
|
||||
def test_use_create_lot_use_existing_lot_1(self):
|
||||
""" Check the behavior of a picking when `use_create_lot` and `use_existing_lot` are
|
||||
set to False and there's a move for a tracked product.
|
||||
@@ -1824,12 +1780,120 @@ class TestSinglePicking(TestStockCommon):
|
||||
move.product_id = self.productA
|
||||
move.product_uom_qty = 10
|
||||
picking = picking.save()
|
||||
self.assertEqual(picking.immediate_transfer, False)
|
||||
self.assertEqual(picking.state, 'draft')
|
||||
|
||||
picking = Form(picking)
|
||||
picking.move_ids_without_package.remove(0)
|
||||
picking = picking.save()
|
||||
self.assertEqual(len(picking.move_ids_without_package), 0)
|
||||
|
||||
def test_additional_move_1(self):
|
||||
""" On a planned trasfer, add a stock move when the picking is already ready. Check that
|
||||
the check availability button appears and work.
|
||||
"""
|
||||
# Make some stock for productA and productB.
|
||||
receipt = self.env['stock.picking'].create({
|
||||
'location_id': self.supplier_location,
|
||||
'location_dest_id': self.stock_location,
|
||||
'picking_type_id': self.picking_type_in,
|
||||
})
|
||||
move_1 = self.MoveObj.create({
|
||||
'name': self.productA.name,
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': self.productA.uom_id.id,
|
||||
'picking_id': receipt.id,
|
||||
'location_id': self.supplier_location,
|
||||
'location_dest_id': self.stock_location,
|
||||
})
|
||||
move_2 = self.MoveObj.create({
|
||||
'name': self.productB.name,
|
||||
'product_id': self.productB.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': self.productB.uom_id.id,
|
||||
'picking_id': receipt.id,
|
||||
'location_id': self.supplier_location,
|
||||
'location_dest_id': self.stock_location,
|
||||
})
|
||||
receipt.action_confirm()
|
||||
move_1.quantity_done = 10
|
||||
move_2.quantity_done = 10
|
||||
receipt.button_validate()
|
||||
self.assertEqual(self.productA.qty_available, 10)
|
||||
self.assertEqual(self.productB.qty_available, 10)
|
||||
|
||||
# Create a delivery for 1 productA, reserve, check the picking is ready
|
||||
delivery_order = self.env['stock.picking'].create({
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location,
|
||||
'picking_type_id': self.picking_type_out,
|
||||
'move_type': 'one',
|
||||
})
|
||||
move_3 = self.MoveObj.create({
|
||||
'name': self.productA.name,
|
||||
'product_id': self.productA.id,
|
||||
'product_uom_qty': 10,
|
||||
'product_uom': self.productA.uom_id.id,
|
||||
'picking_id': delivery_order.id,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location,
|
||||
})
|
||||
delivery_order.action_confirm()
|
||||
delivery_order.action_assign()
|
||||
self.assertEqual(delivery_order.state, 'assigned')
|
||||
|
||||
# Add a unit of productB, the check_availability button should appear.
|
||||
delivery_order = Form(delivery_order)
|
||||
with delivery_order.move_ids_without_package.new() as move:
|
||||
move.product_id = self.productB
|
||||
move.product_uom_qty = 10
|
||||
delivery_order = delivery_order.save()
|
||||
|
||||
# The autocoform ran, the picking shoud be confirmed and reservable.
|
||||
self.assertEqual(delivery_order.state, 'confirmed')
|
||||
self.assertEqual(delivery_order.show_mark_as_todo, False)
|
||||
self.assertEqual(delivery_order.show_check_availability, True)
|
||||
|
||||
delivery_order.action_assign()
|
||||
self.assertEqual(delivery_order.state, 'assigned')
|
||||
self.assertEqual(delivery_order.show_check_availability, False)
|
||||
self.assertEqual(delivery_order.show_mark_as_todo, False)
|
||||
|
||||
stock_location = self.env['stock.location'].browse(self.stock_location)
|
||||
self.assertEqual(self.env['stock.quant']._gather(self.productA, stock_location).reserved_quantity, 10.0)
|
||||
self.assertEqual(self.env['stock.quant']._gather(self.productB, stock_location).reserved_quantity, 10.0)
|
||||
|
||||
def test_additional_move_2(self):
|
||||
""" On an immediate trasfer, add a stock move when the picking is already ready. Check that
|
||||
the check availability button doest not appear.
|
||||
"""
|
||||
# Create a delivery for 1 productA, check the picking is ready
|
||||
delivery_order = self.env['stock.picking'].create({
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location,
|
||||
'picking_type_id': self.picking_type_out,
|
||||
'immediate_transfer': True,
|
||||
'move_ids_without_package': [(0, 0, {
|
||||
'name': self.productA.name,
|
||||
'product_id': self.productA.id,
|
||||
'product_uom': self.productA.uom_id.id,
|
||||
'location_id': self.stock_location,
|
||||
'location_dest_id': self.customer_location,
|
||||
})],
|
||||
})
|
||||
self.assertEqual(delivery_order.state, 'assigned')
|
||||
|
||||
# Add a unit of productB, the check_availability button should not appear.
|
||||
delivery_order = Form(delivery_order)
|
||||
with delivery_order.move_ids_without_package.new() as move:
|
||||
move.product_id = self.productB
|
||||
delivery_order = delivery_order.save()
|
||||
|
||||
self.assertEqual(delivery_order.state, 'assigned')
|
||||
self.assertEqual(delivery_order.show_check_availability, False)
|
||||
self.assertEqual(delivery_order.show_mark_as_todo, False)
|
||||
|
||||
def test_owner_1(self):
|
||||
"""Make a receipt, set an owner and validate"""
|
||||
owner1 = self.env['res.partner'].create({'name': 'owner'})
|
||||
|
||||
Reference in New Issue
Block a user