Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[9.0][REF]stock_auto_move: fix action_assign() #373

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion stock_auto_move/__openerp__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
'data/stock_auto_move_data.xml',
'views/stock_move.xml',
'views/procurement_rule.xml',

'views/stock_location_path.xml',
],
'demo': [
'demo/stock_auto_move_demo.xml',
Expand Down
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
18 changes: 13 additions & 5 deletions stock_auto_move/models/stock_location_path.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
# -*- coding: utf-8 -*-
# © 2014-2015 NDP Systèmes (<http://www.ndp-systemes.fr>)

from openerp import api, models
from openerp import api, fields, models


class StockLocationPath(models.Model):

_inherit = 'stock.location.path'

auto_confirm = fields.Boolean(
help="If this option is selected, the generated moves will be "
"automatically processed as soon as the products are available. "
"This can be useful for situations with chained moves where we "
"do not want an operator action."
)

@api.model
def _apply(self, rule, move):
"""Set auto move to the new move created by push rule."""
move.auto_move = rule.auto == 'transparent'
return super(StockLocationPath, self)._apply(rule, move)
def _prepare_push_apply(self, rule, move):
new_move_vals = super(StockLocationPath, self)._prepare_push_apply(
rule, move)
new_move_vals.update({'auto_move': rule.auto_confirm})
return new_move_vals
50 changes: 42 additions & 8 deletions stock_auto_move/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,55 @@ 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):
"""
Add automatic procurement group to moves that aren't related to any
procurement group and are auto moves. The reason behind it, is we
want to group those automatic moves into a same picking rather than
creating a picking for each move.
"""
automatic_group = self.env.ref('stock_auto_move.automatic_group')
moves = self.filtered(
lambda m: m.auto_move and m.group_id != automatic_group)
lambda m: m.auto_move and not m.group_id)
moves.write({'group_id': automatic_group.id})

@api.multi
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'))
18 changes: 18 additions & 0 deletions stock_auto_move/views/stock_location_path.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2017 ACSONE SA/NV
License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). -->

<odoo>

<record model="ir.ui.view" id="stock_location_path_form_view">
<field name="name">stock.location.path.form (in stock_auto_move)</field>
<field name="model">stock.location.path</field>
<field name="inherit_id" ref="stock.stock_location_path_form"/>
<field name="arch" type="xml">
<field name="auto" position="after">
<field name="auto_confirm"/>
</field>
</field>
</record>

</odoo>