[FIX] sale_stock: avoid traceback when returning a product

Steps to reproduce the bug:
- Create a storable product “P1”:
    - Tracking: SN
    - Update the qty: 1 unit, “SN1”

- Create a SO with and validate it
- Go to the delivery and validate it
- Create a return and validate
- Create a return of return

Problem:
error message: RecursionError: maximum recursion depth exceeded while
calling a Python object

The `_get_origin_moves` and `_get_destination_moves` functions add the
list of origin or destination moves, but the functions themselves are
called with that list, creating an infinite loop.

opw-3659818

closes odoo/odoo#148031

Related: odoo/enterprise#53695
Signed-off-by: William Henrotin (whe) <whe@odoo.com>
This commit is contained in:
Djamel Touati
2024-01-09 20:51:45 +00:00
parent de69aab5fe
commit 3273f028b8
3 changed files with 53 additions and 14 deletions
+3 -14
View File
@@ -46,20 +46,9 @@ class StockMove(models.Model):
return self.sale_line_id.order_id or res
def _get_sale_order_lines(self):
""" Return all possible sale order lines for one or multiple stock moves. """
def _get_origin_moves(move):
origin_moves = move.move_orig_ids
if origin_moves:
origin_moves += _get_origin_moves(origin_moves)
return origin_moves
def _get_destination_moves(move):
destination_moves = move.move_dest_ids
if destination_moves:
destination_moves += _get_destination_moves(destination_moves)
return destination_moves
return (self + _get_origin_moves(self) + _get_destination_moves(self)).sale_line_id
""" Return all possible sale order lines for one stock move. """
self.ensure_one()
return (self + self.browse(self._rollup_move_origs() | self._rollup_move_dests())).sale_line_id
def _assign_picking_post_process(self, new=False):
super(StockMove, self)._assign_picking_post_process(new=new)
@@ -1605,3 +1605,51 @@ class TestSaleStock(TestSaleCommon, ValuationReconciliationTestCommon):
{'product_id': self.product_a.id, 'product_uom_qty': 0, 'quantity': 0, 'state': 'cancel'},
{'product_id': self.product_b.id, 'product_uom_qty': 1, 'quantity': 0, 'state': 'waiting'},
])
def test_create_so_return_with_tracked_product(self):
"""
Creates a sale order with a tracked product, validates it and its delivery, then creates a
return validates it and finally creates a second return.
"""
self.product_a.tracking = 'serial'
self.product_a.type = 'product'
sn1 = self.env['stock.lot'].create({
'name': 'SN0001',
'product_id': self.product_a.id,
'company_id': self.env.company.id,
})
self.env['stock.quant']._update_available_quantity(self.product_a, self.company_data['default_warehouse'].lot_stock_id, 1, lot_id=sn1)
# Creates a sale order for 1 tracked product.
sale_order = self._get_new_sale_order(amount=1, product=self.product_a)
# validates the sale order, then validates the delivery.
sale_order.action_confirm()
self.assertTrue(sale_order.picking_ids)
picking = sale_order.picking_ids
picking.button_validate()
# Checks the delivery amount (must be 1).
self.assertEqual(sale_order.order_line.qty_delivered, 1)
# Creates a return from the delivery picking.
return_picking_form = Form(self.env['stock.return.picking']
.with_context(active_ids=picking.ids, active_id=picking.id,
active_model='stock.picking'))
return_wizard = return_picking_form.save()
self.assertEqual(return_wizard.product_return_moves.quantity, 1)
# validates the return picking.
res = return_wizard.create_returns()
return_picking = self.env['stock.picking'].browse(res['res_id'])
return_picking.button_validate()
# Checks the delivery amount (must be 0).
self.assertEqual(sale_order.order_line.qty_delivered, 0)
return_picking_form = Form(self.env['stock.return.picking']
.with_context(active_ids=return_picking.ids, active_id=return_picking.id,
active_model='stock.picking'))
return_wizard = return_picking_form.save()
self.assertEqual(return_wizard.product_return_moves.quantity, 1)
# validates the return picking.
res = return_wizard.create_returns()
return_picking_2 = self.env['stock.picking'].browse(res['res_id'])
return_picking_2.button_validate()
self.assertEqual(return_wizard.product_return_moves.quantity, 1)
+2
View File
@@ -2143,6 +2143,7 @@ Please change the quantity done or the rounding precision of your unit of measur
moves_to_reserve._action_assign()
def _rollup_move_dests(self, seen=False):
self.ensure_one()
if not seen:
seen = OrderedSet()
if self.id in seen:
@@ -2153,6 +2154,7 @@ Please change the quantity done or the rounding precision of your unit of measur
return seen
def _rollup_move_origs(self, seen=False):
self.ensure_one()
if not seen:
seen = OrderedSet()
if self.id in seen: