Skip to content

Commit

Permalink
[9.0][REF]stock_auto_move: fix action_assign() (#373)
Browse files Browse the repository at this point in the history
* The `_apply` method should not be overridden. In case of three step
operation with the last step set to auto move. The override of the
`_apply` method was causing the second move to be automatic as well.

**Use case**: doing a reception operation in two steps where the moves on
the second step are set to be automatic (using the push rule in this
case), and the reception is done partially.
**Expected Result**: When doing a first reception step partially a back
order is created, and we expect the second step to have a back order for
the remaining qty as well.
**Current behavior**: The correct move is processed automatically but no
backorder is created. The reason for that behavior is we processing the
move directly (i.e calling `action_done` of the `stock.move`) without
going through the normal process (i.e processing the second step
picking).
**The solution to the problem**: The processing the of the automatic
stock move should follow the same behavior as done from the user interface.
I've added a new function in `stock.picking` model called
`_transfer_pickings_with_auto_move` to simulate the same behavior this
function is called inside the `action_assign` of the stock move instead
of calling the `action_done` of the move.
  • Loading branch information
zakiuu authored and pedrobaeza committed Oct 1, 2018
1 parent cf0b656 commit a851aad
Show file tree
Hide file tree
Showing 4 changed files with 261 additions and 7 deletions.
2 changes: 2 additions & 0 deletions stock_auto_move/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
from . import stock_move
from . import procurement_rule
from . import procurement_order
from . import stock_location_path
from . import stock_picking
42 changes: 35 additions & 7 deletions stock_auto_move/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,43 @@ class StockMove(models.Model):
help="If this option is selected, the move will be automatically "
"processed as soon as the products are available.")

@api.model
def _get_auto_moves_by_pickings(self, auto_moves):
""" Group moves by picking.
@param auto_moves: stock.move data set
@return dict dict of moves grouped by pickings
{stock.picking(id): stock.move(id1, id2, id3 ...), ...}
"""
auto_moves_by_pickings = dict()
for move in auto_moves:
if move.picking_id in auto_moves_by_pickings:
auto_moves_by_pickings[move.picking_id] |= move
else:
auto_moves_by_pickings.update({move.picking_id: move})
return auto_moves_by_pickings

@api.multi
def action_assign(self, no_prepare=False):
super(StockMove, self).action_assign(no_prepare=no_prepare)
# Transfer all pickings which have an auto move assigned
moves = self.filtered(lambda m: m.state == 'assigned' and m.auto_move)
todo_pickings = moves.mapped('picking_id')
# We create packing operations to keep packing if any
todo_pickings.do_prepare_partial()
moves.action_done()

already_assigned_moves = self.filtered(
lambda m: m.state == 'assigned')

not_assigned_auto_move = self - already_assigned_moves

res = super(StockMove, self).action_assign(
no_prepare=no_prepare)

# Process only moves that have been processed recently
auto_moves = not_assigned_auto_move.filtered(
lambda m: m.state == 'assigned' and m.auto_move)

# group the moves by pickings
auto_moves_by_pickings = self._get_auto_moves_by_pickings(auto_moves)

# process the moves by creating backorders
self.env['stock.picking']._transfer_pickings_with_auto_move(
auto_moves_by_pickings)
return res

@api.multi
def _change_procurement_group(self):
Expand Down
35 changes: 35 additions & 0 deletions stock_auto_move/models/stock_picking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# -*- coding: utf-8 -*-
# Copyright 2017 ACSONE SA/NV
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).

from openerp import api, models


class StockPicking(models.Model):

_inherit = 'stock.picking'

@api.model
def _transfer_pickings_with_auto_move(self, auto_moves_by_pickings):
"""This function is meant to simulate what a user would normally
transfer a picking from the user interface either partial processing
or full processing.
@params auto_moves_by_pickings: dict of moves grouped by pickings
{stock.picking(id): stock.move(id1, id2, id3 ...), ...}
"""
for picking in auto_moves_by_pickings:
if len(picking.move_lines) != len(auto_moves_by_pickings[picking]):
# Create a back order for remaning moves
backorder_moves = \
picking.move_lines - auto_moves_by_pickings[picking]
self._create_backorder(
picking=picking, backorder_moves=backorder_moves)

