From acfa94b7aeea9f8efdc1886b094697ae3f0f1d2a Mon Sep 17 00:00:00 2001
From: "Victor Piryns (pivi)" <pivi@odoo.com>
Date: Thu, 20 Oct 2022 14:22:51 +0000
Subject: [PATCH] [FIX] sale: not change the package type when setting the
 package qty

Current behaviour:
Changing the package quantity may change the package type,
leading to an incoherent product quantities in the SOL.
(For ex: 20 uom in 2 packages of 20)

Expected behaviour:
When setting the package quantity, don't change the
package type. If the user wants to change the package type
they can do it by manually setting it or changing the product_uom.

Steps to reproduce:
- Install Sale and Inventory
- Activate Packages in settings
- Create 2 package types for a product of choice, one of 10, another of 20
- Create a new SO with that product
- Set the quantity to 10 (a new package_qty=1 and packaging type of 10 should appear)
- Save
- Edit package_qty to 2
- Observe that we have 20 units of the product, 2 packages of 20,
  instead of 2 packages of 10

Reason for the problem:
When setting the `product_packaging_qty`, the `product_uom_qty` compute is triggered.
When `product_uom_qty` changes, it triggers `product_packaging_id`'s compute.
When `product_packaging_id` is computed, it doesn't trigger `product_package_qty`,
because it is protected, since we are writing to it.

Fix:
When we are writing the `product_packaging_qty` and the `product_uom_qty`
we remove the compute for `product_packaging_id`.

Affected versions:
- saas-15.2
- saas-15.3
- 16.0
- master

opw-3010703

closes odoo/odoo#106811

X-original-commit: 42c9f7fb2287e4653999d5a5ce010547e9c3712f
Signed-off-by: Piryns Victor (pivi) <pivi@odoo.com>
---
 addons/sale/models/sale_order_line.py |  8 +++++-
 addons/sale/tests/test_sale_order.py  | 41 +++++++++++++++++++++++++++
 2 files changed, 48 insertions(+), 1 deletion(-)

diff --git a/addons/sale/models/sale_order_line.py b/addons/sale/models/sale_order_line.py
index 806e3eeb42ef..936f3e471589 100644
--- a/addons/sale/models/sale_order_line.py
+++ b/addons/sale/models/sale_order_line.py
@@ -993,7 +993,13 @@ class SaleOrderLine(models.Model):
                 % '\n'.join(fields.mapped('field_description'))
             )
 
-        return super().write(values)
+        result = super().write(values)
+
+        # Don't recompute the package_id if we are setting the quantity of the items and the quantity of packages
+        if 'product_uom_qty' in values and 'product_packaging_qty' in values and 'product_packaging_id' not in values:
+            self.env.remove_to_compute(self._fields['product_packaging_id'], self)
+
+        return result
 
     def _get_protected_fields(self):
         """ Give the fields that should not be modified on a locked SO.
diff --git a/addons/sale/tests/test_sale_order.py b/addons/sale/tests/test_sale_order.py
index 13a6fce08226..5a7442859c65 100644
--- a/addons/sale/tests/test_sale_order.py
+++ b/addons/sale/tests/test_sale_order.py
@@ -205,6 +205,47 @@ class TestSaleOrder(SaleCommon):
         so_form.save()
         self.assertEqual(so.order_line.product_uom_qty, 12)
 
+        packaging_pack_of_10 = self.env['product.packaging'].create({
+            'name': "PackOf10",
+            'product_id': self.product.id,
+            'qty': 10.0,
+        })
+        packaging_pack_of_20 = self.env['product.packaging'].create({
+            'name': "PackOf20",
+            'product_id': self.product.id,
+            'qty': 20.0,
+        })
+
+        so2 = self.env['sale.order'].create({
+            'partner_id': self.partner.id,
+        })
+        so2_form = Form(so2)
+        with so2_form.order_line.new() as line:
+            line.product_id = self.product
+            line.product_uom_qty = 10
+        so2_form.save()
+        self.assertEqual(so2.order_line.product_packaging_id.id, packaging_pack_of_10.id)
+        self.assertEqual(so2.order_line.product_packaging_qty, 1.0)
+
+        with so2_form.order_line.edit(0) as line:
+            line.product_packaging_qty = 2
+        so2_form.save()
+        self.assertEqual(so2.order_line.product_uom_qty, 20)
+        # we should have 2 pack of 10, as we've set the package_qty manually,
+        # we shouldn't recompute the packaging_id, since the package_qty is protected,
+        # therefor cannot be recomputed during the same transaction, which could lead
+        # to an incorrect line like (qty=20,pack_qty=2,pack_id=PackOf20)
+        self.assertEqual(so2.order_line.product_packaging_qty, 2)
+        self.assertEqual(so2.order_line.product_packaging_id.id, packaging_pack_of_10.id)
+
+        with so2_form.order_line.edit(0) as line:
+            line.product_packaging_id = packaging_pack_of_20
+        so2_form.save()
+        self.assertEqual(so2.order_line.product_uom_qty, 20)
+        # we should have 1 pack of 20, as we've set the package type manually
+        self.assertEqual(so2.order_line.product_packaging_qty, 1)
+        self.assertEqual(so2.order_line.product_packaging_id.id, packaging_pack_of_20.id)
+
     def _create_sale_order(self):
         """Create dummy sale order (without lines)"""
         return self.env['sale.order'].with_context(
-- 
GitLab