Skip to content

Commit

Permalink
new method to validate moves in a separate transfer (camptocamp#72)
Browse files Browse the repository at this point in the history
Calling _action_done() directly on the stock.move while leaving the
stock.picking "assigned" with the other moves already required various
workarounds as it is normally not an expected flow in Odoo.

This method is to be used in services instead of calling _action_done() on
the moves. If a move to set to done contains move lines that should not
be validated, the line to keep should be extracted beforehand with
`move_id.split_other_move_lines`.
  • Loading branch information
sebalix authored and simahawk committed Oct 12, 2020
1 parent cbe4d07 commit 0faa4fe
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 21 deletions.
44 changes: 43 additions & 1 deletion shopfloor/models/stock_move.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from odoo import models
from odoo import _, models


class StockMove(models.Model):
Expand Down Expand Up @@ -36,3 +36,45 @@ def _action_done(self, cancel_backorder=False):
if picking.state == "done":
picking.action_done()
return moves

def extract_and_action_done(self):
"""Extract the moves in a separate transfer and validate them.
You can combine this method with `split_other_move_lines` method
to first extract some move lines in a separate move, then validate it
with this method.
"""
moves = self.filtered(lambda m: m.state == "assigned")
if not moves:
return False
for picking in moves.picking_id:
moves_todo = picking.move_lines & moves
if moves_todo == picking.move_lines:
# No need to create a new transfer if we are processing all moves
new_picking = picking
else:
new_picking = picking.copy(
{
"name": "/",
"move_lines": [],
"move_line_ids": [],
"backorder_id": picking.id,
}
)
new_picking.message_post(
body=_(
"Created from backorder "
"<a href=# data-oe-model=stock.picking data-oe-id=%d>%s</a>."
)
% (picking.id, picking.name)
)
moves_todo.write({"picking_id": new_picking.id})
moves_todo.package_level_id.write({"picking_id": new_picking.id})
moves_todo.move_line_ids.write({"picking_id": new_picking.id})
moves_todo.move_line_ids.package_level_id.write(
{"picking_id": new_picking.id}
)
new_picking.action_assign()
assert new_picking.state == "assigned"
new_picking.action_done()
return True
1 change: 1 addition & 0 deletions shopfloor/tests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,4 @@
from . import test_zone_picking_unload_all
from . import test_zone_picking_unload_set_destination
from . import test_misc
from . import test_stock_split
112 changes: 92 additions & 20 deletions shopfloor/tests/test_stock_split.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def setUpClass(cls):
cls.pack_location = cls.warehouse.wh_pack_stock_loc_id
cls.ship_location = cls.warehouse.wh_output_stock_loc_id
cls.stock_location = cls.env.ref("stock.stock_location_stock")
# Create a product
# Create products
cls.product_a = (
cls.env["product.product"]
.sudo()
Expand All @@ -39,6 +39,30 @@ def setUpClass(cls):
}
)
)
cls.product_b = (
cls.env["product.product"]
.sudo()
.create(
{
"name": "Product B",
"type": "product",
"default_code": "B",
"barcode": "B",
"weight": 2,
}
)
)
cls.product_b_packaging = (
cls.env["product.packaging"]
.sudo()
.create(
{
"name": "Box",
"product_id": cls.product_b.id,
"barcode": "ProductBBox",
}
)
)
# Put product_a quantities in different packages to get several move lines
cls.package_1 = cls.env["stock.quant.package"].create({"name": "PACKAGE_1"})
cls.package_2 = cls.env["stock.quant.package"].create({"name": "PACKAGE_2"})
Expand All @@ -53,6 +77,8 @@ def setUpClass(cls):
cls._update_qty_in_location(
cls.stock_location, cls.product_a, 5, package=cls.package_3
)
# Put product_b quantities in stock
cls._update_qty_in_location(cls.stock_location, cls.product_b, 10)
# Create the pick/pack/ship transfer
cls.ship_move_a = cls.env["stock.move"].create(
{
Expand All @@ -68,12 +94,28 @@ def setUpClass(cls):
"state": "draft",
}
)
cls.ship_move_a._assign_picking()
cls.ship_move_a._action_confirm(merge=False)
cls.pack_move = cls.ship_move_a.move_orig_ids[0]
cls.pick_move = cls.pack_move.move_orig_ids[0]
cls.picking = cls.pick_move.picking_id
cls.packing = cls.pack_move.picking_id
cls.ship_move_b = cls.env["stock.move"].create(
{
"name": cls.product_b.display_name,
"product_id": cls.product_b.id,
"product_uom_qty": 4,
"product_uom": cls.product_b.uom_id.id,
"location_id": cls.ship_location.id,
"location_dest_id": cls.customer_location.id,
"warehouse_id": cls.warehouse.id,
"picking_type_id": cls.warehouse.out_type_id.id,
"procure_method": "make_to_order",
"state": "draft",
}
)
(cls.ship_move_a | cls.ship_move_b)._assign_picking()
(cls.ship_move_a | cls.ship_move_b)._action_confirm(merge=False)
cls.pack_move_a = cls.ship_move_a.move_orig_ids[0]
cls.pick_move_a = cls.pack_move_a.move_orig_ids[0]
cls.pack_move_b = cls.ship_move_b.move_orig_ids[0]
cls.pick_move_b = cls.pack_move_b.move_orig_ids[0]
cls.picking = cls.pick_move_a.picking_id
cls.packing = cls.pack_move_a.picking_id
cls.picking.action_assign()