# Create immediate transfer wizard so it will fill the qty_done
# on the auto move linked operation
picking.do_prepare_partial()
wizard = self.env['stock.immediate.transfer'].create(
{'pick_id': picking.id})
wizard.process()

return
189 changes: 189 additions & 0 deletions stock_auto_move/tests/test_stock_auto_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ class TestStockAutoMove(common.TransactionCase):
def setUp(self):
super(TestStockAutoMove, self).setUp()
self.product_a1232 = self.browse_ref("product.product_product_6")
self.product_2 = self.env.ref("product.product_product_9")
self.location_shelf = self.browse_ref(
"stock.stock_location_components")
self.location_1 = self.browse_ref("stock_auto_move.stock_location_a")
Expand Down Expand Up @@ -144,3 +145,191 @@ def test_30_push_rule_auto(self):
('location_id', '=', self.location_1.id)])
self.assertEqual(len(quants_in_3), 0)
self.assertGreater(len(quants_in_1), 0)

def test_40_chained_auto_move(self):
"""
Test case:
- product with tracking set to serial.
- warehouse reception steps set to two steps.
- the push rule on the reception route set to auto move.
- create movement using the reception picking type.
Expected Result:
The second step movement should be processed automatically
after processing the first movement.
"""
warehouse = self.env.ref('stock.warehouse0')
warehouse.reception_steps = 'two_steps'
warehouse.reception_route_id.push_ids.auto_confirm = True
warehouse.int_type_id.use_create_lots = False
warehouse.int_type_id.use_existing_lots = True

picking = self.env['stock.picking'].with_context(
default_picking_type_id=warehouse.in_type_id.id).create({
'partner_id': self.env.ref('base.res_partner_1').id,
'picking_type_id': warehouse.in_type_id.id,
'group_id': self.auto_group_id,
'location_id':
self.env.ref('stock.stock_location_suppliers').id})

move1 = self.env["stock.move"].create({
'name': "Supply source location for test",
'product_id': self.product_a1232.id,
'product_uom': self.product_uom_unit_id,
'product_uom_qty': 2,
'picking_id': picking.id,
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': warehouse.wh_input_stock_loc_id.id,
'picking_type_id': warehouse.in_type_id.id,
})
picking.action_confirm()
self.assertTrue(picking.pack_operation_ids)
self.assertEqual(len(picking.pack_operation_ids), 1)
picking.pack_operation_ids.qty_done = 2
picking.do_transfer()
self.assertTrue(move1.move_dest_id)

self.assertTrue(move1.move_dest_id.auto_move)
self.assertEqual(move1.move_dest_id.state, 'done')

def test_50_partial_chained_auto_move(self):
"""
Test case:
- product with tracking set to serial.
- warehouse reception steps set to two steps.
- the push rule on the reception route set to auto move.
- create picking using the reception picking type.
- do partial reception on first step
Expected Result:
The second step movement should be processed automatically
and a back order is created.
"""
warehouse = self.env.ref('stock.warehouse0')
warehouse.reception_steps = 'two_steps'
warehouse.reception_route_id.push_ids.auto_confirm = True
warehouse.int_type_id.use_create_lots = False
warehouse.int_type_id.use_existing_lots = True

picking = self.env['stock.picking'].with_context(
default_picking_type_id=warehouse.in_type_id.id).create({
'partner_id': self.env.ref('base.res_partner_1').id,
'picking_type_id': warehouse.in_type_id.id,
'group_id': self.auto_group_id,
'location_id':
self.env.ref('stock.stock_location_suppliers').id})

