Skip to content

Commit

Permalink
[IMP] stock_available_to_promise_release: Improved release process ro…
Browse files Browse the repository at this point in the history
…bustness

Previous to this change, if the release process was 'forced' multiple time on an already released move, the system issued new procumrement rules without thaking into account the qty already released or processed. As side effect, the qty into the picking operation was higher than the expected qty to deliver. We now take into account the qty already released into the release process
  • Loading branch information
lmignon committed Oct 17, 2024
1 parent 77a99cb commit 581644f
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 1 deletion.
30 changes: 29 additions & 1 deletion stock_available_to_promise_release/models/stock_move.py
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ def _run_stock_rule(self):
procurement_requests.append(
self.env["procurement.group"].Procurement(
move.product_id,
move.product_uom_qty,
move._get_qty_to_release(),
move.product_uom,
move.location_id,
move.rule_id and move.rule_id.name or "/",
Expand All @@ -583,6 +583,34 @@ def _run_stock_rule(self):

return released_moves

def _get_qty_to_release(self):
"""Return the qty to release for the move
The qty to release is the move qty minus the qty released for this move
minus the the qty already reserved for the move.
This qty will never exceed the ordered available to promise qty.
"""
self.ensure_one()
released_moves = self.move_orig_ids.filtered(
lambda m: m.state not in ("done", "cancel")
)
all_released_qty = sum(released_moves.mapped("product_uom_qty"))
others_requesting_moves = (
released_moves.move_dest_ids.filtered(
lambda m: m.state not in ("done", "cancel")
)
- self
)
others_moves_requested_qty = sum(
others_requesting_moves.mapped("product_uom_qty")
) - sum(others_requesting_moves.mapped("reserved_availability"))
current_released_qty = all_released_qty - others_moves_requested_qty
to_release = (
self.product_uom_qty - self.reserved_availability - current_released_qty
)
return min(to_release, self.ordered_available_to_promise_qty)

def _before_release(self):
"""Hook that aims to be overridden."""
self._release_set_expected_date()
Expand Down
81 changes: 81 additions & 0 deletions stock_available_to_promise_release/tests/test_reservation.py
Original file line number Diff line number Diff line change
Expand Up @@ -1242,3 +1242,84 @@ def test_release_policy(self):
picking.release_available_to_promise()
new_picking = self._pickings_in_group(picking.group_id) - picking
self.assertEqual(new_picking.move_type, "one")

def test_release_available_multiple_calls(self):
self.wh.delivery_route_id.write({"available_to_promise_defer_pull": True})
# put some qty in output location
self._update_qty_in_location(self.wh.lot_stock_id, self.product1, 5.0)
ship = self._create_picking_chain(self.wh, [(self.product1, 10)])

ship.release_available_to_promise()
pick_pick = ship.move_ids.move_orig_ids.picking_id
self.assertEqual(pick_pick.move_ids.product_uom_qty, 5.0)

ship_backorder = ship.backorder_ids
self.assertTrue(ship_backorder)
self.assertEqual(ship_backorder.move_ids.product_uom_qty, 5.0)
self.assertFalse(ship_backorder.move_ids.move_orig_ids.picking_id)
ships = ship + ship_backorder

# the same call to release_available_to_promise should not create a new picking
# nor change the qty of the existing one
ships.release_available_to_promise()
self.assertEqual(pick_pick, ship.move_ids.move_orig_ids.picking_id)
self.assertEqual(pick_pick.move_ids.product_uom_qty, 5.0)
self.assertEqual(ship_backorder.move_ids.product_uom_qty, 5.0)
self.assertFalse(ship_backorder.move_ids.move_orig_ids.picking_id)

# put more qty in output location
# and force release
self._update_qty_in_location(self.wh.lot_stock_id, self.product1, 10.0)
ships.move_ids.need_release = True

# the release should update the qty of the existing picking to the new qty
# available
ships.release_available_to_promise()
self.assertEqual(pick_pick, ship.move_ids.move_orig_ids.picking_id)
self.assertEqual(pick_pick.move_ids.product_uom_qty, 10.0)
self.assertEqual(ship_backorder.move_ids.move_orig_ids.picking_id, pick_pick)

# partially process the picking
pick_pick.action_assign()
pick_pick.move_line_ids.qty_done = 3.0
pick_pick._action_done()

# the pick should still contain the remaining qty
pick_pick = ship.move_ids.move_orig_ids.filtered(
lambda p: p.state not in ("done", "cancel")
).picking_id
self.assertEqual(pick_pick.move_ids.product_uom_qty, 7.0)

# force release again
ship.move_ids.need_release = True
ship.release_available_to_promise()

# release should take into account already processed qty
self.assertEqual(pick_pick.move_ids.product_uom_qty, 7.0)

# force release of backorder
ship_backorder.move_ids.need_release = True
ship_backorder.release_available_to_promise()
self.assertEqual(pick_pick.move_ids.product_uom_qty, 7.0)

# if we release the two ships at same time, it's without effect
ships.move_ids.need_release = True
ships.release_available_to_promise()
self.assertEqual(pick_pick.move_ids.product_uom_qty, 7.0)

# if we cancel the remaining pick and release again, the new
# picking must be for the remaining qty
pick_pick.action_cancel()
ship.move_ids.need_release = True
ship.release_available_to_promise()

pick_pick = ship.move_ids.move_orig_ids.picking_id.filtered(
lambda p: p.state not in ("done", "cancel")
)
# only the first picking is released -> 5.0 - 3.0 = 2.0
self.assertEqual(pick_pick.move_ids.product_uom_qty, 2.0)

ship_backorder.move_ids.need_release = True
ship_backorder.release_available_to_promise()
# the backorder is for all the remaining qty
self.assertEqual(pick_pick.move_ids.product_uom_qty, 7.0)

0 comments on commit 581644f

Please sign in to comment.