From 16c9f192f605aadfb4b3fdea5030ce3579ebb0df Mon Sep 17 00:00:00 2001 From: Arnold Moyaux Date: Fri, 12 Mar 2021 09:59:30 +0000 Subject: [FIX] mrp_subcontracting, stock: Resupply Subcontractor on Order Steps to reproduce the bug: - Let's consider a consumable product P with a subcontracted BOM B - Let's consider that B is subcontracted by a partner S and B has a storable component C - C has the route Resupply Subcontractor on Order and has a partner SUP as supplier - Create a purchase order PO with 1 P to S and confirm PO - A delivery order DO1 is created with C to S - Change the ordered quantity on PO and set 2 instead of 1 Bug: DO1 was canceled and a new delivery order DO2 was created with only 1 C instead of 2 It happens due to merge move, when updating the PO line a new rule is trigger and create the object in this order: - Move Sub-Stock(finished) -> Subcontract Order -> Move Stock-Sub(comp) Then the action_confirm is trigger and will run _merge_move on object from left to right order. But when the move Sub-Stock is merged, everything is write in the first move and the new move is unlink. It result by canceling all the following object (so the new subcontractor and the Move Stock-Sub). It was not an issue for the subcontracting since the write of stock.move is overridden in order to update the order quantity when the move quantity is updated. However in this case the rule are not triggered in order to create the moves that ressuply the subcontractor. In order to avoid this mess, this PR prevent the merge in case of a subcontracting move. opw-2419222 closes odoo/odoo#68098 X-original-commit: 48f2bbb844502c91dce59e8d869b56df0960fd0a Signed-off-by: Simon Goffin (sig) Co-authored-by: simongoffin --- addons/mrp_subcontracting/models/stock_move.py | 6 ++- .../tests/test_purchase_subcontracting.py | 57 ++++++++++++++++++++++ 2 files changed, 62 insertions(+), 1 deletion(-) (limited to 'addons') diff --git a/addons/mrp_subcontracting/models/stock_move.py b/addons/mrp_subcontracting/models/stock_move.py index 45f5bcce5eb..00d492c0662 100644 --- a/addons/mrp_subcontracting/models/stock_move.py +++ b/addons/mrp_subcontracting/models/stock_move.py @@ -112,6 +112,7 @@ class StockMove(models.Model): def _action_confirm(self, merge=True, merge_into=False): subcontract_details_per_picking = defaultdict(list) + move_to_not_merge = self.env['stock.move'] for move in self: if move.location_id.usage != 'supplier' or move.location_dest_id.usage == 'supplier': continue @@ -128,10 +129,13 @@ class StockMove(models.Model): 'is_subcontract': True, 'location_id': move.picking_id.partner_id.with_company(move.company_id).property_stock_subcontractor.id }) + move_to_not_merge |= move for picking, subcontract_details in subcontract_details_per_picking.items(): picking._subcontracted_produce(subcontract_details) - res = super(StockMove, self)._action_confirm(merge=merge, merge_into=merge_into) + # We avoid merging move due to complication with stock.rule. + super(StockMove, move_to_not_merge)._action_confirm(merge=False) + res = super(StockMove, self - move_to_not_merge)._action_confirm(merge=merge, merge_into=merge_into) if subcontract_details_per_picking: self.env['stock.picking'].concat(*list(subcontract_details_per_picking.keys())).action_assign() return res diff --git a/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py b/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py index 41ed4a44fff..9d13f6c5c35 100644 --- a/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py +++ b/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py @@ -77,3 +77,60 @@ class TestSubcontractingDropshippingFlows(TestMrpSubcontractingCommon): ('partner_id', '=', partner.id), ]).order_id self.assertTrue(po) + + def test_mrp_subcontracting_purchase_2(self): + """Let's consider a subcontracted BOM with 1 component. Tick "Resupply Subcontractor on Order" on the component and set a supplier on it. + Purchase 1 BOM to the subcontractor. Confirm the purchase and change the purchased quantity to 2. + Check that 2 components are delivered to the subcontractor + """ + # Tick "resupply subconractor on order on component" + self.bom.bom_line_ids = [(5, 0, 0)] + self.bom.bom_line_ids = [(0, 0, {'product_id': self.comp1.id, 'product_qty': 1})] + resupply_sub_on_order_route = self.env['stock.location.route'].search([('name', '=', 'Resupply Subcontractor on Order')]) + (self.comp1).write({'route_ids': [(4, resupply_sub_on_order_route.id, None)]}) + # Create a supplier and set it to component + vendor = self.env['res.partner'].create({'name': 'AAA', 'email': 'from.test@example.com'}) + supplier_info1 = self.env['product.supplierinfo'].create({ + 'name': vendor.id, + 'price': 50, + }) + self.comp1.write({'seller_ids': [(0, 0, {'name': vendor.id, 'product_code': 'COMP1'})]}) + # Purchase 1 BOM to the subcontractor + po = Form(self.env['purchase.order']) + po.partner_id = self.subcontractor_partner1 + with po.order_line.new() as po_line: + po_line.product_id = self.finished + po_line.product_qty = 1 + po_line.price_unit = 100 + po = po.save() + # Confirm the purchase + po.button_confirm() + # Check one delivery order with the component has been created for the subcontractor + mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)]) + self.assertEqual(mo.state, 'confirmed') + # Check that 1 delivery with 1 component for the subcontractor has been created + picking_delivery = mo.picking_ids + wh = picking_delivery.picking_type_id.warehouse_id + origin = picking_delivery.origin + self.assertEqual(len(picking_delivery), 1) + self.assertEqual(len(picking_delivery.move_ids_without_package), 1) + self.assertEqual(picking_delivery.picking_type_id, wh.out_type_id) + self.assertEqual(picking_delivery.partner_id, self.subcontractor_partner1) + + # Change the purchased quantity to 2 + po.order_line.write({'product_qty': 2}) + # Check that two deliveries with 1 component for the subcontractor have been created + picking_deliveries = self.env['stock.picking'].search([('origin', '=', origin)]) + self.assertEqual(len(picking_deliveries), 2) + self.assertEqual(picking_deliveries[0].picking_type_id, wh.out_type_id) + self.assertEqual(picking_deliveries[0].partner_id, self.subcontractor_partner1) + self.assertTrue(picking_deliveries[0].state != 'cancel') + move1 = picking_deliveries[0].move_ids_without_package + self.assertEqual(picking_deliveries[1].picking_type_id, wh.out_type_id) + self.assertEqual(picking_deliveries[1].partner_id, self.subcontractor_partner1) + self.assertTrue(picking_deliveries[1].state != 'cancel') + move2 = picking_deliveries[1].move_ids_without_package + self.assertEqual(move1.product_id, self.comp1) + self.assertEqual(move1.product_uom_qty, 1) + self.assertEqual(move2.product_id, self.comp1) + self.assertEqual(move2.product_uom_qty, 1) -- cgit v1.2.3