Skip to content

Commit

Permalink
[REF]stock_auto_move: fix action_assign()
Browse files Browse the repository at this point in the history
**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 committed Oct 2, 2017
1 parent 100ad9a commit eb84780
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 7 deletions.
3 changes: 3 additions & 0 deletions stock_auto_move/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
# -*- 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_pack_operation
from . import stock_picking
41 changes: 34 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,42 @@ 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()
res = super(StockMove, self).action_assign(no_prepare=no_prepare)

# Use a variable in the context to whether check
# the auto moves or not otherwise we will have an
# infinite loop
if self.env.context.get('no_check_auto_moves'):
return res

# Select auto moves that are assigned
auto_moves = self.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
28 changes: 28 additions & 0 deletions stock_auto_move/models/stock_pack_operation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# -*- 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 StockPackOperation(models.Model):

_inherit = 'stock.pack.operation'

@api.multi
def _auto_fill_pack_lot_ids_qty(self):
"""
This method automatically fills quantites of the lots
used in operations and also update the qty done on
those operations.
This method is meant to be used with automatic moves.
"""
operations = self.filtered(
lambda o: o.pack_lot_ids and o.product_id and
not o.package_id and not o.result_package_id)
for operation in operations:
for pack_lot in operations.mapped('pack_lot_ids'):
if not pack_lot.qty:
pack_lot.qty = pack_lot.qty_todo
operation.qty_done = operation.product_qty
return True
77 changes: 77 additions & 0 deletions stock_auto_move/models/stock_picking.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# -*- 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 immediate transfer wizard so it will fill the qty_done
# on the auto move linked operation
wizard = self.env['stock.immediate.transfer'].create(
{'pick_id': picking.id})
wizard.process()
else:
# create back order with the auto moves
backorder = picking.copy(
default={
'name': '/',
'move_lines': [],
'backorder_id': picking.id})
# move the operations related to the auto moves to the new
# picking
auto_moves_by_pickings[picking].write(
{'picking_id': backorder.id})
auto_moves_by_pickings[picking].mapped(
'linked_move_operation_ids.operation_id').write({
'picking_id': backorder.id})
backorder.action_confirm()
backorder.with_context(
no_check_auto_moves=True).action_assign()

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

return

@api.multi
def _add_pack_ops_serials(self):
""" For pickings with operations matching certain condition, this
method select automatically the lots to be used in the transfer.
The conditions are:
- The tracking on the operation product has to be set to serial.
- The qty to process is set.
"""
for rec in self:
if not all([m.auto_move for m in rec.move_lines]):
continue
auto_select_ops = rec.pack_operation_ids.filtered(
lambda op:
op.product_id.tracking != 'none' and
op.pack_lot_ids and op.qty_done)
for operation in auto_select_ops:
selected_lots = \
list(operation.pack_lot_ids)[:int(operation.qty_done)]
for lot in selected_lots:
lot.do_plus()

@api.multi
def do_transfer(self):
self._add_pack_ops_serials()
return super(StockPicking, self).do_transfer()
131 changes: 131 additions & 0 deletions stock_auto_move/tests/test_stock_auto_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,134 @@ 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'
self.product_a1232.tracking = 'serial'
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.write({
'pack_lot_ids': [
(0, 0, {'lot_name': 'Test 1', 'qty': 1.0, }),
(0, 0, {'lot_name': 'Test 2', 'qty': 1.0, })],
'qty_done': picking.pack_operation_ids.product_qty,
})
picking.do_new_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'
self.product_a1232.tracking = 'serial'
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.write({
'pack_lot_ids': [
(0, 0, {'lot_name': 'Test 1', 'qty': 1.0, })],
'qty_done': 1.0,
})
result = picking.do_new_transfer()

# process the back order wizard
self.assertTrue(result)
self.assertTrue('res_id' in result)
back_order_wiz_id = self.env['stock.backorder.confirmation'].browse(
result['res_id'])
back_order_wiz_id.process()

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.write({
'pack_lot_ids': [
(0, 0, {'lot_name': 'Test 2', 'qty': 1.0, })],
'qty_done': picking.pack_operation_ids.product_qty,
})
back_order.do_new_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')

0 comments on commit eb84780

Please sign in to comment.