From dab82509fa24b75b9cc976fd12cef7dfbe2c3125 Mon Sep 17 00:00:00 2001 From: "Adrien Widart (awt)" Date: Tue, 19 Sep 2023 08:08:06 +0000 Subject: [PATCH] [FIX] {sale_,}purchase_stock: handle extra qty on MTO SM To reproduce the issue: 1. In Settings, enable "Multi Routes" 2. In Routes, unarchive MTO 3. Create a product P: - Storable - With a vendor - Routes: MTO + Buy 4. Create and confirm a SO for 10 x P 5. On the delivery, set the done quantity to 12 6. Validate the delivery Error: The state of the delivery is set to _done_ but the SM has changed, its demand is 2 and the done quantity is 0. Moreover, a backorder has been created Since [1], extra moves are supposed to be merged with the initial one. However, there is an issue with the above case. The initial SM has generated a POL (the product is an MTO-buy one). However, since the extra move is an MTS one, it does not generate any POL. As a result, the SMs don't have the same value for the field `created_purchase_line_ids`. This is an issue because for two SM to be merged, this field must be the same on both SM: https://github.com/odoo/odoo/blob/ef3c21255c6f2b1172be3b1f8f0dc83bc276d806/addons/purchase_stock/models/stock_move.py#L20-L24 As a result, the SMs are not merged, the method `_create_extra_move` only returns the extra move, the initial one is lost and it leads to an unexpected behaviour. We should also copy the values of `created_purchase_line_ids` to ensure the merged of both initial and extra moves. [1] f9867a5fa572a15fb89c49c61e569427d6388cbc OPW-3504138 closes odoo/odoo#137869 Related: odoo/enterprise#48547 Signed-off-by: Djamel Touati (otd) Signed-off-by: Adrien Widart (awt) --- addons/purchase_stock/models/stock_move.py | 1 + .../tests/test_sale_purchase_stock_flow.py | 81 ++++++++++++++----- 2 files changed, 63 insertions(+), 19 deletions(-) diff --git a/addons/purchase_stock/models/stock_move.py b/addons/purchase_stock/models/stock_move.py index c25eefc6275..46b0d9a22af 100644 --- a/addons/purchase_stock/models/stock_move.py +++ b/addons/purchase_stock/models/stock_move.py @@ -150,6 +150,7 @@ class StockMove(models.Model): def _prepare_extra_move_vals(self, qty): vals = super(StockMove, self)._prepare_extra_move_vals(qty) vals['purchase_line_id'] = self.purchase_line_id.id + vals['created_purchase_line_ids'] = [Command.set(self.created_purchase_line_ids.ids)] return vals def _prepare_move_split_vals(self, uom_qty): diff --git a/addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py b/addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py index c283f6d9df5..1629fa177dd 100644 --- a/addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py +++ b/addons/sale_purchase_stock/tests/test_sale_purchase_stock_flow.py @@ -6,38 +6,81 @@ from odoo.tests.common import TransactionCase, Form class TestSalePurchaseStockFlow(TransactionCase): + @classmethod + def setUpClass(cls): + super(TestSalePurchaseStockFlow, cls).setUpClass() + cls.mto_route = cls.env.ref('stock.route_warehouse0_mto') + cls.buy_route = cls.env.ref('purchase_stock.route_warehouse0_buy') + cls.mto_route.active = True + + cls.customer_location = cls.env.ref('stock.stock_location_customers') + + cls.vendor = cls.env['res.partner'].create({'name': 'Super Vendor'}) + cls.customer = cls.env['res.partner'].create({'name': 'Super Customer'}) + + cls.mto_product = cls.env['product.product'].create({ + 'name': 'SuperProduct', + 'type': 'product', + 'route_ids': [(6, 0, (cls.mto_route + cls.buy_route).ids)], + 'seller_ids': [(0, 0, { + 'partner_id': cls.vendor.id, + })], + }) + def test_cancel_so_with_draft_po(self): """ Sell a MTO+Buy product -> a PO is generated Cancel the SO -> an activity should be added to the PO """ - mto_route = self.env.ref('stock.route_warehouse0_mto') - buy_route = self.env.ref('purchase_stock.route_warehouse0_buy') - mto_route.active = True - - vendor = self.env['res.partner'].create({ - 'name': 'Super Vendor' - }) - - product = self.env['product.product'].create({ - 'name': 'SuperProduct', - 'type': 'product', - 'route_ids': [(6, 0, (mto_route + buy_route).ids)], - 'seller_ids': [(0, 0, { - 'partner_id': vendor.id, - })], - }) - so_form = Form(self.env['sale.order']) so_form.partner_id = self.env.user.partner_id with so_form.order_line.new() as line: - line.product_id = product + line.product_id = self.mto_product so = so_form.save() so.action_confirm() - po = self.env['purchase.order'].search([('partner_id', '=', vendor.id)]) + po = self.env['purchase.order'].search([('partner_id', '=', self.vendor.id)]) so._action_cancel() self.assertTrue(po.activity_ids) self.assertIn(so.name, po.activity_ids.note) + + def test_qty_delivered_with_mto_and_done_quantity_change(self): + """ + MTO product P + Sell 10 x P. On the delivery, set the done quantity to 12, validate and + then set the done quantity to 10: the delivered qty of the SOL should + be 10 + """ + so = self.env['sale.order'].create({ + 'partner_id': self.customer.id, + 'order_line': [(0, 0, { + 'name': self.mto_product.name, + 'product_id': self.mto_product.id, + 'product_uom_qty': 10, + 'product_uom': self.mto_product.uom_id.id, + 'price_unit': 1, + })], + }) + so.action_confirm() + + delivery = so.picking_ids.filtered(lambda p: p.location_dest_id == self.customer_location) + sm = delivery.move_ids + sm.move_line_ids = [(5, 0, 0), (0, 0, { + 'location_id': sm.location_id.id, + 'location_dest_id': sm.location_dest_id.id, + 'product_id': sm.product_id.id, + 'qty_done': 12, + 'company_id': sm.company_id.id, + 'product_uom_id': sm.product_uom.id, + 'picking_id': delivery.id, + })] + delivery.button_validate() + + self.assertEqual(delivery.state, 'done') + self.assertEqual(delivery.move_ids.move_line_ids.qty_done, 12) + self.assertEqual(so.order_line.qty_delivered, 12) + + sm.move_line_ids.qty_done = 10 + self.assertEqual(so.order_line.qty_delivered, 10)