From 457fbed3866333dcf56d8b757859bd23d71a2660 Mon Sep 17 00:00:00 2001 From: "Anh Thao Pham (pta)" Date: Wed, 20 Mar 2024 12:54:03 +0000 Subject: [PATCH] [FIX] stock, mrp_subcontracting_purchase: fix subcontract location after dest. update Steps to reproduce: - Install mrp and purchase - Go to "Inventory / Configuration / Settings" - Activate "Storage Locations" - Go to "Inventory / Configuration / Warehouse Management / Operations Types" - Edit "Receipts" type by activating "Show Detailed Operations" - Go to "Manufactoring / Configuration / Settings" - Activate "Subcontracting" - Create product: (e.g. Product XYZ) * Product Type: Storable Product - Create a BoM for Product XYZ: * BoM Type: Subcontracting * Subcontractors: [any] (e.g. Azure Interior) - Create a PO: * Vendor: Azure Interior * Products: 2 x Product XYZ - Confirm the PO - Open the picking from PO via the Receipt smart button - In "Operations" tab, set done to 1 - On the picking form, change the destination location (e.g. WH/Stock/Shelf1) - Save - In "Detailed Operations" tab, a line should have appeared - Select the same destination location on that line (i.e. WH/Stock/Shelf1) - Validate the picking and create a backorder for the remaining quantity to produce - Go to "Inventory / Reporting / Locations" - Check the locations of Product XYZ (Search Product: XYZ - Group by: Location) => The "On Hand Quantity" for Product XYZ is as followed: * Virtual Locations/Production: -1.00 (correct) * WH/Stock/Shelf1: 1.00 (correct) - Open the backorder picking from PO via the Receipt smart button - Record the production of the remaining unit - Validate the picking - Go to "Inventory / Reporting / Locations" - Check the locations of Product XYZ Issue: The "On Hand Quantity" for Product XYZ is as followed: * Partners/Vendors: -1.00 (incorrect, it should be empty) * Physical Locations/Subcontracting Location: 1.00 (incorrect, it should be 0.00) * Virtual Locations/Production: -2.00 (correct) * WH/Stock/Shelf1: 2.00 (correct) Cause: When the PO is confirmed, the stock picking and the stock move are created, they both have the same source and destination locations. However, in an overridden method from "mrp_subcontracting" module, a check is performed on the move to determine if it is a subcontract. If it is the case, its source location is set to the subcontractor location and so, the source location of the picking and the move is not the same anymore. When the destination location is changed on the picking, an onchange is triggering an update of the destination location AND the source location of the move to the values coming from the picking, erasing the subcontractor location set on the move. The issue only happens for the backorder, because the source location update is not propagated to the stock move lines. In the case of the original picking, the move lines were already created with the subcontractor location as source location. But when the backorder is created, the move lines are created with the values coming from a move without the subcontractor location. Solution: Do not propagate "location_id" from the picking to the subcontracting moves. opw-3777379 closes odoo/odoo#161011 X-original-commit: e0f7577da1a42512d2fdc5ee316f27a1ce040522 Signed-off-by: William Henrotin (whe) Signed-off-by: Anh Thao Pham (pta) --- .../i18n/mrp_subcontracting.pot | 14 +++++ .../models/stock_picking.py | 17 ++++++ .../tests/test_mrp_subcontracting_purchase.py | 59 +++++++++++++++++++ addons/stock/i18n/stock.pot | 7 +++ addons/stock/models/stock_picking.py | 2 +- 5 files changed, 98 insertions(+), 1 deletion(-) diff --git a/addons/mrp_subcontracting/i18n/mrp_subcontracting.pot b/addons/mrp_subcontracting/i18n/mrp_subcontracting.pot index 07f297713ad..e285eadda38 100644 --- a/addons/mrp_subcontracting/i18n/mrp_subcontracting.pot +++ b/addons/mrp_subcontracting/i18n/mrp_subcontracting.pot @@ -196,6 +196,13 @@ msgid "" " subcontracting portal view" msgstr "" +#. module: mrp_subcontracting +#. odoo-python +#: code:addons/mrp_subcontracting/models/stock_picking.py:0 +#, python-format +msgid "Locations to update" +msgstr "" + #. module: mrp_subcontracting #: model:ir.model.fields,field_description:mrp_subcontracting.field_res_partner__production_ids #: model:ir.model.fields,field_description:mrp_subcontracting.field_res_users__production_ids @@ -619,6 +626,13 @@ msgstr "" msgid "You cannot write on fields %s in mrp.production." msgstr "" +#. module: mrp_subcontracting +#. odoo-python +#: code:addons/mrp_subcontracting/models/stock_picking.py:0 +#, python-format +msgid "You might want to update the locations of this transfer's operations" +msgstr "" + #. module: mrp_subcontracting #. odoo-python #: code:addons/mrp_subcontracting/models/mrp_production.py:0 diff --git a/addons/mrp_subcontracting/models/stock_picking.py b/addons/mrp_subcontracting/models/stock_picking.py index 8748ff8af6e..9e446f9cbcc 100644 --- a/addons/mrp_subcontracting/models/stock_picking.py +++ b/addons/mrp_subcontracting/models/stock_picking.py @@ -181,3 +181,20 @@ class StockPicking(models.Model): finished_move.write({'move_dest_ids': [(4, move.id, False)]}) all_mo.action_assign() + + @api.onchange('location_id', 'location_dest_id') + def _onchange_locations(self): + moves = self.move_ids | self.move_ids_without_package + moves.filtered(lambda m: m.is_subcontract).update({ + "location_dest_id": self.location_dest_id, + }) + moves.filtered(lambda m: not m.is_subcontract).update({ + "location_id": self.location_id, + "location_dest_id": self.location_dest_id, + }) + if self._origin.location_id != self.location_id and any(line.quantity for line in self.move_ids.move_line_ids): + return {'warning': { + 'title': _("Locations to update"), + 'message': _("You might want to update the locations of this transfer's operations"), + } + } diff --git a/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py b/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py index df3950a0d7c..a89e3b62d01 100644 --- a/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py +++ b/addons/mrp_subcontracting_purchase/tests/test_mrp_subcontracting_purchase.py @@ -810,3 +810,62 @@ class MrpSubcontractingPurchaseTest(TestMrpSubcontractingCommon): self.assertEqual(bom_data['lines']['leftover_capacity'], 1) # 01/16 + 2 x 2 days (for components and for final product) = 01/20 self.assertEqual(bom_data['lines']['leftover_date'], '01/20/2024') + + def test_location_after_dest_location_update_backorder_production(self): + """ + Buy 2 subcontracted products. + Receive 1 product after changing the destination location. + Create a backorder. + Receive the last one. + Check the locations. + """ + grp_multi_loc = self.env.ref('stock.group_stock_multi_locations') + self.env.user.write({'groups_id': [Command.link(grp_multi_loc.id)]}) + subcontract_loc = self.env.company.subcontracting_location_id + production_loc = self.finished.property_stock_production + final_loc = self.env['stock.location'].create({ + 'name': 'Final location', + 'location_id': self.env.ref('stock.warehouse0').lot_stock_id.id, + }) + # buy 2 subcontracted products + po = self.env['purchase.order'].create({ + 'partner_id': self.subcontractor_partner1.id, + 'order_line': [Command.create({ + 'name': self.finished.name, + 'product_id': self.finished.id, + 'product_qty': 2.0, + 'product_uom': self.finished.uom_id.id, + 'price_unit': 1.0, + })], + }) + po.button_confirm() + + receipt = po.picking_ids + # receive 1 subcontracted product + receipt.move_ids.quantity = 1 + receipt_form = Form(receipt) + # change the destination location + receipt_form.location_dest_id = final_loc + receipt_form.save() + # change the destination location on the move line too + receipt.move_line_ids.location_dest_id = final_loc + # create the backorder + backorder_wizard_dict = receipt.button_validate() + backorder_wizard = Form(self.env[backorder_wizard_dict['res_model']].with_context(backorder_wizard_dict['context'])).save() + backorder_wizard.process() + backorder = receipt.backorder_ids + # test the stock quantities after receiving 1 product + stock_quants = self.env['stock.quant'].search([('product_id', '=', self.finished.id)]) + self.assertEqual(len(stock_quants), 3) + self.assertEqual(stock_quants.filtered(lambda q: q.location_id == final_loc).quantity, 1.0) + self.assertEqual(stock_quants.filtered(lambda q: q.location_id == subcontract_loc).quantity, 0.0) + self.assertEqual(stock_quants.filtered(lambda q: q.location_id == production_loc).quantity, -1.0) + # receive the last subcontracted product + backorder.move_ids.quantity = 1 + backorder.button_validate() + # test the final stock quantities + stock_quants = self.env['stock.quant'].search([('product_id', '=', self.finished.id)]) + self.assertEqual(len(stock_quants), 3) + self.assertEqual(stock_quants.filtered(lambda q: q.location_id == final_loc).quantity, 2.0) + self.assertEqual(stock_quants.filtered(lambda q: q.location_id == subcontract_loc).quantity, 0.0) + self.assertEqual(stock_quants.filtered(lambda q: q.location_id == production_loc).quantity, -2.0) diff --git a/addons/stock/i18n/stock.pot b/addons/stock/i18n/stock.pot index 3b9aa135e12..546dd2c259b 100644 --- a/addons/stock/i18n/stock.pot +++ b/addons/stock/i18n/stock.pot @@ -4533,6 +4533,13 @@ msgstr "" msgid "Locations" msgstr "" +#. module: stock +#. odoo-python +#: code:addons/stock/models/stock_picking.py:0 +#, python-format +msgid "Locations to update" +msgstr "" + #. module: stock #: model:ir.actions.server,name:stock.action_toggle_is_locked msgid "Lock/Unlock" diff --git a/addons/stock/models/stock_picking.py b/addons/stock/models/stock_picking.py index 5b904eb1fde..9f3d2dec17a 100644 --- a/addons/stock/models/stock_picking.py +++ b/addons/stock/models/stock_picking.py @@ -813,7 +813,7 @@ class Picking(models.Model): }) if self._origin.location_id != self.location_id and any(line.quantity for line in self.move_ids.move_line_ids): return {'warning': { - 'title': 'Locations to update', + 'title': _("Locations to update"), 'message': _("You might want to update the locations of this transfer's operations") } }