diff --git a/setup/stock_secondary_unit/odoo/addons/stock_secondary_unit b/setup/stock_secondary_unit/odoo/addons/stock_secondary_unit new file mode 120000 index 000000000000..4aaeec1bcb15 --- /dev/null +++ b/setup/stock_secondary_unit/odoo/addons/stock_secondary_unit @@ -0,0 +1 @@ +../../../../stock_secondary_unit \ No newline at end of file diff --git a/setup/stock_secondary_unit/setup.py b/setup/stock_secondary_unit/setup.py new file mode 100644 index 000000000000..28c57bb64031 --- /dev/null +++ b/setup/stock_secondary_unit/setup.py @@ -0,0 +1,6 @@ +import setuptools + +setuptools.setup( + setup_requires=['setuptools-odoo'], + odoo_addon=True, +) diff --git a/stock_secondary_unit/README.rst b/stock_secondary_unit/README.rst new file mode 100644 index 000000000000..19e5c8c77ca9 --- /dev/null +++ b/stock_secondary_unit/README.rst @@ -0,0 +1,93 @@ +==================== +Stock Secondary Unit +==================== + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Production%2FStable-green.png + :target: https://odoo-community.org/page/development-status + :alt: Production/Stable +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fstock--logistics--warehouse-lightgray.png?logo=github + :target: https://github.com/OCA/stock-logistics-warehouse/tree/15.0/stock_secondary_unit + :alt: OCA/stock-logistics-warehouse +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/stock-logistics-warehouse-15-0/stock-logistics-warehouse-15-0-stock_secondary_unit + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/153/15.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of stock module to allow define +other units with their conversion factor. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module you need to: + +#. Go to a *Product > General Information tab*. +#. Create any record in "Secondary unit of measure". +#. Set the conversion factor. +#. Go to *Inventory tab* and set a second unit of measure. +#. Push button 'Quantity on hand' and set quantities in stock for this product. +#. Go to product list and you can see the secondary unit value. + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues `_. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Tecnativa + +Contributors +~~~~~~~~~~~~ + +* `Tecnativa `_ + + * Carlos Dauden + * Sergio Teruel + * Carlos Roca +* Kitti Upariphutthiphong +* Pimolnat Suntian +* Alan Ramos + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +This module is part of the `OCA/stock-logistics-warehouse `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_secondary_unit/__init__.py b/stock_secondary_unit/__init__.py new file mode 100644 index 000000000000..3275ac2adf3d --- /dev/null +++ b/stock_secondary_unit/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import models diff --git a/stock_secondary_unit/__manifest__.py b/stock_secondary_unit/__manifest__.py new file mode 100644 index 000000000000..7ac653761475 --- /dev/null +++ b/stock_secondary_unit/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2018 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Stock Secondary Unit", + "summary": "Get product quantities in a secondary unit", + "version": "16.0.1.0.0", + "development_status": "Production/Stable", + "category": "stock", + "website": "https://github.com/OCA/stock-logistics-warehouse", + "author": "Tecnativa, Odoo Community Association (OCA)", + "license": "AGPL-3", + "application": False, + "installable": True, + "depends": ["stock", "product_secondary_unit"], + "data": [ + "views/product_views.xml", + "views/stock_move_views.xml", + "views/stock_picking_views.xml", + "report/report_deliveryslip.xml", + ], +} diff --git a/stock_secondary_unit/i18n/es.po b/stock_secondary_unit/i18n/es.po new file mode 100644 index 000000000000..ffec03ed99b1 --- /dev/null +++ b/stock_secondary_unit/i18n/es.po @@ -0,0 +1,123 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_secondary_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 11.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2018-09-11 06:27+0000\n" +"PO-Revision-Date: 2018-09-11 08:28+0200\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=2; plural=(n != 1);\n" +"X-Generator: Poedit 2.0.6\n" + +#. module: stock_secondary_unit +#: model_terms:ir.ui.view,arch_db:stock_secondary_unit.report_delivery_document +msgid "Secondary Qty" +msgstr "" + +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__product_uom_qty +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_reservation__product_uom_qty +msgid "Demand" +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,name:stock_secondary_unit.model_product_product +msgid "Product" +msgstr "Producto" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_stock_move_line +msgid "Product Moves (Stock Move Line)" +msgstr "" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_product_template +msgid "Product Template" +msgstr "Plantilla de producto" + +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_product_product__secondary_unit_qty_available +#: model:ir.model.fields,field_description:stock_secondary_unit.field_product_template__secondary_unit_qty_available +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit__secondary_unit_qty_available +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 +#, 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 +#, 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" +msgstr "Unidad Secundaria" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_stock_move +msgid "Stock Move" +msgstr "" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_stock_product_secondary_unit +#, fuzzy +msgid "Stock Product Secondary Unit" +msgstr "Unidad Secundaria" + +#. module: stock_secondary_unit +#: model:ir.model.fields,help:stock_secondary_unit.field_stock_move__product_uom_qty +#: model:ir.model.fields,help:stock_secondary_unit.field_stock_reservation__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 "Display Name" +#~ msgstr "Mostrar Nombre" + +#~ msgid "ID" +#~ msgstr "ID" + +#~ msgid "Last Modified on" +#~ msgstr "Última modificación en" + +#, fuzzy +#~ msgid "Stock Secondary Unit Mixin" +#~ msgstr "Unidad Secundaria" + +#~ msgid "On Hand (2unit)" +#~ msgstr "A mano (2Ud.)" + +#~ msgid "Second Unit Quantity On Hand" +#~ msgstr "Segunda unidad de medida por defecto" diff --git a/stock_secondary_unit/i18n/stock_secondary_unit.pot b/stock_secondary_unit/i18n/stock_secondary_unit.pot new file mode 100644 index 000000000000..c4ed242ce055 --- /dev/null +++ b/stock_secondary_unit/i18n/stock_secondary_unit.pot @@ -0,0 +1,97 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_secondary_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.0\n" +"Report-Msgid-Bugs-To: \n" +"Last-Translator: \n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: stock_secondary_unit +#: model_terms:ir.ui.view,arch_db:stock_secondary_unit.report_delivery_document +msgid "Secondary Qty" +msgstr "" + +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__product_uom_qty +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_reservation__product_uom_qty +msgid "Demand" +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,name:stock_secondary_unit.model_product_product +msgid "Product" +msgstr "" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_stock_move_line +msgid "Product Moves (Stock Move Line)" +msgstr "" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_product_template +msgid "Product Template" +msgstr "" + +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_product_product__secondary_unit_qty_available +#: model:ir.model.fields,field_description:stock_secondary_unit.field_product_template__secondary_unit_qty_available +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit__secondary_unit_qty_available +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 +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 +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" +msgstr "" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_stock_move +msgid "Stock Move" +msgstr "" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_stock_product_secondary_unit +msgid "Stock Product Secondary Unit" +msgstr "" + +#. module: stock_secondary_unit +#: model:ir.model.fields,help:stock_secondary_unit.field_stock_move__product_uom_qty +#: model:ir.model.fields,help:stock_secondary_unit.field_stock_reservation__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 new file mode 100644 index 000000000000..4006e4ec6fbe --- /dev/null +++ b/stock_secondary_unit/i18n/zh_CN.po @@ -0,0 +1,112 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_secondary_unit +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2019-10-21 15:32+0000\n" +"Last-Translator: Tony Gu \n" +"Language-Team: none\n" +"Language: zh_CN\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 3.8\n" + +#. module: stock_secondary_unit +#: model_terms:ir.ui.view,arch_db:stock_secondary_unit.report_delivery_document +msgid "Secondary Qty" +msgstr " 辅助单位数量" + +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_move__product_uom_qty +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_reservation__product_uom_qty +msgid "Demand" +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,name:stock_secondary_unit.model_product_product +msgid "Product" +msgstr "产品" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_stock_move_line +msgid "Product Moves (Stock Move Line)" +msgstr "产品移动(库存移动行)" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_product_template +msgid "Product Template" +msgstr "产品模板" + +#. module: stock_secondary_unit +#: model:ir.model.fields,field_description:stock_secondary_unit.field_product_product__secondary_unit_qty_available +#: model:ir.model.fields,field_description:stock_secondary_unit.field_product_template__secondary_unit_qty_available +#: model:ir.model.fields,field_description:stock_secondary_unit.field_stock_product_secondary_unit__secondary_unit_qty_available +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 +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 +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" +msgstr "辅助单位" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_stock_move +msgid "Stock Move" +msgstr "库存移动" + +#. module: stock_secondary_unit +#: model:ir.model,name:stock_secondary_unit.model_stock_product_secondary_unit +msgid "Stock Product Secondary Unit" +msgstr "库存产品辅助单位" + +#. module: stock_secondary_unit +#: model:ir.model.fields,help:stock_secondary_unit.field_stock_move__product_uom_qty +#: model:ir.model.fields,help:stock_secondary_unit.field_stock_reservation__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 "Display Name" +#~ msgstr "显示名称" + +#~ msgid "ID" +#~ msgstr "ID" + +#~ msgid "Last Modified on" +#~ msgstr "最后修改时间" + +#~ msgid "Stock Secondary Unit Mixin" +#~ msgstr "库存辅助单位混合类" diff --git a/stock_secondary_unit/models/__init__.py b/stock_secondary_unit/models/__init__.py new file mode 100644 index 000000000000..d342115d5a6d --- /dev/null +++ b/stock_secondary_unit/models/__init__.py @@ -0,0 +1,6 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +# Keep order +from . import stock_product_secondary_unit_mixin +from . import product_product +from . import product_template +from . import stock_move diff --git a/stock_secondary_unit/models/product_product.py b/stock_secondary_unit/models/product_product.py new file mode 100644 index 000000000000..6794b98e0d66 --- /dev/null +++ b/stock_secondary_unit/models/product_product.py @@ -0,0 +1,17 @@ +# Copyright 2018 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ProductProduct(models.Model): + _inherit = ["product.product", "stock.product.secondary.unit.mixin"] + _name = "product.product" + + stock_secondary_uom_id = fields.Many2one( + comodel_name="product.secondary.unit", + string="Second unit for inventory", + readonly=False, + domain="['|', ('product_id', '=', id)," + "'&', ('product_tmpl_id', '=', product_tmpl_id)," + " ('product_id', '=', False)]", + ) diff --git a/stock_secondary_unit/models/product_template.py b/stock_secondary_unit/models/product_template.py new file mode 100644 index 000000000000..c8b1845aa6e4 --- /dev/null +++ b/stock_secondary_unit/models/product_template.py @@ -0,0 +1,14 @@ +# Copyright 2018 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = ["product.template", "stock.product.secondary.unit.mixin"] + _name = "product.template" + + stock_secondary_uom_id = fields.Many2one( + comodel_name="product.secondary.unit", + domain="[('product_tmpl_id', '=', id), ('product_id', '=', False)]", + string="Second unit for inventory", + ) diff --git a/stock_secondary_unit/models/stock_move.py b/stock_secondary_unit/models/stock_move.py new file mode 100644 index 000000000000..ee6f4c858255 --- /dev/null +++ b/stock_secondary_unit/models/stock_move.py @@ -0,0 +1,70 @@ +# Copyright 2018 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, fields, models +from odoo.tools.float_utils import float_round + + +class StockMove(models.Model): + _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, + precompute=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() + + @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", "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", precompute=True + ) + + @api.model_create_multi + def create(self, vals_list): + for vals in vals_list: + move = self.env["stock.move"].browse(vals.get("move_id", False)) + if move.secondary_uom_id: + uom = self.env["uom.uom"].browse(vals["product_uom_id"]) + factor = move.secondary_uom_id.factor * uom.factor + move_line_qty = vals.get("product_uom_qty", vals.get("qty_done", 0.0)) + qty = float_round( + move_line_qty / (factor or 1.0), + precision_rounding=move.secondary_uom_id.uom_id.rounding, + ) + vals.update( + { + "secondary_uom_qty": qty, + "secondary_uom_id": move.secondary_uom_id.id, + } + ) + return super().create(vals_list) + + @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/models/stock_product_secondary_unit_mixin.py b/stock_secondary_unit/models/stock_product_secondary_unit_mixin.py new file mode 100644 index 000000000000..99796f4c1fd1 --- /dev/null +++ b/stock_secondary_unit/models/stock_product_secondary_unit_mixin.py @@ -0,0 +1,28 @@ +# Copyright 2018 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo import api, fields, models +from odoo.tools.float_utils import float_round + + +class StockProductSecondaryUnitMixin(models.AbstractModel): + _name = "stock.product.secondary.unit.mixin" + _description = "Stock Product Secondary Unit Mixin" + + secondary_unit_qty_available = fields.Float( + string="Quantity On Hand (2Unit)", + compute="_compute_secondary_unit_qty_available", + digits="Product Unit of Measure", + ) + + @api.depends("stock_secondary_uom_id") + def _compute_secondary_unit_qty_available(self): + for product in self: + if not product.stock_secondary_uom_id: + product.secondary_unit_qty_available = 0.0 + else: + qty = product.qty_available / ( + product.stock_secondary_uom_id.factor or 1.0 + ) + product.secondary_unit_qty_available = float_round( + qty, precision_rounding=product.uom_id.rounding + ) diff --git a/stock_secondary_unit/readme/CONTRIBUTORS.rst b/stock_secondary_unit/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000000..05d01eb2dc89 --- /dev/null +++ b/stock_secondary_unit/readme/CONTRIBUTORS.rst @@ -0,0 +1,8 @@ +* `Tecnativa `_ + + * Carlos Dauden + * Sergio Teruel + * Carlos Roca +* Kitti Upariphutthiphong +* Pimolnat Suntian +* Alan Ramos diff --git a/stock_secondary_unit/readme/DESCRIPTION.rst b/stock_secondary_unit/readme/DESCRIPTION.rst new file mode 100644 index 000000000000..ce69e8533cab --- /dev/null +++ b/stock_secondary_unit/readme/DESCRIPTION.rst @@ -0,0 +1,2 @@ +This module extends the functionality of stock module to allow define +other units with their conversion factor. diff --git a/stock_secondary_unit/readme/USAGE.rst b/stock_secondary_unit/readme/USAGE.rst new file mode 100644 index 000000000000..4d189ae535ac --- /dev/null +++ b/stock_secondary_unit/readme/USAGE.rst @@ -0,0 +1,8 @@ +To use this module you need to: + +#. Go to a *Product > General Information tab*. +#. Create any record in "Secondary unit of measure". +#. Set the conversion factor. +#. Go to *Inventory tab* and set a second unit of measure. +#. Push button 'Quantity on hand' and set quantities in stock for this product. +#. Go to product list and you can see the secondary unit value. diff --git a/stock_secondary_unit/report/report_deliveryslip.xml b/stock_secondary_unit/report/report_deliveryslip.xml new file mode 100644 index 000000000000..31e4afaf708e --- /dev/null +++ b/stock_secondary_unit/report/report_deliveryslip.xml @@ -0,0 +1,44 @@ + + + + + + diff --git a/stock_secondary_unit/static/description/icon.png b/stock_secondary_unit/static/description/icon.png new file mode 100644 index 000000000000..3a0328b516c4 Binary files /dev/null and b/stock_secondary_unit/static/description/icon.png differ diff --git a/stock_secondary_unit/static/description/index.html b/stock_secondary_unit/static/description/index.html new file mode 100644 index 000000000000..5f1eaa1e5979 --- /dev/null +++ b/stock_secondary_unit/static/description/index.html @@ -0,0 +1,441 @@ + + + + + + +Stock Secondary Unit + + + +
+

