From 8276a9b51b7c5fc0ae6dd557ae88a9c31f1d1962 Mon Sep 17 00:00:00 2001 From: "Adrien Widart (awt)" <awt@odoo.com> Date: Mon, 7 Nov 2022 14:57:42 +0000 Subject: [PATCH] [FIX] mrp_subcontracting_dropshipping,purchase_stock: return to supplier When returning a dropshipped and subcontracted product to the supplier location, the received quantity of the PO line will be incorrect To reproduce the issue: 1. Create two storable products P_compo, P_Finished 2. Create a BoM: - Product: P_finished - Type: Subcontracting - Subcontractors: a subcontractor S - Components: 1 x P_compo 3. Create and confirm a PO: - Vendor: S - Deliver To: Dropship - Drop Ship Address: a partner P - Products: 1 x P_finished 4. Validate the receipt 5. Create a return with 1 x P_finished: - Update SO/PO quantities: True - Return Location: Partner Locations/Vendors 6. Validate the return 7. Go back to the PO Error: The qty received is 2, it should be 0 There is currently no code to handle the return of a dropshipped product OPW-3030895 closes odoo/odoo#105185 Signed-off-by: William Henrotin (whe) <whe@odoo.com> --- .../models/stock_move.py | 4 ++ .../tests/test_purchase_subcontracting.py | 38 ++++++++++++++----- addons/purchase_stock/models/purchase.py | 13 +------ addons/purchase_stock/models/stock.py | 9 +++++ 4 files changed, 43 insertions(+), 21 deletions(-) diff --git a/addons/mrp_subcontracting_dropshipping/models/stock_move.py b/addons/mrp_subcontracting_dropshipping/models/stock_move.py index fd507f22b192..309367f78f19 100644 --- a/addons/mrp_subcontracting_dropshipping/models/stock_move.py +++ b/addons/mrp_subcontracting_dropshipping/models/stock_move.py @@ -7,6 +7,10 @@ from odoo import models class StockMove(models.Model): _inherit = "stock.move" + def _is_purchase_return(self): + res = super()._is_purchase_return() + return res or self._is_dropshipped_returned() + def _is_dropshipped(self): res = super()._is_dropshipped() return res or ( diff --git a/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py b/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py index 5d7e0d485e60..8395aea162e8 100644 --- a/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py +++ b/addons/mrp_subcontracting_dropshipping/tests/test_purchase_subcontracting.py @@ -139,7 +139,8 @@ class TestSubcontractingDropshippingFlows(TestMrpSubcontractingCommon): """ Create and confirm a PO with a subcontracted move. The picking type of the PO is 'Dropship' and the delivery address a customer. Then, process - a return with the stock location as destination + a return with the stock location as destination and another return with + the supplier as destination """ subcontractor, client = self.env['res.partner'].create([ {'name': 'SuperSubcontractor'}, @@ -178,7 +179,7 @@ class TestSubcontractingDropshippingFlows(TestMrpSubcontractingCommon): "order_line": [(0, 0, { 'product_id': p_finished.id, 'name': p_finished.name, - 'product_qty': 1.0, + 'product_qty': 2.0, })], }) po.button_confirm() @@ -187,27 +188,46 @@ class TestSubcontractingDropshippingFlows(TestMrpSubcontractingCommon): self.assertEqual(mo.picking_type_id, self.warehouse.subcontracting_type_id) delivery = po.picking_ids - delivery.move_line_ids.qty_done = 1.0 + delivery.move_line_ids.qty_done = 2.0 delivery.button_validate() self.assertEqual(delivery.state, 'done') self.assertEqual(mo.state, 'done') - self.assertEqual(po.order_line.qty_received, 1) + self.assertEqual(po.order_line.qty_received, 2) + # return 1 x P_finished to the stock location stock_location = self.warehouse.lot_stock_id stock_location.return_location = True return_form = Form(self.env['stock.return.picking'].with_context(active_ids=delivery.ids, active_id=delivery.id, active_model='stock.picking')) + with return_form.product_return_moves.edit(0) as line: + line.quantity = 1.0 return_form.location_id = stock_location return_wizard = return_form.save() return_picking_id, _pick_type_id = return_wizard._create_returns() - delivery_return = self.env['stock.picking'].browse(return_picking_id) - delivery_return.move_line_ids.qty_done = 1.0 - delivery_return.button_validate() + delivery_return01 = self.env['stock.picking'].browse(return_picking_id) + delivery_return01.move_line_ids.qty_done = 1.0 + delivery_return01.button_validate() - self.assertEqual(delivery_return.state, 'done') + self.assertEqual(delivery_return01.state, 'done') self.assertEqual(p_finished.qty_available, 1, 'One product has been returned to the stock location, so it should be available') - self.assertEqual(po.order_line.qty_received, 1, 'One product has been returned to the stock location, so we should still consider it as received') + self.assertEqual(po.order_line.qty_received, 2, 'One product has been returned to the stock location, so we should still consider it as received') + + # return 1 x P_finished to the supplier location + supplier_location = dropship_picking_type.default_location_src_id + return_form = Form(self.env['stock.return.picking'].with_context(active_ids=delivery.ids, active_id=delivery.id, active_model='stock.picking')) + with return_form.product_return_moves.edit(0) as line: + line.quantity = 1.0 + return_form.location_id = supplier_location + return_wizard = return_form.save() + return_picking_id, _pick_type_id = return_wizard._create_returns() + + delivery_return02 = self.env['stock.picking'].browse(return_picking_id) + delivery_return02.move_line_ids.qty_done = 1.0 + delivery_return02.button_validate() + + self.assertEqual(delivery_return02.state, 'done') + self.assertEqual(po.order_line.qty_received, 1) def test_po_to_subcontractor(self): """ diff --git a/addons/purchase_stock/models/purchase.py b/addons/purchase_stock/models/purchase.py index c7b919e644dd..036edef539e2 100644 --- a/addons/purchase_stock/models/purchase.py +++ b/addons/purchase_stock/models/purchase.py @@ -294,7 +294,7 @@ class PurchaseOrderLine(models.Model): # the PO. Therefore, we can skip them since they will be handled later on. for move in line.move_ids.filtered(lambda m: m.product_id == line.product_id): if move.state == 'done': - if move.location_dest_id.usage == "supplier": + if move._is_purchase_return(): if move.to_refund: total -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom, rounding_method='HALF-UP') elif move.origin_returned_move_id and move.origin_returned_move_id._is_dropshipped() and not move._is_dropshipped_returned(): @@ -303,17 +303,6 @@ class PurchaseOrderLine(models.Model): # receive the product physically in our stock. To avoid counting the # quantity twice, we do nothing. pass - elif ( - move.location_dest_id.usage == "internal" - and move.location_id.usage != "supplier" - and move.warehouse_id - and move.location_dest_id - not in self.env["stock.location"].search( - [("id", "child_of", move.warehouse_id.view_location_id.id)] - ) - ): - if move.to_refund: - total -= move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom, rounding_method='HALF-UP') else: total += move.product_uom._compute_quantity(move.product_uom_qty, line.product_uom, rounding_method='HALF-UP') line._track_qty_received(total) diff --git a/addons/purchase_stock/models/stock.py b/addons/purchase_stock/models/stock.py index 7f131ba3da56..5281b06e215e 100644 --- a/addons/purchase_stock/models/stock.py +++ b/addons/purchase_stock/models/stock.py @@ -135,6 +135,15 @@ class StockMove(models.Model): _('Odoo is not able to generate the anglo saxon entries. The total valuation of %s is zero.') % related_aml.product_id.display_name) return valuation_price_unit_total, valuation_total_qty + def _is_purchase_return(self): + self.ensure_one() + return self.location_dest_id.usage == "supplier" or ( + self.location_dest_id.usage == "internal" + and self.location_id.usage != "supplier" + and self.warehouse_id + and self.location_dest_id not in self.env["stock.location"].search([("id", "child_of", self.warehouse_id.view_location_id.id)]) + ) + class StockWarehouse(models.Model): _inherit = 'stock.warehouse' -- GitLab