move1 = self.env["stock.move"].create({
'name': "Supply source location for test",
'product_id': self.product_a1232.id,
'product_uom': self.product_uom_unit_id,
'product_uom_qty': 2,
'picking_id': picking.id,
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': warehouse.wh_input_stock_loc_id.id,
'picking_type_id': warehouse.in_type_id.id,
})
picking.action_confirm()
self.assertTrue(picking.pack_operation_ids)
self.assertEqual(len(picking.pack_operation_ids), 1)
picking.pack_operation_ids.qty_done = 1
picking.pack_operation_ids.product_qty = 1
picking.do_transfer()

self.assertTrue(move1.move_dest_id)
self.assertEqual(len(move1.move_dest_id), 1)

self.assertTrue(move1.move_dest_id.auto_move)
self.assertEqual(move1.move_dest_id.state, 'done')

# look up for the back order created
back_order = self.env['stock.picking'].search(
[('backorder_id', '=', picking.id)])
self.assertTrue(back_order)
self.assertEqual(len(back_order), 1)

back_order.pack_operation_ids.qty_done = 1
back_order.do_transfer()

move2 = back_order.move_lines
self.assertTrue(move2.move_dest_id)
self.assertEqual(len(move2.move_dest_id), 1)

self.assertTrue(move2.move_dest_id.auto_move)
self.assertEqual(move2.move_dest_id.state, 'done')

def test_60_partial_chained_auto_move(self):
"""
Test case:
- product with tracking set to serial.
- warehouse reception steps set to two steps.
- create picking with two move lines.
- set one of the move on the second step picking to be an auto
move.
- do partial reception on first step
Expected Result:
The second step movement should be processed automatically
and a back order is created with the product that is not set as an
auto move.
"""
warehouse = self.env.ref('stock.warehouse0')
warehouse.reception_steps = 'two_steps'
warehouse.reception_route_id.push_ids.auto_confirm = True
warehouse.int_type_id.use_create_lots = False
warehouse.int_type_id.use_existing_lots = True

picking = self.env['stock.picking'].with_context(
default_picking_type_id=warehouse.in_type_id.id).create({
'partner_id': self.env.ref('base.res_partner_1').id,
'picking_type_id': warehouse.in_type_id.id,
'group_id': self.auto_group_id,
'location_id':
self.env.ref('stock.stock_location_suppliers').id})

move1 = self.env["stock.move"].create({
'name': "Supply source location for test",
'product_id': self.product_a1232.id,
'product_uom': self.product_uom_unit_id,
'product_uom_qty': 2,
'picking_id': picking.id,
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': warehouse.wh_input_stock_loc_id.id,
'picking_type_id': warehouse.in_type_id.id,
})

move2 = self.env["stock.move"].create({
'name': "Supply source location for test",
'product_id': self.product_2.id,
'product_uom': self.product_uom_unit_id,
'product_uom_qty': 2,
'picking_id': picking.id,
'location_id': self.env.ref('stock.stock_location_suppliers').id,
'location_dest_id': warehouse.wh_input_stock_loc_id.id,
'picking_type_id': warehouse.in_type_id.id,
})

picking.action_confirm()
self.assertTrue(move1.move_dest_id.auto_move)
self.assertTrue(move2.move_dest_id.auto_move)
second_step_picking = move2.move_dest_id.picking_id
move2.move_dest_id.auto_move = False

# do partial reception of the first picking
move1.linked_move_operation_ids.operation_id.qty_done = 2
move1.linked_move_operation_ids.operation_id.product_qty = 2

move2.linked_move_operation_ids.operation_id.qty_done = 1
move2.linked_move_operation_ids.operation_id.product_qty = 1

picking.do_transfer()

second_step_back_order = self.env['stock.picking'].search(
[('backorder_id', '=', second_step_picking.id)])

self.assertEqual(second_step_picking.state, 'done')
self.assertEqual(len(second_step_picking.move_lines), 1)
self.assertEqual(len(second_step_picking.pack_operation_ids), 1)

self.assertEqual(len(second_step_back_order.move_lines), 2)
self.assertTrue(second_step_back_order.move_lines.filtered(
lambda m: m.state == 'assigned'))
self.assertTrue(second_step_back_order.move_lines.filtered(
lambda m: m.state == 'waiting'))

0 comments on commit a851aad

Please sign in to comment.