Stock Secondary Unit

+ + +

Production/Stable License: AGPL-3 OCA/stock-logistics-warehouse Translate me on Weblate Try me on Runbot

+

This module extends the functionality of stock module to allow define +other units with their conversion factor.

+

Table of contents

+ +
+

Usage

+

To use this module you need to:

+
    +
  1. Go to a Product > General Information tab.
  2. +
  3. Create any record in “Secondary unit of measure”.
  4. +
  5. Set the conversion factor.
  6. +
  7. Go to Inventory tab and set a second unit of measure.
  8. +
  9. Push button ‘Quantity on hand’ and set quantities in stock for this product.
  10. +
  11. Go to product list and you can see the secondary unit value.
  12. +
+
+
+

Bug Tracker

+

Bugs are tracked on GitHub Issues. +In case of trouble, please check there if your issue has already been reported. +If you spotted it first, help us smashing it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Tecnativa
  • +
+
+
+

Contributors

+ +
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use.

+

This module is part of the OCA/stock-logistics-warehouse project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + diff --git a/stock_secondary_unit/test-requirements.txt b/stock_secondary_unit/test-requirements.txt new file mode 100644 index 000000000000..b864c6e58b4e --- /dev/null +++ b/stock_secondary_unit/test-requirements.txt @@ -0,0 +1 @@ +odoo-addon-product_secondary_unit @ git+https://github.com/OCA/product-attribute.git@refs/pull/1325/head#subdirectory=setup/product_secondary_unit diff --git a/stock_secondary_unit/tests/__init__.py b/stock_secondary_unit/tests/__init__.py new file mode 100644 index 000000000000..38c91ad2e9f9 --- /dev/null +++ b/stock_secondary_unit/tests/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from . import test_stock_secondary_unit diff --git a/stock_secondary_unit/tests/test_stock_secondary_unit.py b/stock_secondary_unit/tests/test_stock_secondary_unit.py new file mode 100644 index 000000000000..34ea83e6c5ca --- /dev/null +++ b/stock_secondary_unit/tests/test_stock_secondary_unit.py @@ -0,0 +1,232 @@ +# Copyright 2018 Tecnativa - Sergio Teruel +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +from odoo.tests import Form, TransactionCase, tagged + + +@tagged("-at_install", "post_install") +class TestProductSecondaryUnit(TransactionCase): + @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.picking_type_out.show_operations = True + + 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"] + cls.attribute_color = ProductAttribute.create({"name": "test_color"}) + cls.attribute_value_white = ProductAttributeValue.create( + {"name": "test_white", "attribute_id": cls.attribute_color.id} + ) + cls.attribute_value_black = ProductAttributeValue.create( + {"name": "test_black", "attribute_id": cls.attribute_color.id} + ) + cls.product_template = cls.env["product.template"].create( + { + "name": "test", + "uom_id": cls.product_uom_kg.id, + "uom_po_id": cls.product_uom_kg.id, + "type": "product", + "secondary_uom_ids": [ + ( + 0, + 0, + { + "code": "A", + "name": "unit-500", + "uom_id": cls.product_uom_unit.id, + "factor": 0.5, + }, + ), + ( + 0, + 0, + { + "code": "B", + "name": "unit-900", + "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, + "value_ids": [ + (4, cls.attribute_value_white.id), + (4, cls.attribute_value_black.id), + ], + }, + ) + ], + } + ) + secondary_unit = cls.env["product.secondary.unit"].search( + [("product_tmpl_id", "=", cls.product_template.id)], limit=1 + ) + cls.product_template.product_variant_ids.write( + {"stock_secondary_uom_id": secondary_unit.id} + ) + StockQuant = cls.env["stock.quant"] + cls.quant_white = StockQuant.create( + { + "product_id": cls.product_template.product_variant_ids[0].id, + "location_id": cls.warehouse.lot_stock_id.id, + "quantity": 10.0, + } + ) + cls.quant_black = StockQuant.create( + { + "product_id": cls.product_template.product_variant_ids[1].id, + "location_id": cls.warehouse.lot_stock_id.id, + "quantity": 10.0, + } + ) + + def test_01_stock_secondary_unit_template(self): + self.assertEqual(self.product_template.secondary_unit_qty_available, 0) + + def test_02_stock_secondary_unit_variant(self): + for variant in self.product_template.product_variant_ids.filtered( + "product_template_attribute_value_ids" + ): + self.assertEqual(variant.secondary_unit_qty_available, 20) + + def test_03_stock_picking_secondary_unit(self): + StockPicking = self.env["stock.picking"] + product1 = self.product_template.product_variant_ids[0] + move_vals = { + "product_id": product1.id, + "name": product1.display_name, + "secondary_uom_id": product1.product_tmpl_id.secondary_uom_ids[0].id, + "product_uom": product1.uom_id.id, + "product_uom_qty": 10.0, + "location_id": self.location_supplier.id, + "location_dest_id": self.location_stock.id, + } + do_vals = { + "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), + ], # 2 moves + } + delivery_order = StockPicking.create(do_vals) + delivery_order.action_confirm() + # Move is merged into 1 line for both stock.move and stock.move.line + self.assertEqual(len(delivery_order.move_lines), 1) + self.assertEqual(len(delivery_order.move_line_ids), 1) + # Qty merged to 20, and secondary unit qty is 40line + uom_qty = sum(delivery_order.move_lines.mapped("product_uom_qty")) + secondary_uom_qty = sum( + delivery_order.move_line_ids.mapped("secondary_uom_qty") + ) + self.assertEqual(uom_qty, 20.0) + self.assertEqual(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.product_tmpl_id.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.product_tmpl_id.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.product_tmpl_id.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.product_tmpl_id.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.product_tmpl_id.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.product_tmpl_id.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.product_tmpl_id.secondary_uom_ids[1] + picking = picking_form.save() + picking.action_confirm() + self.assertEqual(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.product_tmpl_id.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.product_tmpl_id.secondary_uom_ids[0] + picking = picking_form.save() + picking.action_confirm() + self.assertEqual(len(picking.move_lines), 1) + self.assertEqual(picking.move_lines.secondary_uom_qty, 2) diff --git a/stock_secondary_unit/views/product_views.xml b/stock_secondary_unit/views/product_views.xml new file mode 100644 index 000000000000..40bc4b3789f2 --- /dev/null +++ b/stock_secondary_unit/views/product_views.xml @@ -0,0 +1,115 @@ + + + + + Product template Secondary Unit + product.template + + + + + + + + + + + product.template + + + + + + + + + product.product + + + + + + + + + product.template + + + + + + + + + + product.product + + + + + + + + + diff --git a/stock_secondary_unit/views/stock_move_views.xml b/stock_secondary_unit/views/stock_move_views.xml new file mode 100644 index 000000000000..712a70f9731d --- /dev/null +++ b/stock_secondary_unit/views/stock_move_views.xml @@ -0,0 +1,44 @@ + + + + + Stock Move Secondary Unit + stock.move.line + + + + + + + + + + stock.move.line.operations.tree + stock.move.line + + + + + + + + + diff --git a/stock_secondary_unit/views/stock_picking_views.xml b/stock_secondary_unit/views/stock_picking_views.xml new file mode 100644 index 000000000000..b0b5d37950f8 --- /dev/null +++ b/stock_secondary_unit/views/stock_picking_views.xml @@ -0,0 +1,50 @@ + + + + + Stock Picking Secondary Unit + stock.picking + + + + + + + + + + + + +