Repository: OCA/stock-logistics-warehouse

Issue:
[12.0] stock_secondary_unit "secondary qty" value in picking not affecting "initial demand"
AFFECTED VERSIONS

12.0 (it works ok on 13.0)

STEPS TO REPRODUCE

Activate Units of Measure in general settings > inventory

Inventory > Master Data > Products > Set a secondary unit in a product

![image](https://user-images.githubusercontent.com/55489873/123983258-04aa0200-d9c4-11eb-90a1-e32686f07ac4.png)

Create a new transfer > add product > select secondary UoM > input secondary qty

![image](https://user-images.githubusercontent.com/55489873/123983462-2e632900-d9c4-11eb-9b3c-c813bd2be121.png)

CURRENT BEHAVIOR

Value "Initial demand" is not affected by secondary qty input
In the same way, if "Initial demand" is set, "secondary qty" does not change - basically there is no relation between the two fields

![image](https://user-images.githubusercontent.com/55489873/123983876-7f731d00-d9c4-11eb-9686-bf8d54618d2b.png)

REQUIRED BEHAVIOR 

When "secondary qty" is updated, also "initial demand" should update - and viceversa, as it happens in SO with secondary unit modules

VIDEO

https://recordit.co/zcuDUx6xco








Discussion:

Resolving patch:
diff --git a/stock_secondary_unit/models/stock_move.py b/stock_secondary_unit/models/stock_move.py
--- a/stock_secondary_unit/models/stock_move.py
+++ b/stock_secondary_unit/models/stock_move.py
@@ -2,7 +2,7 @@
 # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
 from odoo import api, fields, models
 from odoo.addons import decimal_precision as dp
-from odoo.tools.float_utils import float_round
+from odoo.tools.float_utils import float_compare, float_round
 
 
 class StockSecondaryUnitMixin(models.AbstractModel):
@@ -28,6 +28,52 @@ def _merge_moves_fields(self):
         res['secondary_uom_qty'] = self[-1:].secondary_uom_qty
         return res
 
+    @api.onchange('secondary_uom_id', 'secondary_uom_qty')
+    def onchange_secondary_uom(self):
+        if not self.secondary_uom_id:
+            return
+        factor = self.secondary_uom_id.factor * self.product_uom.factor
+
+        qty = float_round(
+            self.secondary_uom_qty * factor,
+            precision_rounding=self.product_uom.rounding
+        )
+        if float_compare(
+            self.product_uom_qty, qty, precision_rounding=self.product_uom.rounding
+        ) != 0:
+            self.product_uom_qty = qty
+
+    @api.onchange('product_uom_qty')
+    def onchange_secondary_unit_product_uom_qty(self):
+        if not self.secondary_uom_id:
+            return
+        factor = self.secondary_uom_id.factor * self.product_uom.factor
+
+        qty = float_round(
+            self.product_uom_qty / (factor or 1.0),
+            precision_rounding=self.secondary_uom_id.uom_id.rounding
+        )
+        if float_compare(
+            self.secondary_uom_qty,
+            qty,
+            precision_rounding=self.secondary_uom_id.uom_id.rounding
+        ) != 0:
+            self.secondary_uom_qty = qty
+
+    @api.onchange('product_uom')
+    def onchange_product_uom_for_secondary(self):
+        if not self.secondary_uom_id:
+            return
+        factor = self.product_uom.factor * self.secondary_uom_id.factor
+        qty = float_round(
+            self.product_uom_qty / (factor or 1.0),
+            precision_rounding=self.product_uom.rounding
+        )
+        if float_compare(
+            self.secondary_uom_qty, qty, precision_rounding=self.product_uom.rounding
+        ) != 0:
+            self.secondary_uom_qty = qty
+
 
 class StockMoveLine(models.Model):
     _inherit = ['stock.move.line', 'stock.secondary.unit.mixin']


Test patch:
diff --git a/stock_secondary_unit/tests/test_stock_secondary_unit.py b/stock_secondary_unit/tests/test_stock_secondary_unit.py
--- a/stock_secondary_unit/tests/test_stock_secondary_unit.py
+++ b/stock_secondary_unit/tests/test_stock_secondary_unit.py
@@ -1,8 +1,9 @@
 # Copyright 2018 Tecnativa - Sergio Teruel
 # License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
-from odoo.tests import SavepointCase
+from odoo.tests import Form, SavepointCase, tagged
 
 
+@tagged("-at_install", "post_install")
 class TestProductSecondaryUnit(SavepointCase):
     at_install = False
     post_install = True
@@ -10,8 +11,11 @@ class TestProductSecondaryUnit(SavepointCase):
     @classmethod
     def setUpClass(cls):
         super().setUpClass()
+        cls.StockPicking = cls.env["stock.picking"]
+        cls.env.user.groups_id = [(4, cls.env.ref("uom.group_uom").id)]
         cls.warehouse = cls.env.ref('stock.warehouse0')
         cls.product_uom_kg = cls.env.ref('uom.product_uom_kgm')
+        cls.product_uom_ton = cls.env.ref("uom.product_uom_ton")
         cls.product_uom_unit = cls.env.ref('uom.product_uom_unit')
         ProductAttribute = cls.env['product.attribute']
         ProductAttributeValue = cls.env['product.attribute.value']
@@ -42,6 +46,12 @@ def setUpClass(cls):
                     'uom_id': cls.product_uom_unit.id,
                     'factor': 0.9,
                 }),
+                (0, 0, {
+                    'code': 'C',
+                    'name': 'box 10',
+                    'uom_id': cls.product_uom_unit.id,
+                    'factor': 10,
+                }),
             ],
             'attribute_line_ids': [(0, 0, {
                 'attribute_id': cls.attribute_color.id,
@@ -67,6 +77,7 @@ def setUpClass(cls):
             'location_id': cls.warehouse.lot_stock_id.id,
             'quantity': 10.0,
         })
+        cls.picking_type_out = cls.env.ref("stock.picking_type_out")
 
     def test_01_stock_secondary_unit_template(self):
         self.assertEqual(
@@ -110,3 +121,32 @@ def test_03_stock_picking_secondary_unit(self):
             sum(delivery_order.move_line_ids.mapped('secondary_uom_qty'))
         self.assertEquals(uom_qty, 20.0)
         self.assertEquals(secondary_uom_qty, 40.0)
+
+    def test_04_picking_secondary_unit(self):
+        product = self.product_template.product_variant_ids[0]
+        with Form(
+            self.StockPicking.with_context(
+                planned_picking=True,
+                default_picking_type_id=self.picking_type_out.id,
+            )
+        ) as picking_form:
+            with picking_form.move_ids_without_package.new() as move:
+                move.product_id = product
+                move.secondary_uom_qty = 1
+                move.secondary_uom_id = product.secondary_uom_ids[0]
+                self.assertEqual(move.product_uom_qty, 0.5)
+                move.secondary_uom_qty = 2
+                self.assertEqual(move.product_uom_qty, 1)
+                move.secondary_uom_id = product.secondary_uom_ids[1]
+                self.assertEqual(move.product_uom_qty, 1.8)
+                move.product_uom_qty = 5
+                self.assertAlmostEqual(move.secondary_uom_qty, 5.56, 2)
+                # Change uom from stock move line
+                move.secondary_uom_qty = 1
+                move.secondary_uom_id = product.secondary_uom_ids[2]
+                self.assertEqual(move.product_uom_qty, 10)
+                move.product_uom = self.product_uom_ton
+                self.assertAlmostEqual(move.secondary_uom_qty, 1000, 2)
+
+        picking = picking_form.save()
+        picking.action_confirm()