From 03eb35530c3ef416a00fe38d5c9e2711d29369da Mon Sep 17 00:00:00 2001 From: sergio-teruel Date: Sat, 19 Dec 2020 08:01:10 +0100 Subject: [PATCH] [IMP] stock_secondary_unit: Use secondary units directly in pickings [IMP] stock_secondary_unit: Use product_secondary_unit mixin model from product-attribute repository. --- stock_secondary_unit/__manifest__.py | 2 +- stock_secondary_unit/i18n/es.po | 52 ++++---- .../i18n/stock_secondary_unit.pot | 37 +++--- stock_secondary_unit/i18n/zh_CN.po | 50 +++++--- stock_secondary_unit/models/stock_move.py | 48 ++++--- .../tests/test_stock_secondary_unit.py | 120 ++++++++++++++++-- .../views/stock_picking_views.xml | 6 +- 7 files changed, 223 insertions(+), 92 deletions(-) diff --git a/stock_secondary_unit/__manifest__.py b/stock_secondary_unit/__manifest__.py index 2be2e08a7..2271b25a1 100644 --- a/stock_secondary_unit/__manifest__.py +++ b/stock_secondary_unit/__manifest__.py @@ -3,7 +3,7 @@ { "name": "Stock Secondary Unit", "summary": "Get product quantities in a secondary unit", - "version": "13.0.1.1.1", + "version": "13.0.2.0.0", "development_status": "Production/Stable", "category": "stock", "website": "https://github.com/OCA/stock-logistics-warehouse", diff --git a/stock_secondary_unit/i18n/es.po b/stock_secondary_unit/i18n/es.po index 8d6d0a8b3..2acd50bdd 100644 --- a/stock_secondary_unit/i18n/es.po +++ b/stock_secondary_unit/i18n/es.po @@ -24,19 +24,26 @@ msgstr "" #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit__display_name -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__display_name msgid "Display Name" msgstr "Mostrar Nombre" +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move_line__qty_done +msgid "Done" +msgstr "" + #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit__id -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__id msgid "ID" msgstr "ID" +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__product_uom_qty +msgid "Initial Demand" +msgstr "" + #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit____last_update -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin____last_update msgid "Last Modified on" msgstr "Última modificación en" @@ -62,28 +69,12 @@ msgstr "Plantilla de producto" msgid "Quantity On Hand (2Unit)" msgstr "Cantidad a mano (2Ud.)" -#. module: stock_secondary_unit -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__secondary_uom_id -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move_line__secondary_uom_id -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__secondary_uom_id -#, fuzzy -msgid "Second unit" -msgstr "Unidad Secundaria" - #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_product_product__stock_secondary_uom_id #: model:ir.model.fields,field_description:stock_secondary_unit.field_product_template__stock_secondary_uom_id msgid "Second unit for inventory" msgstr "Segunda unidad de medida para inventario" -#. module: stock_secondary_unit -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__secondary_uom_qty -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move_line__secondary_uom_qty -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__secondary_uom_qty -#, fuzzy -msgid "Secondary Qty" -msgstr "Unidad Secundaria" - #. module: stock_secondary_unit #: model_terms:ir.ui.view,arch_db:stock_secondary_unit.view_template_property_form msgid "Secondary unit" @@ -101,10 +92,27 @@ msgid "Stock Product Secondary Unit" msgstr "Unidad Secundaria" #. module: stock_secondary_unit -#: model:ir.model,name:stock_secondary_unit.model_stock_secondary_unit_mixin +#: model:ir.model.fields,help:stock_secondary_unit.field_stock_move__product_uom_qty +msgid "" +"This is the quantity of products from an inventory point of view. For moves " +"in the state 'done', this is the quantity of products that were actually " +"moved. For other moves, this is the quantity of product that is planned to " +"be moved. Lowering this quantity does not generate a backorder. Changing " +"this quantity on assigned moves affects the product reservation, and should " +"be done with care." +msgstr "" + #, fuzzy -msgid "Stock Secondary Unit Mixin" -msgstr "Unidad Secundaria" +#~ msgid "Second unit" +#~ msgstr "Unidad Secundaria" + +#, fuzzy +#~ msgid "Secondary Qty" +#~ msgstr "Unidad Secundaria" + +#, fuzzy +#~ msgid "Stock Secondary Unit Mixin" +#~ msgstr "Unidad Secundaria" #~ msgid "On Hand (2unit)" #~ msgstr "A mano (2Ud.)" diff --git a/stock_secondary_unit/i18n/stock_secondary_unit.pot b/stock_secondary_unit/i18n/stock_secondary_unit.pot index af04cff9f..dd55f8e0d 100644 --- a/stock_secondary_unit/i18n/stock_secondary_unit.pot +++ b/stock_secondary_unit/i18n/stock_secondary_unit.pot @@ -20,19 +20,26 @@ msgstr "" #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit__display_name -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__display_name msgid "Display Name" msgstr "" +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move_line__qty_done +msgid "Done" +msgstr "" + #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit__id -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__id msgid "ID" msgstr "" +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__product_uom_qty +msgid "Initial Demand" +msgstr "" + #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit____last_update -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin____last_update msgid "Last Modified on" msgstr "" @@ -58,26 +65,12 @@ msgstr "" msgid "Quantity On Hand (2Unit)" msgstr "" -#. module: stock_secondary_unit -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__secondary_uom_id -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move_line__secondary_uom_id -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__secondary_uom_id -msgid "Second unit" -msgstr "" - #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_product_product__stock_secondary_uom_id #: model:ir.model.fields,field_description:stock_secondary_unit.field_product_template__stock_secondary_uom_id msgid "Second unit for inventory" msgstr "" -#. module: stock_secondary_unit -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__secondary_uom_qty -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move_line__secondary_uom_qty -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__secondary_uom_qty -msgid "Secondary Qty" -msgstr "" - #. module: stock_secondary_unit #: model_terms:ir.ui.view,arch_db:stock_secondary_unit.view_template_property_form msgid "Secondary unit" @@ -94,6 +87,12 @@ msgid "Stock Product Secondary Unit" msgstr "" #. module: stock_secondary_unit -#: model:ir.model,name:stock_secondary_unit.model_stock_secondary_unit_mixin -msgid "Stock Secondary Unit Mixin" +#: model:ir.model.fields,help:stock_secondary_unit.field_stock_move__product_uom_qty +msgid "" +"This is the quantity of products from an inventory point of view. For moves " +"in the state 'done', this is the quantity of products that were actually " +"moved. For other moves, this is the quantity of product that is planned to " +"be moved. Lowering this quantity does not generate a backorder. Changing " +"this quantity on assigned moves affects the product reservation, and should " +"be done with care." msgstr "" diff --git a/stock_secondary_unit/i18n/zh_CN.po b/stock_secondary_unit/i18n/zh_CN.po index d326116ef..47986d9e0 100644 --- a/stock_secondary_unit/i18n/zh_CN.po +++ b/stock_secondary_unit/i18n/zh_CN.po @@ -1,6 +1,6 @@ # Translation of Odoo Server. # This file contains the translation of the following modules: -# * stock_secondary_unit +# * stock_secondary_unit # msgid "" msgstr "" @@ -23,19 +23,26 @@ msgstr " 辅助单位数量" #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit__display_name -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__display_name msgid "Display Name" msgstr "显示名称" +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move_line__qty_done +msgid "Done" +msgstr "" + #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit__id -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__id msgid "ID" msgstr "ID" +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__product_uom_qty +msgid "Initial Demand" +msgstr "" + #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit____last_update -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin____last_update msgid "Last Modified on" msgstr "最后修改时间" @@ -61,26 +68,12 @@ msgstr "产品模板" msgid "Quantity On Hand (2Unit)" msgstr "在手数量(辅助单位)" -#. module: stock_secondary_unit -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__secondary_uom_id -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move_line__secondary_uom_id -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__secondary_uom_id -msgid "Second unit" -msgstr "辅助单位" - #. module: stock_secondary_unit #: model:ir.model.fields,field_description:stock_secondary_unit.field_product_product__stock_secondary_uom_id #: model:ir.model.fields,field_description:stock_secondary_unit.field_product_template__stock_secondary_uom_id msgid "Second unit for inventory" msgstr "库存辅助单位" -#. module: stock_secondary_unit -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__secondary_uom_qty -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move_line__secondary_uom_qty -#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_secondary_unit_mixin__secondary_uom_qty -msgid "Secondary Qty" -msgstr "辅助单位数量" - #. module: stock_secondary_unit #: model_terms:ir.ui.view,arch_db:stock_secondary_unit.view_template_property_form msgid "Secondary unit" @@ -97,6 +90,21 @@ msgid "Stock Product Secondary Unit" msgstr "库存产品辅助单位" #. module: stock_secondary_unit -#: model:ir.model,name:stock_secondary_unit.model_stock_secondary_unit_mixin -msgid "Stock Secondary Unit Mixin" -msgstr "库存辅助单位混合类" +#: model:ir.model.fields,help:stock_secondary_unit.field_stock_move__product_uom_qty +msgid "" +"This is the quantity of products from an inventory point of view. For moves " +"in the state 'done', this is the quantity of products that were actually " +"moved. For other moves, this is the quantity of product that is planned to " +"be moved. Lowering this quantity does not generate a backorder. Changing " +"this quantity on assigned moves affects the product reservation, and should " +"be done with care." +msgstr "" + +#~ msgid "Second unit" +#~ msgstr "辅助单位" + +#~ msgid "Secondary Qty" +#~ msgstr "辅助单位数量" + +#~ msgid "Stock Secondary Unit Mixin" +#~ msgstr "库存辅助单位混合类" diff --git a/stock_secondary_unit/models/stock_move.py b/stock_secondary_unit/models/stock_move.py index 49821a399..22c1580e1 100644 --- a/stock_secondary_unit/models/stock_move.py +++ b/stock_secondary_unit/models/stock_move.py @@ -4,31 +4,45 @@ from odoo import api, fields, models from odoo.tools.float_utils import float_round -class StockSecondaryUnitMixin(models.AbstractModel): - _name = "stock.secondary.unit.mixin" - _description = "Stock Secondary Unit Mixin" - - secondary_uom_id = fields.Many2one( - comodel_name="product.secondary.unit", string="Second unit" - ) - secondary_uom_qty = fields.Float( - string="Secondary Qty", digits="Product Unit of Measure" - ) - - class StockMove(models.Model): - _inherit = ["stock.move", "stock.secondary.unit.mixin"] + _inherit = ["stock.move", "product.secondary.unit.mixin"] _name = "stock.move" + _secondary_unit_fields = { + "qty_field": "product_uom_qty", + "uom_field": "product_uom", + } + + product_uom_qty = fields.Float( + store=True, readonly=False, compute="_compute_product_uom_qty", copy=True + ) + + @api.depends("secondary_uom_qty", "secondary_uom_id") + def _compute_product_uom_qty(self): + self._compute_helper_target_field_qty() + + @api.onchange("product_uom") + def onchange_product_uom_for_secondary(self): + self._onchange_helper_product_uom_for_secondary() def _merge_moves_fields(self): res = super()._merge_moves_fields() - res["secondary_uom_qty"] = self[-1:].secondary_uom_qty + res["secondary_uom_qty"] = sum(self.mapped("secondary_uom_qty")) return res + @api.model + def _prepare_merge_moves_distinct_fields(self): + """Don't merge moves with distinct secondary units""" + distinct_fields = super()._prepare_merge_moves_distinct_fields() + distinct_fields += ["secondary_uom_id"] + return distinct_fields + class StockMoveLine(models.Model): - _inherit = ["stock.move.line", "stock.secondary.unit.mixin"] + _inherit = ["stock.move.line", "product.secondary.unit.mixin"] _name = "stock.move.line" + _secondary_unit_fields = {"qty_field": "qty_done", "uom_field": "product_uom_id"} + + qty_done = fields.Float(store=True, readonly=False, compute="_compute_qty_done") @api.model def create(self, vals): @@ -45,3 +59,7 @@ class StockMoveLine(models.Model): {"secondary_uom_qty": qty, "secondary_uom_id": move.secondary_uom_id.id} ) return super().create(vals) + + @api.depends("secondary_uom_id", "secondary_uom_qty") + def _compute_qty_done(self): + self._compute_helper_target_field_qty() diff --git a/stock_secondary_unit/tests/test_stock_secondary_unit.py b/stock_secondary_unit/tests/test_stock_secondary_unit.py index 5059a43b3..404c2bbbc 100644 --- a/stock_secondary_unit/tests/test_stock_secondary_unit.py +++ b/stock_secondary_unit/tests/test_stock_secondary_unit.py @@ -1,17 +1,24 @@ # 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 - @classmethod def setUpClass(cls): super().setUpClass() + # Active multiple units of measure security group for user + cls.env.user.groups_id = [(4, cls.env.ref("uom.group_uom").id)] + cls.StockPicking = cls.env["stock.picking"] cls.warehouse = cls.env.ref("stock.warehouse0") + cls.location_supplier = cls.env.ref("stock.stock_location_suppliers") + cls.location_stock = cls.env.ref("stock.stock_location_stock") + cls.picking_type_in = cls.env.ref("stock.picking_type_in") + cls.picking_type_out = cls.env.ref("stock.picking_type_out") + 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"] @@ -34,7 +41,7 @@ class TestProductSecondaryUnit(SavepointCase): 0, { "code": "A", - "name": "unit-700", + "name": "unit-500", "uom_id": cls.product_uom_unit.id, "factor": 0.5, }, @@ -49,6 +56,16 @@ class TestProductSecondaryUnit(SavepointCase): "factor": 0.9, }, ), + ( + 0, + 0, + { + "code": "C", + "name": "box 10", + "uom_id": cls.product_uom_unit.id, + "factor": 10, + }, + ), ], "attribute_line_ids": [ ( @@ -97,22 +114,19 @@ class TestProductSecondaryUnit(SavepointCase): def test_03_stock_picking_secondary_unit(self): StockPicking = self.env["stock.picking"] product1 = self.product_template.product_variant_ids[0] - location = self.env.ref("stock.stock_location_suppliers") - location_dest = self.env.ref("stock.stock_location_stock") - picking_type = self.env.ref("stock.picking_type_in") move_vals = { "product_id": product1.id, "name": product1.display_name, "secondary_uom_id": product1.secondary_uom_ids[0].id, "product_uom": product1.uom_id.id, "product_uom_qty": 10.0, - "location_id": location.id, - "location_dest_id": location_dest.id, + "location_id": self.location_supplier.id, + "location_dest_id": self.location_stock.id, } do_vals = { - "location_id": location.id, - "location_dest_id": location_dest.id, - "picking_type_id": picking_type.id, + "location_id": self.location_supplier.id, + "location_dest_id": self.location_stock.id, + "picking_type_id": self.picking_type_in.id, "move_ids_without_package": [ (0, None, move_vals), (0, None, move_vals), @@ -130,3 +144,83 @@ class TestProductSecondaryUnit(SavepointCase): ) self.assertEquals(uom_qty, 20.0) self.assertEquals(secondary_uom_qty, 40.0) + + def test_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() + with Form(picking) as picking_form: + # Test detail operations + with picking_form.move_line_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.qty_done, 0.5) + move.secondary_uom_qty = 2 + self.assertEqual(move.qty_done, 1) + move.secondary_uom_id = product.secondary_uom_ids[1] + self.assertEqual(move.qty_done, 1.8) + move.qty_done = 5 + self.assertAlmostEqual(move.secondary_uom_qty, 5.56, 2) + + def test_secondary_unit_merge_move_diff_uom(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] + 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[1] + picking = picking_form.save() + picking.action_confirm() + self.assertEquals(len(picking.move_lines), 2) + + def test_secondary_unit_merge_move_same_uom(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] + 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] + picking = picking_form.save() + picking.action_confirm() + self.assertEquals(len(picking.move_lines), 1) + self.assertEquals(picking.move_lines.secondary_uom_qty, 2) diff --git a/stock_secondary_unit/views/stock_picking_views.xml b/stock_secondary_unit/views/stock_picking_views.xml index 0b913b24b..a712c5775 100644 --- a/stock_secondary_unit/views/stock_picking_views.xml +++ b/stock_secondary_unit/views/stock_picking_views.xml @@ -12,11 +12,15 @@ expr="//field[@name='move_ids_without_package']/tree/field[@name='product_uom_qty']" position="before" > - +