[FIX] sale_mrp: decrease SOL qty with kit after return

If a user decrease the SOL qty of a kit once the return is done, an
unexpcted picking will be created

To reproduce the issue:
1. Create a kit K:
   - With one component
   - There is one component in the stock
2. Create and confirm a SO with 1 x K
3. Process the delivery
4. Return the delivery
5. Set the SOL qty to 0

Error: a useless and unexpected picking is created to return the kit

When setting a new SOL qty, we check if we should create and run a
new procurement to adapt the qty on delivery-side. To do so, we get
the qty being delivered, and we compute the difference with the new
SOL qty. Here is the issue, when getting the qty being delivered, it
returns the old SOL qty (1.0):
https://github.com/odoo/odoo/blob/797f67bcb222142b79a9eee3e4a882ff679af9db/addons/sale_mrp/models/sale.py#L164-L167
This is not true. Since the kit has been returned, the qty being
delivered is zero. As a result, a procurement is created to
compensate that incorrect qty.

The incorrect return value (1.0) comes from [1]. However, that fix
is not correct. Here was the initial issue: To get the qty being
delivered, we use the moves of the components, include the BoM lines
qties and try to compote the kit quantity. To do so, we call
`_compute_kit_quantities` with the components' moves and the kit qty.
However, there is an error: the moves are based on the old kit
quantity, and we give the new kit quantity to the method. This is why
the result was incorrect in the use case of [1].

[1] bafad58353c562a9b79735640c23d3cca004e0b0

OPW-3077209

closes odoo/odoo#113438

X-original-commit: 0b8af1d63d496489ac1608141c9b2dbccc8b7966
Signed-off-by: Adrien Widart <awt@odoo.com>
Signed-off-by: Tiffany Chang <tic@odoo.com>
This commit is contained in:
Adrien Widart (awt)
2023-02-23 09:30:49 +01:00
parent fe7c58f027
commit 037e2a3e79
2 changed files with 51 additions and 3 deletions
+2 -3
View File
@@ -119,14 +119,13 @@ class SaleOrderLine(models.Model):
# kits that are currently in delivery
bom = self.env['mrp.bom']._bom_find(self.product_id, bom_type='phantom')[self.product_id]
if bom:
if not self.product_uom_qty or (previous_product_uom_qty and not previous_product_uom_qty.get(self.id, 0.0)):
return (previous_product_uom_qty and previous_product_uom_qty.get(self.id, 0.0)) or self.qty_delivered
moves = self.move_ids.filtered(lambda r: r.state != 'cancel' and not r.scrapped)
filters = {
'incoming_moves': lambda m: m.location_dest_id.usage == 'customer' and (not m.origin_returned_move_id or (m.origin_returned_move_id and m.to_refund)),
'outgoing_moves': lambda m: m.location_dest_id.usage != 'customer' and m.to_refund
}
order_qty = self.product_uom._compute_quantity(self.product_uom_qty, bom.product_uom_id)
order_qty = previous_product_uom_qty.get(self.id, 0) if previous_product_uom_qty else self.product_uom_qty
order_qty = self.product_uom._compute_quantity(order_qty, bom.product_uom_id)
qty = moves._compute_kit_quantities(self.product_id, order_qty, bom, filters)
return bom.product_uom_id._compute_quantity(qty, self.product_uom)
return super(SaleOrderLine, self)._get_qty_procurement(previous_product_uom_qty=previous_product_uom_qty)
@@ -2110,3 +2110,52 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon):
{'product_id': self.component_f.id, 'product_uom_qty': 0},
{'product_id': self.component_g.id, 'product_uom_qty': 0},
])
def test_kit_return_and_decrease_sol_qty_to_zero(self):
"""
Create and confirm a SO with a kit product.
Deliver & Return the components
Set the SOL qty to 0
"""
stock_location = self.company_data['default_warehouse'].lot_stock_id
grp_uom = self.env.ref('uom.group_uom')
self.env.user.write({'groups_id': [(4, grp_uom.id)]})
# 10 kit_3 = 10 x compo_f + 20 x compo_g
self.env['stock.quant']._update_available_quantity(self.component_f, stock_location, 10)
self.env['stock.quant']._update_available_quantity(self.component_g, stock_location, 20)
so_form = Form(self.env['sale.order'])
so_form.partner_id = self.partner_a
with so_form.order_line.new() as line:
line.product_id = self.kit_3
line.product_uom_qty = 2
line.product_uom = self.uom_ten
so = so_form.save()
so.action_confirm()
delivery = so.picking_ids
for m in delivery.move_ids:
m.quantity_done = m.product_uom_qty
delivery.button_validate()
self.assertEqual(delivery.state, 'done')
self.assertEqual(so.order_line.qty_delivered, 2)
ctx = {'active_id': delivery.id, 'active_model': 'stock.picking'}
return_wizard = Form(self.env['stock.return.picking'].with_context(ctx)).save()
return_picking_id, dummy = return_wizard._create_returns()
return_picking = self.env['stock.picking'].browse(return_picking_id)
for m in return_picking.move_ids:
m.quantity_done = m.product_uom_qty
return_picking.button_validate()
self.assertEqual(return_picking.state, 'done')
self.assertEqual(so.order_line.qty_delivered, 0)
with Form(so) as so_form:
with so_form.order_line.edit(0) as line:
line.product_uom_qty = 0
self.assertEqual(so.picking_ids, delivery | return_picking)