diff --git a/addons/sale_mrp/models/sale_order_line.py b/addons/sale_mrp/models/sale_order_line.py index 9a1b499798c..85cbca703f2 100644 --- a/addons/sale_mrp/models/sale_order_line.py +++ b/addons/sale_mrp/models/sale_order_line.py @@ -109,10 +109,16 @@ class SaleOrderLine(models.Model): def _get_qty_procurement(self, previous_product_uom_qty=False): self.ensure_one() # Specific case when we change the qty on a SO for a kit product. - # We don't try to be too smart and keep a simple approach: we compare the quantity before - # and after update, and return the difference. We don't take into account what was already - # sent, or any other exceptional case. + # We don't try to be too smart and keep a simple approach: we use the quantity of entire + # kits that are currently in delivery bom = self.env['mrp.bom']._bom_find(self.product_id, bom_type='phantom')[self.product_id] if bom: - 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) + 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) diff --git a/addons/sale_mrp/tests/test_sale_mrp_flow.py b/addons/sale_mrp/tests/test_sale_mrp_flow.py index b001b679dd4..e1b596a7fc0 100644 --- a/addons/sale_mrp/tests/test_sale_mrp_flow.py +++ b/addons/sale_mrp/tests/test_sale_mrp_flow.py @@ -43,6 +43,12 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon): cls.uom_unit.write({ 'name': 'Test-Unit', 'rounding': 0.01}) + cls.uom_ten = cls.UoM.create({ + 'name': 'Test-Ten', + 'category_id': cls.categ_unit.id, + 'factor_inv': 10, + 'uom_type': 'bigger', + 'rounding': 0.001}) cls.uom_dozen = cls.UoM.create({ 'name': 'Test-DozenA', 'category_id': cls.categ_unit.id, @@ -1848,3 +1854,77 @@ class TestSaleMrpFlow(ValuationReconciliationTestCommon): price = line.product_id.with_company(line.company_id)._compute_average_price(0, line.product_uom_qty, line.move_ids) self.assertEqual(price, 10) + + def test_kit_decrease_sol_qty(self): + """ + Create and confirm a SO with a qty. Increasing/Decreasing the SOL qty + should update the qty on the delivery. Then, process the delivery, make + a return and adapt the SOL qty -> there should not be any new picking + """ + stock_location = self.company_data['default_warehouse'].lot_stock_id + custo_location = self.env.ref('stock.stock_location_customers') + + grp_uom = self.env.ref('uom.group_uom') + self.env.user.write({'groups_id': [(4, grp_uom.id)]}) + + # 100 kit_3 = 100 x compo_f + 200 x compo_g + self.env['stock.quant']._update_available_quantity(self.component_f, stock_location, 100) + self.env['stock.quant']._update_available_quantity(self.component_g, stock_location, 200) + + 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 = 7 + line.product_uom = self.uom_ten + so = so_form.save() + so.action_confirm() + + delivery = so.picking_ids + self.assertRecordValues(delivery.move_ids, [ + {'product_id': self.component_f.id, 'product_uom_qty': 70}, + {'product_id': self.component_g.id, 'product_uom_qty': 140}, + ]) + + # Decrease + with Form(so) as so_form: + with so_form.order_line.edit(0) as line: + line.product_uom_qty = 6 + self.assertRecordValues(delivery.move_ids, [ + {'product_id': self.component_f.id, 'product_uom_qty': 60}, + {'product_id': self.component_g.id, 'product_uom_qty': 120}, + ]) + + # Increase + with Form(so) as so_form: + with so_form.order_line.edit(0) as line: + line.product_uom_qty = 10 + self.assertRecordValues(delivery.move_ids, [ + {'product_id': self.component_f.id, 'product_uom_qty': 100}, + {'product_id': self.component_g.id, 'product_uom_qty': 200}, + ]) + + delivery.action_set_quantities_to_reservation() + delivery.button_validate() + + # Return 2 [uom_ten] x kit_3 + return_wizard_form = Form(self.env['stock.return.picking'].with_context(active_ids=delivery.ids, active_id=delivery.id, active_model='stock.picking')) + return_wizard = return_wizard_form.save() + return_wizard.product_return_moves[0].quantity = 20 + return_wizard.product_return_moves[1].quantity = 40 + action = return_wizard.create_returns() + return_picking = self.env['stock.picking'].browse(action['res_id']) + return_picking.action_set_quantities_to_reservation() + return_picking.button_validate() + + # Adapt the SOL qty according to the delivered one + with Form(so) as so_form: + with so_form.order_line.edit(0) as line: + line.product_uom_qty = 8 + + self.assertRecordValues(so.picking_ids.move_ids, [ + {'product_id': self.component_f.id, 'location_dest_id': custo_location.id, 'quantity_done': 100, 'state': 'done'}, + {'product_id': self.component_g.id, 'location_dest_id': custo_location.id, 'quantity_done': 200, 'state': 'done'}, + {'product_id': self.component_f.id, 'location_dest_id': stock_location.id, 'quantity_done': 20, 'state': 'done'}, + {'product_id': self.component_g.id, 'location_dest_id': stock_location.id, 'quantity_done': 40, 'state': 'done'}, + ])