[FIX] {sale_,}stock: avoid cancel propagation
If the 'cancel next move' feature is enabled, canceling a SOL could
lead to unexpected picking creation
To reproduce the issue:
(Debug mode enabled)
1. In Settings, enable "Multi-Step Routes"
2. Edit the warehouse:
- Outgoing shipments: 2 steps
3. Edit the delivery route:
- For each rule:
- Cancel next move: True
4. Confirm a SO with one product
5. Set the SOL quantity to 0
Error: A third picking is created, from customer to output location
Step 4, it creates two SM:
\- SM_SO: from Stock to Ouput
\- SM_OC: from Output to Customer
Step 5, thanks to the procurement process, we create a stock move:
\- SM_OC_neg, from Output to Customer with a negative qty.
While confirming this SM, and thanks to the same process, we then
create a second stock move :
\- SM_SO_neg, from Stock to Output, with a negative qty.
Both negative SM are linked. While confirming SM_SO_neg, we merge it
with SM_SO. Therefore:
\- Dest moves of SM_SO_neg are given to SM_SO
\- SM_SO_neg is deleted (fully absorbed by SM_SO)
\- SM_SO has now a zero demand, so we cancel it:
https://github.com/odoo/odoo/blob/bd7aadf589ef1ba4556164bc70fc0fbb62928e48/addons/stock/models/stock_move.py#L1024-L1025
However, because of step 3, we also cancel its dest moves:
https://github.com/odoo/odoo/blob/bd7aadf589ef1ba4556164bc70fc0fbb62928e48/addons/stock/models/stock_move.py#L1740-L1745
i.e., we cancel SM_OC *and* SM_OC_neg. This leads to an inconsistency:
Back to the confirmation of SM_SO_neg. As explained, this SM has
been canceled during the procurement process. Still, we keep
processing its confirmation (we overwrite its state, we assign it to a
picking, and so on). Hence the error.
In `_merge_moves`, when deleting a stock move, we first clean it
(for instance, we disable the cancel propagation):
https://github.com/odoo/odoo/blob/bd7aadf589ef1ba4556164bc70fc0fbb62928e48/addons/stock/models/stock_move.py#L1018-L1020
We should do the same with SMs we are going to cancel. That way, we
fix the root cause of the issue: when cancelling SM_SO, we don't
cancel neither SM_OC neither SM_OC_neg, so we don't have any
inconsistency when going back to the confirmation of SM_SO_neg, and
everything will work correctly.
OPW-3753453
closes odoo/odoo#158663
X-original-commit: 7f385686b35b36561cfe9d2a320ff1699547efd2
Signed-off-by: William Henrotin (whe) <whe@odoo.com>
Signed-off-by: Adrien Widart (awt) <awt@odoo.com>
This commit is contained in:
@@ -1680,6 +1680,34 @@ class TestSaleStock(TestSaleCommon, ValuationReconciliationTestCommon):
|
||||
return_picking_2.button_validate()
|
||||
self.assertEqual(return_wizard.product_return_moves.quantity, 1)
|
||||
|
||||
def test_2_steps_decrease_sol_qty_to_zero(self):
|
||||
"""
|
||||
2 steps delivery, 'cancel next move' enabled
|
||||
SO with one product
|
||||
On the SO, cancel the qty of the product
|
||||
On each picking, the SM should be canceled
|
||||
"""
|
||||
warehouse = self.env['stock.warehouse'].search([('company_id', '=', self.env.company.id)], limit=1)
|
||||
|
||||
warehouse.delivery_steps = 'pick_ship'
|
||||
warehouse.delivery_route_id.rule_ids.propagate_cancel = True
|
||||
|
||||
so = self.env['sale.order'].create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': self.product_a.name,
|
||||
'product_id': self.product_a.id,
|
||||
'product_uom_qty': 1,
|
||||
'product_uom': self.product_a.uom_id.id,
|
||||
'price_unit': self.product_a.list_price,
|
||||
})],
|
||||
})
|
||||
so.action_confirm()
|
||||
|
||||
so.order_line.product_uom_qty = 0
|
||||
|
||||
self.assertEqual(so.picking_ids.move_ids.mapped('state'), ['cancel', 'cancel'])
|
||||
|
||||
def test_2_steps_fixed_procurement_propagation_with_backorder(self):
|
||||
"""
|
||||
When validating a picking (partially coming from a backorder) linked to 2 destinations moves in a 2-steps delivery,
|
||||
|
||||
@@ -1053,9 +1053,10 @@ Please change the quantity done or the rounding precision of your unit of measur
|
||||
pos_move.product_uom_qty = 0
|
||||
moves_to_cancel |= pos_move
|
||||
|
||||
# We are using propagate to False in order to not cancel destination moves merged in moves[0]
|
||||
(moves_to_unlink | moves_to_cancel)._clean_merged()
|
||||
|
||||
if moves_to_unlink:
|
||||
# We are using propagate to False in order to not cancel destination moves merged in moves[0]
|
||||
moves_to_unlink._clean_merged()
|
||||
moves_to_unlink._action_cancel()
|
||||
moves_to_unlink.sudo().unlink()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user