From 707cd830f8ad80ca72e0cfc3702cc062a7cd43d0 Mon Sep 17 00:00:00 2001 From: "Nathan Marotte (nama)" Date: Wed, 18 Aug 2021 12:49:44 +0000 Subject: [PATCH] [FIX] sale_mrp : wrong delivered quantities on kit return Issue: When returning a product sold that is only made of kits and validating the return, the delivered quantities of the sales order was set back to the full amount delivered Steps to reproduce : 1) Install MRP and Sales 2) Create a BoM Kit for a new product [Nested Kit] that has one or more consumable or storable product as component 3) Create a BoM Kit for a new product [Main Kit] that has [Nested Kit] as component 4) Create a SO for [Main Kit], confirm, validate delivery 5) Check SO, 1 product is delivered (correct) 6) Go back to the Delivery, create a return for the delivery (don't validate) 7) Check SO, 0 product is delivered (correct) 8) Now validate the return for the delivery -> Check SO, 1 product is delivered (bug) Why is that a bug: Since the Main Kit was returned, the delivered should be 0 and not the full amount initially delivered. It was set back to 1 because we didn't look at the type of picking, when computing the quantity delivered, the fall back considered that if all the moves were done, everything was delivered, but it is the opposite when returning opw-2542337 closes odoo/odoo#78915 X-original-commit: 206c9c8da4d8001525a540561174326e49d1188d Signed-off-by: Arnold Moyaux Signed-off-by: William Henrotin (whe) --- addons/sale_mrp/models/sale.py | 3 +- addons/sale_mrp/tests/test_sale_mrp_flow.py | 89 +++++++++++++++++++++ 2 files changed, 91 insertions(+), 1 deletion(-) diff --git a/addons/sale_mrp/models/sale.py b/addons/sale_mrp/models/sale.py index 707c534bc3a..126dedb78b0 100644 --- a/addons/sale_mrp/models/sale.py +++ b/addons/sale_mrp/models/sale.py @@ -114,7 +114,8 @@ class SaleOrderLine(models.Model): # when the product sold is made only of kits. In this case, the BOM of the stock moves # do not correspond to the product sold => no relevant BOM. elif boms: - if all(m.state == 'done' for m in order_line.move_ids): + # if the move is ingoing, the product **sold** has delivered qty 0 + if all(m.state == 'done' and m.location_dest_id.usage == 'customer' for m in order_line.move_ids): order_line.qty_delivered = order_line.product_uom_qty else: order_line.qty_delivered = 0.0 diff --git a/addons/sale_mrp/tests/test_sale_mrp_flow.py b/addons/sale_mrp/tests/test_sale_mrp_flow.py index 722e8444358..cd217d037d0 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_flow.py +++ b/addons/sale_mrp/tests/test_sale_mrp_flow.py @@ -1755,3 +1755,92 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon): check_cogs_entry_values(self.invoice_1, 40) # Check that the cost of Good Sold entries for variant 2 are equal to 2 * 10 = 20 check_cogs_entry_values(self.invoice_2, 20) + + def test_13_so_return_kit(self): + """ + Test that when returning a SO containing only a kit that contains another kit, the + SO delivered quantities is set to 0 (with the all-or-nothing policy). + Products : + Main Kit + Nested Kit + Screw + BoMs : + Main Kit BoM (kit), recipe : + Nested Kit Bom (kit), recipe : + Screw + Business flow : + Create those + Create a Sales order selling one Main Kit BoM + Confirm the sales order + Validate the delivery (outgoing) (qty_delivered = 1) + Create a return for the delivery + Validate return for delivery (ingoing) (qty_delivered = 0) + """ + main_kit_product = self.env['product.product'].create({ + 'name': 'Main Kit', + 'type': 'product', + }) + + nested_kit_product = self.env['product.product'].create({ + 'name': 'Nested Kit', + 'type': 'product', + }) + + product = self.env['product.product'].create({ + 'name': 'Screw', + 'type': 'product', + }) + + self.env['mrp.bom'].create({ + 'product_id': nested_kit_product.id, + 'product_tmpl_id': nested_kit_product.product_tmpl_id.id, + 'product_qty': 1.0, + 'type': 'phantom', + 'bom_line_ids': [(5, 0), (0, 0, {'product_id': product.id})] + }) + + self.env['mrp.bom'].create({ + 'product_id': main_kit_product.id, + 'product_tmpl_id': main_kit_product.product_tmpl_id.id, + 'product_qty': 1.0, + 'type': 'phantom', + 'bom_line_ids': [(5, 0), (0, 0, {'product_id': nested_kit_product.id})] + }) + + # Create a SO for product Main Kit Product + order_form = Form(self.env['sale.order']) + order_form.partner_id = self.env.ref('base.res_partner_2') + with order_form.order_line.new() as line: + line.product_id = main_kit_product + line.product_uom_qty = 1 + order = order_form.save() + order.action_confirm() + qty_del_not_yet_validated = sum(sol.qty_delivered for sol in order.order_line) + self.assertEqual(qty_del_not_yet_validated, 0.0, 'No delivery validated yet') + + # Validate delivery + pick = order.picking_ids + pick.move_ids.write({'quantity_done': 1}) + pick.button_validate() + qty_del_validated = sum(sol.qty_delivered for sol in order.order_line) + self.assertEqual(qty_del_validated, 1.0, 'The order went from warehouse to client, so it has been delivered') + + # 1 was delivered, now create a return + stock_return_picking_form = Form(self.env['stock.return.picking'].with_context( + active_ids=pick.ids, active_id=pick.ids[0], active_model='stock.picking')) + return_wiz = stock_return_picking_form.save() + for return_move in return_wiz.product_return_moves: + return_move.write({ + 'quantity': 1, + 'to_refund': True + }) + res = return_wiz.create_returns() + return_pick = self.env['stock.picking'].browse(res['res_id']) + return_pick.move_line_ids.qty_done = 1 + return_pick.button_validate() # validate return + + # Delivered quantities to the client should be 0 + qty_del_return_validated = sum(sol.qty_delivered for sol in order.order_line) + self.assertNotEqual(qty_del_return_validated, 1.0, "The return was validated, therefore the delivery from client to" + " company was successful, and the client is left without his 1 product.") + self.assertEqual(qty_del_return_validated, 0.0, "The return has processed, client doesn't have any quantity anymore")