@classmethod
Expand All @@ -90,27 +132,27 @@ def _update_qty_in_location(
)

def test_split_pickings_from_source_location(self):
dest_location = self.pick_move.location_dest_id.sudo().copy(
dest_location = self.pick_move_a.location_dest_id.sudo().copy(
{
"name": self.pick_move.location_dest_id.name + "_2",
"barcode": self.pick_move.location_dest_id.barcode + "_2",
"location_id": self.pick_move.location_dest_id.id,
"name": self.pick_move_a.location_dest_id.name + "_2",
"barcode": self.pick_move_a.location_dest_id.barcode + "_2",
"location_id": self.pick_move_a.location_dest_id.id,
}
)
# Pick goods from stock and move some of them to a different destination
self.assertEqual(self.pick_move.state, "assigned")
for i, move_line in enumerate(self.pick_move.move_line_ids):
self.assertEqual(self.pick_move_a.state, "assigned")
for i, move_line in enumerate(self.pick_move_a.move_line_ids):
move_line.qty_done = move_line.product_uom_qty
if i % 2:
move_line.location_dest_id = dest_location
self.pick_move.with_context(_sf_no_backorder=True)._action_done()
self.assertEqual(self.pick_move.state, "done")
self.pick_move_a.with_context(_sf_no_backorder=True)._action_done()
self.assertEqual(self.pick_move_a.state, "done")
# Pack step, we want to split move lines from common source location
self.assertEqual(self.pack_move.state, "assigned")
move_lines_to_process = self.pack_move.move_line_ids.filtered(
self.assertEqual(self.pack_move_a.state, "assigned")
move_lines_to_process = self.pack_move_a.move_line_ids.filtered(
lambda ml: ml.location_id == dest_location
)
self.assertEqual(len(self.pack_move.move_line_ids), 3)
self.assertEqual(len(self.pack_move_a.move_line_ids), 3)
self.assertEqual(len(self.packing.package_level_ids), 3)
self.assertEqual(len(move_lines_to_process), 1)
new_packing = move_lines_to_process._split_pickings_from_source_location()
Expand All @@ -120,9 +162,39 @@ def test_split_pickings_from_source_location(self):
self.assertTrue(new_packing != self.packing)
self.assertEqual(new_packing.backorder_id, self.packing)
self.assertEqual(
self.pick_move.move_dest_ids.picking_id, self.packing | new_packing
self.pick_move_a.move_dest_ids.picking_id, self.packing | new_packing
)
self.assertEqual(move_lines_to_process.state, "assigned")
self.assertEqual(
set(self.pack_move.move_line_ids.mapped("state")), {"assigned"}
set(self.pack_move_a.move_line_ids.mapped("state")), {"assigned"}
)

def test_extract_and_action_done_one_move(self):
self.assertFalse(self.picking.backorder_ids)
self.assertEqual(self.picking.state, "assigned")
for move_line in self.pick_move_b.move_line_ids:
move_line.qty_done = move_line.product_uom_qty
self.pick_move_b.extract_and_action_done()
new_picking = self.picking.backorder_ids
self.assertTrue(new_picking)
# Check move lines repartition
self.assertNotIn(self.pick_move_b, self.picking.move_lines)
self.assertEqual(new_picking.move_lines, self.pick_move_b)
# Check states
self.assertEqual(self.picking.state, "assigned")
self.assertEqual(self.pick_move_b.state, "done")
self.assertEqual(new_picking.state, "done")

def test_extract_and_action_done_several_moves(self):
self.assertFalse(self.picking.backorder_ids)
self.assertEqual(self.picking.state, "assigned")
for move_line in self.picking.move_line_ids:
move_line.qty_done = move_line.product_uom_qty
self.picking.move_lines.extract_and_action_done()
# No backorder as all moves of the picking have been validated
new_picking = self.picking.backorder_ids
self.assertFalse(new_picking)
# Check move lines repartition
self.assertEqual(len(self.picking.move_lines), 2)
# Check states
self.assertEqual(self.picking.state, "done")

0 comments on commit 0faa4fe

Please sign in to comment.