diff --git a/stock_product_pack/README.rst b/stock_product_pack/README.rst new file mode 100644 index 000000000..31221ec62 --- /dev/null +++ b/stock_product_pack/README.rst @@ -0,0 +1,112 @@ +================== +Stock product Pack +================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:ebc372d1ed11084b43923fe380d6ec0197ee89b67458ebb1aebadc39d0a93953 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |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%2Fproduct--pack-lightgray.png?logo=github + :target: https://github.com/OCA/product-pack/tree/18.0/stock_product_pack + :alt: OCA/product-pack +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/product-pack-18-0/product-pack-18-0-stock_product_pack + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/product-pack&target_branch=18.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module allows you to get *On Hand* and *Forecasted* values +calculated from components of the pack. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module, you need to: + +1. Go to *Inventory -> Master Data -> Product*, create a product and + check "Is Pack?" field then set Pack Type and Pack component price. +2. Choose products to include in the pack. +3. Go to *Inventory -> Operations -> Transfers* and create transfer + related to components of a Pack 'X'. +4. Then go to *Inventory -> Master Data -> Product*, find the Pack 'X' + and you will see in the smart buttons that show *On Hand* and + *Forecasted* the quantities based on the *On Hand* and *Forecasted* + quantities of its components. That is the minimum quantities of its + components. + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +------- + +* NaN·tic +* ADHOC SA +* Tecnativa + +Contributors +------------ + +- `Tecnativa `__: + + - Ernesto Tejeda + - Pedro M. Baeza + - Sergio Teruel + - João Marques + +- `ADHOC SA `__: + + - Bruno Zanotti + +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. + +.. |maintainer-ernestotejeda| image:: https://github.com/ernestotejeda.png?size=40px + :target: https://github.com/ernestotejeda + :alt: ernestotejeda + +Current `maintainer `__: + +|maintainer-ernestotejeda| + +This module is part of the `OCA/product-pack `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/stock_product_pack/__init__.py b/stock_product_pack/__init__.py new file mode 100644 index 000000000..cb45f2710 --- /dev/null +++ b/stock_product_pack/__init__.py @@ -0,0 +1,2 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import models diff --git a/stock_product_pack/__manifest__.py b/stock_product_pack/__manifest__.py new file mode 100644 index 000000000..882d28475 --- /dev/null +++ b/stock_product_pack/__manifest__.py @@ -0,0 +1,21 @@ +# Copyright 2021 Julio Cesar Bravo Rodriguez +# Copyright 2019 NaN (http://www.nan-tic.com) - Àngel Àlvarez +# Copyright 2019 Tecnativa - Ernesto Tejeda +# Copyright 2020 Tecnativa - João Marques +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). +{ + "name": "Stock product Pack", + "version": "18.0.1.0.0", + "category": "Warehouse", + "summary": "This module allows you to get the right available quantities " + "of the packs", + "website": "https://github.com/OCA/product-pack", + "author": "NaN·tic, ADHOC SA, Tecnativa, Odoo Community Association (OCA)", + "maintainers": ["ernestotejeda"], + "license": "AGPL-3", + "depends": ["product_pack", "stock"], + "data": ["security/ir.model.access.csv", "views/product_template_views.xml"], + "installable": True, + "auto_install": True, + "application": False, +} diff --git a/stock_product_pack/i18n/es.po b/stock_product_pack/i18n/es.po new file mode 100644 index 000000000..ba2bf64d5 --- /dev/null +++ b/stock_product_pack/i18n/es.po @@ -0,0 +1,53 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2019-09-18 14:54+0000\n" +"PO-Revision-Date: 2023-09-03 13:43+0000\n" +"Last-Translator: Ivorra78 \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: stock_product_pack +#: model:ir.model.fields,field_description:stock_product_pack.field_product_product__dont_create_move +#: model:ir.model.fields,field_description:stock_product_pack.field_product_template__dont_create_move +msgid "Don't create move" +msgstr "No crear movimiento" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_procurement_group +msgid "Procurement Group" +msgstr "Grupo de Adquisiciones" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_product_template +msgid "Product" +msgstr "Producto" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_product_product +msgid "Product Variant" +msgstr "Variante del producto" + +#. module: stock_product_pack +#: model:ir.model.fields,help:stock_product_pack.field_product_product__dont_create_move +#: model:ir.model.fields,help:stock_product_pack.field_product_template__dont_create_move +msgid "" +"With this option, the pack won't create an stock.move and will be set as " +"delivered upon sale confirmation. This is useful to use get pack stock " +"availability (type = 'product') but without actually having stock and moves " +"of it." +msgstr "" +"Con esta opción, el paquete no creará un stock.move y se establecerá como " +"entregado tras la confirmación de la venta. Esto es útil para obtener la " +"disponibilidad de existencias del paquete (type = 'product') pero sin tener " +"existencias ni movimientos del mismo." diff --git a/stock_product_pack/i18n/fr.po b/stock_product_pack/i18n/fr.po new file mode 100644 index 000000000..92e6eece6 --- /dev/null +++ b/stock_product_pack/i18n/fr.po @@ -0,0 +1,48 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2020-05-28 12:19+0000\n" +"Last-Translator: Yann Papouin \n" +"Language-Team: none\n" +"Language: fr\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 3.10\n" + +#. module: stock_product_pack +#: model:ir.model.fields,field_description:stock_product_pack.field_product_product__dont_create_move +#: model:ir.model.fields,field_description:stock_product_pack.field_product_template__dont_create_move +msgid "Don't create move" +msgstr "" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_procurement_group +msgid "Procurement Group" +msgstr "" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_product_template +msgid "Product" +msgstr "Article" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_product_product +msgid "Product Variant" +msgstr "" + +#. module: stock_product_pack +#: model:ir.model.fields,help:stock_product_pack.field_product_product__dont_create_move +#: model:ir.model.fields,help:stock_product_pack.field_product_template__dont_create_move +msgid "" +"With this option, the pack won't create an stock.move and will be set as " +"delivered upon sale confirmation. This is useful to use get pack stock " +"availability (type = 'product') but without actually having stock and moves " +"of it." +msgstr "" diff --git a/stock_product_pack/i18n/it.po b/stock_product_pack/i18n/it.po new file mode 100644 index 000000000..6afb1bf24 --- /dev/null +++ b/stock_product_pack/i18n/it.po @@ -0,0 +1,52 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-11-21 11:36+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: stock_product_pack +#: model:ir.model.fields,field_description:stock_product_pack.field_product_product__dont_create_move +#: model:ir.model.fields,field_description:stock_product_pack.field_product_template__dont_create_move +msgid "Don't create move" +msgstr "Non creare il movimento" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_procurement_group +msgid "Procurement Group" +msgstr "Gruppo di approvvigionamento" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_product_template +msgid "Product" +msgstr "Prodotto" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_product_product +msgid "Product Variant" +msgstr "Variante prodotto" + +#. module: stock_product_pack +#: model:ir.model.fields,help:stock_product_pack.field_product_product__dont_create_move +#: model:ir.model.fields,help:stock_product_pack.field_product_template__dont_create_move +msgid "" +"With this option, the pack won't create an stock.move and will be set as " +"delivered upon sale confirmation. This is useful to use get pack stock " +"availability (type = 'product') but without actually having stock and moves " +"of it." +msgstr "" +"Con questa opzione, il collo non creerà un movimento di magazzino e verrà " +"impostato a consegnato alla conferma della vendita. Questo è utile per " +"utilizzare la disponibilità di magazzino del collo (tipo = 'prodotto') ma " +"senza avere realmente movimenti." diff --git a/stock_product_pack/i18n/pt.po b/stock_product_pack/i18n/pt.po new file mode 100644 index 000000000..f6fadc0df --- /dev/null +++ b/stock_product_pack/i18n/pt.po @@ -0,0 +1,48 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 12.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2019-12-12 22:05+0000\n" +"Last-Translator: Pedro Castro Silva \n" +"Language-Team: none\n" +"Language: pt\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n > 1;\n" +"X-Generator: Weblate 3.9.1\n" + +#. module: stock_product_pack +#: model:ir.model.fields,field_description:stock_product_pack.field_product_product__dont_create_move +#: model:ir.model.fields,field_description:stock_product_pack.field_product_template__dont_create_move +msgid "Don't create move" +msgstr "" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_procurement_group +msgid "Procurement Group" +msgstr "" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_product_template +msgid "Product" +msgstr "Produto" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_product_product +msgid "Product Variant" +msgstr "" + +#. module: stock_product_pack +#: model:ir.model.fields,help:stock_product_pack.field_product_product__dont_create_move +#: model:ir.model.fields,help:stock_product_pack.field_product_template__dont_create_move +msgid "" +"With this option, the pack won't create an stock.move and will be set as " +"delivered upon sale confirmation. This is useful to use get pack stock " +"availability (type = 'product') but without actually having stock and moves " +"of it." +msgstr "" diff --git a/stock_product_pack/i18n/stock_product_pack.pot b/stock_product_pack/i18n/stock_product_pack.pot new file mode 100644 index 000000000..4620ff5f0 --- /dev/null +++ b/stock_product_pack/i18n/stock_product_pack.pot @@ -0,0 +1,45 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * stock_product_pack +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 16.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_product_pack +#: model:ir.model.fields,field_description:stock_product_pack.field_product_product__dont_create_move +#: model:ir.model.fields,field_description:stock_product_pack.field_product_template__dont_create_move +msgid "Don't create move" +msgstr "" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_procurement_group +msgid "Procurement Group" +msgstr "" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_product_template +msgid "Product" +msgstr "" + +#. module: stock_product_pack +#: model:ir.model,name:stock_product_pack.model_product_product +msgid "Product Variant" +msgstr "" + +#. module: stock_product_pack +#: model:ir.model.fields,help:stock_product_pack.field_product_product__dont_create_move +#: model:ir.model.fields,help:stock_product_pack.field_product_template__dont_create_move +msgid "" +"With this option, the pack won't create an stock.move and will be set as " +"delivered upon sale confirmation. This is useful to use get pack stock " +"availability (type = 'product') but without actually having stock and moves " +"of it." +msgstr "" diff --git a/stock_product_pack/models/__init__.py b/stock_product_pack/models/__init__.py new file mode 100644 index 000000000..0637be5c0 --- /dev/null +++ b/stock_product_pack/models/__init__.py @@ -0,0 +1,4 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). +from . import product_template +from . import product_product +from . import stock_rule diff --git a/stock_product_pack/models/product_product.py b/stock_product_pack/models/product_product.py new file mode 100644 index 000000000..a24a6559b --- /dev/null +++ b/stock_product_pack/models/product_product.py @@ -0,0 +1,102 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# Copyright 2020 Tecnativa - João Marques +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import math + +from odoo import api, models + + +class ProductProduct(models.Model): + _inherit = "product.product" + + def _compute_quantities_dict( + self, lot_id, owner_id, package_id, from_date=False, to_date=False + ): + packs = self.filtered("pack_ok") + subproducts = packs.pack_line_ids.filtered( + lambda p: p.product_id.type == "consu" + ).mapped("product_id") + res = super(ProductProduct, self | subproducts)._compute_quantities_dict( + lot_id, owner_id, package_id, from_date=from_date, to_date=to_date + ) + for pack in packs.with_context(prefetch_fields=False): + pack_qty_available = [] + pack_virtual_available = [] + pack_free_qty = [] + + for line in pack.pack_line_ids.filtered( + lambda p: p.product_id.type == "consu" + ): + sub_qty = line.quantity + if sub_qty: + pack_qty_available.append( + math.floor( + ( + res[line.product_id.id]["qty_available"] + - res[line.product_id.id]["outgoing_qty"] + ) + / sub_qty + ) + ) + pack_virtual_available.append( + math.floor( + res[line.product_id.id]["virtual_available"] / sub_qty + ) + ) + pack_free_qty.append( + math.floor(res[line.product_id.id]["free_qty"] / sub_qty) + ) + res[pack.id] = { + "qty_available": (pack_qty_available and min(pack_qty_available) or 0), + "free_qty": (pack_free_qty and min(pack_free_qty) or 0), + "incoming_qty": 0, + "outgoing_qty": 0, + "virtual_available": ( + pack_virtual_available and min(pack_virtual_available) or 0 + ), + } + return res + + @api.depends( + "stock_move_ids.product_qty", + "pack_line_ids.product_id.stock_move_ids.product_qty", + "stock_move_ids.state", + "pack_line_ids.product_id.stock_move_ids.state", + "stock_move_ids.quantity", + "pack_line_ids.product_id.stock_move_ids.quantity", + ) + @api.depends_context( + "lot_id", + "owner_id", + "package_id", + "from_date", + "to_date", + "location", + "warehouse", + ) + def _compute_quantities(self): + """In v13 Odoo introduces a filter for products not services. + To keep how it was working on v12 we try to get stock for + service products if they are pack. + """ + service_pack_products = self.filtered( + lambda p: p.type == "service" and p.pack_ok + ) + result = super( + ProductProduct, self - service_pack_products + )._compute_quantities() + res = service_pack_products._compute_quantities_dict( + self._context.get("lot_id"), + self._context.get("owner_id"), + self._context.get("package_id"), + self._context.get("from_date"), + self._context.get("to_date"), + ) + for product in service_pack_products: + product.qty_available = res[product.id]["qty_available"] + product.incoming_qty = res[product.id]["incoming_qty"] + product.outgoing_qty = res[product.id]["outgoing_qty"] + product.virtual_available = res[product.id]["virtual_available"] + product.free_qty = res[product.id]["free_qty"] + return result diff --git a/stock_product_pack/models/product_template.py b/stock_product_pack/models/product_template.py new file mode 100644 index 000000000..04aba34ff --- /dev/null +++ b/stock_product_pack/models/product_template.py @@ -0,0 +1,16 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import fields, models + + +class ProductTemplate(models.Model): + _inherit = "product.template" + + dont_create_move = fields.Boolean( + string="Don't create move", + help="With this option, the pack won't create an stock.move and will" + " be set as delivered upon sale confirmation. This is useful to use " + "get pack stock availability (type = 'product') but" + " without actually having stock and moves of it.", + ) diff --git a/stock_product_pack/models/stock_rule.py b/stock_product_pack/models/stock_rule.py new file mode 100644 index 000000000..316977e36 --- /dev/null +++ b/stock_product_pack/models/stock_rule.py @@ -0,0 +1,23 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from odoo import api, models + + +class ProcurementGroup(models.Model): + _inherit = "procurement.group" + + @api.model + def run(self, procurements, raise_user_error=True): + """If 'run' is called on a pack product storable. + we remove the procurement with this product pack. + """ + for procurement in procurements: + if ( + procurement.product_id + and procurement.product_id.pack_ok + and procurement.product_id.dont_create_move + and procurement.product_id.pack_type == "detailed" + ): + procurements.remove(procurement) + + return super().run(procurements, raise_user_error=raise_user_error) diff --git a/stock_product_pack/pyproject.toml b/stock_product_pack/pyproject.toml new file mode 100644 index 000000000..4231d0ccc --- /dev/null +++ b/stock_product_pack/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["whool"] +build-backend = "whool.buildapi" diff --git a/stock_product_pack/readme/CONTRIBUTORS.md b/stock_product_pack/readme/CONTRIBUTORS.md new file mode 100644 index 000000000..139c58419 --- /dev/null +++ b/stock_product_pack/readme/CONTRIBUTORS.md @@ -0,0 +1,7 @@ +- [Tecnativa](https://www.tecnativa.com): + - Ernesto Tejeda + - Pedro M. Baeza + - Sergio Teruel + - João Marques +- [ADHOC SA](https://www.adhoc.com.ar): + - Bruno Zanotti diff --git a/stock_product_pack/readme/DESCRIPTION.md b/stock_product_pack/readme/DESCRIPTION.md new file mode 100644 index 000000000..0fb4d3844 --- /dev/null +++ b/stock_product_pack/readme/DESCRIPTION.md @@ -0,0 +1,2 @@ +This module allows you to get *On Hand* and *Forecasted* values +calculated from components of the pack. diff --git a/stock_product_pack/readme/USAGE.md b/stock_product_pack/readme/USAGE.md new file mode 100644 index 000000000..bd357773f --- /dev/null +++ b/stock_product_pack/readme/USAGE.md @@ -0,0 +1,12 @@ +To use this module, you need to: + +1. Go to *Inventory -\> Master Data -\> Product*, create a product and + check "Is Pack?" field then set Pack Type and Pack component price. +2. Choose products to include in the pack. +3. Go to *Inventory -\> Operations -\> Transfers* and create transfer + related to components of a Pack 'X'. +4. Then go to *Inventory -\> Master Data -\> Product*, find the Pack + 'X' and you will see in the smart buttons that show *On Hand* and + *Forecasted* the quantities based on the *On Hand* and *Forecasted* + quantities of its components. That is the minimum quantities of its + components. diff --git a/stock_product_pack/security/ir.model.access.csv b/stock_product_pack/security/ir.model.access.csv new file mode 100644 index 000000000..124c24182 --- /dev/null +++ b/stock_product_pack/security/ir.model.access.csv @@ -0,0 +1,2 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_product_pack_line_stock_manager,product.pack.line,product_pack.model_product_pack_line,stock.group_stock_manager,1,1,1,1 diff --git a/stock_product_pack/static/description/icon.png b/stock_product_pack/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/stock_product_pack/static/description/icon.png differ diff --git a/stock_product_pack/static/description/index.html b/stock_product_pack/static/description/index.html new file mode 100644 index 000000000..cb2ecb6ee --- /dev/null +++ b/stock_product_pack/static/description/index.html @@ -0,0 +1,455 @@ + + + + + +Stock product Pack + + + +
+

Stock product Pack

+ + +

Beta License: AGPL-3 OCA/product-pack Translate me on Weblate Try me on Runboat

+

This module allows you to get On Hand and Forecasted values +calculated from components of the pack.

+

Table of contents

+ +
+

Usage

+

To use this module, you need to:

+
    +
  1. Go to Inventory -> Master Data -> Product, create a product and +check “Is Pack?” field then set Pack Type and Pack component price.
  2. +
  3. Choose products to include in the pack.
  4. +
  5. Go to Inventory -> Operations -> Transfers and create transfer +related to components of a Pack ‘X’.
  6. +
  7. Then go to Inventory -> Master Data -> Product, find the Pack ‘X’ +and you will see in the smart buttons that show On Hand and +Forecasted the quantities based on the On Hand and Forecasted +quantities of its components. That is the minimum quantities of its +components.
  8. +
+
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

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

+
+
+

Credits

+
+

Authors

+
    +
  • NaN·tic
  • +
  • ADHOC SA
  • +
  • Tecnativa
  • +
+
+
+

Contributors

+
    +
  • Tecnativa:
      +
    • Ernesto Tejeda
    • +
    • Pedro M. Baeza
    • +
    • Sergio Teruel
    • +
    • João Marques
    • +
    +
  • +
  • ADHOC SA:
      +
    • Bruno Zanotti
    • +
    +
  • +
+
+
+

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.

+

Current maintainer:

+

ernestotejeda

+

This module is part of the OCA/product-pack project on GitHub.

+

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

+
+
+
+ + diff --git a/stock_product_pack/tests/__init__.py b/stock_product_pack/tests/__init__.py new file mode 100644 index 000000000..21c96b0cc --- /dev/null +++ b/stock_product_pack/tests/__init__.py @@ -0,0 +1,3 @@ +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +from . import test_stock_product_pack diff --git a/stock_product_pack/tests/test_stock_product_pack.py b/stock_product_pack/tests/test_stock_product_pack.py new file mode 100644 index 000000000..785d31ef0 --- /dev/null +++ b/stock_product_pack/tests/test_stock_product_pack.py @@ -0,0 +1,227 @@ +# Copyright 2019 Tecnativa - Ernesto Tejeda +# Copyright 2020 Tecnativa - João Marques +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import logging + +from odoo.tests import Form, TransactionCase, tagged + +_logger = logging.getLogger(__name__) + + +@tagged("post_install", "-at_install") +class TestStockProductPack(TransactionCase): + @classmethod + def setUpClass(cls): + super().setUpClass() + category_all_id = cls.env.ref("product.product_category_all").id + cls.product_obj = cls.env["product.product"] + cls.stock_rule_obj = cls.env["stock.rule"] + # The model stock doesn't add anymore the 'product' + # selection to the product type. + # Thus the type is changed to 'consu' + component_1 = cls.product_obj.create( + { + "name": "Component 1", + "type": "consu", + "is_storable": True, + "categ_id": category_all_id, + } + ) + component_2 = cls.product_obj.create( + { + "name": "Component 2", + "type": "consu", + "is_storable": True, + "categ_id": category_all_id, + } + ) + component_3 = cls.product_obj.create( + { + "name": "Component 3", + "type": "service", + "categ_id": category_all_id, + } + ) + component_4 = cls.product_obj.create( + { + "name": "Component 4", + "type": "consu", + "is_storable": True, + "categ_id": category_all_id, + } + ) + cls.pack_dc = cls.product_obj.create( + { + "name": "Pack", + "type": "consu", + "pack_ok": True, + "pack_type": "detailed", + "pack_component_price": "detailed", + "categ_id": category_all_id, + "pack_line_ids": [ + ( + 0, + 0, + {"product_id": component_1.id, "quantity": 1}, + ), + ( + 0, + 0, + {"product_id": component_2.id, "quantity": 1}, + ), + ( + 0, + 0, + {"product_id": component_3.id, "quantity": 1}, + ), + ( + 0, + 0, + {"product_id": component_4.id, "quantity": 1}, + ), + ], + } + ) + warehouse = cls.env["stock.warehouse"].search( + [("company_id", "=", cls.env.user.id)], limit=1 + ) + cls.stock_rule = cls.stock_rule_obj.create( + { + "name": "Stock to Costumers", + "action": "pull", + "picking_type_id": cls.env.ref("stock.picking_type_internal").id, + "route_id": cls.env.ref("stock.route_warehouse0_mto").id, + "procure_method": "make_to_stock", + "warehouse_id": warehouse.id, + "location_dest_id": cls.env.ref("stock.stock_location_stock").id, + } + ) + cls.pack_dc_with_dm = cls.product_obj.create( + { + "name": "Pack With storeable and not move product", + "type": "consu", + "pack_ok": True, + "dont_create_move": True, + "pack_type": "detailed", + "pack_component_price": "detailed", + "categ_id": category_all_id, + "pack_line_ids": [ + ( + 0, + 0, + {"product_id": component_1.id, "quantity": 1}, + ), + ( + 0, + 0, + {"product_id": component_2.id, "quantity": 1}, + ), + ( + 0, + 0, + {"product_id": component_3.id, "quantity": 1}, + ), + ( + 0, + 0, + {"product_id": component_4.id, "quantity": 1}, + ), + ], + } + ) + + def test_compute_quantities_dict(self): + location_id = (self.env.ref("stock.stock_location_suppliers").id,) + location_dest_id = (self.env.ref("stock.stock_location_stock").id,) + components = self.pack_dc.pack_line_ids.mapped("product_id") + + picking = self.env["stock.picking"].create( + { + "partner_id": self.env.ref("base.res_partner_4").id, + "picking_type_id": self.env.ref("stock.picking_type_in").id, + "location_id": location_id, + "location_dest_id": location_dest_id, + "move_ids": [ + ( + 0, + 0, + { + "name": "incoming_move_test_01", + "product_id": components[0].id, + "product_uom_qty": 5, + "product_uom": components[0].uom_id.id, + "location_id": location_id, + "location_dest_id": location_dest_id, + }, + ), + ( + 0, + 0, + { + "name": "incoming_move_test_02", + "product_id": components[1].id, + "product_uom_qty": 7, + "product_uom": components[1].uom_id.id, + "location_id": location_id, + "location_dest_id": location_dest_id, + }, + ), + ( + 0, + 0, + { + "name": "incoming_move_test_03", + "product_id": components[3].id, + "product_uom_qty": 9, + "product_uom": components[3].uom_id.id, + "location_id": location_id, + "location_dest_id": location_dest_id, + }, + ), + ], + } + ) + picking.action_confirm() + self.assertEqual(self.pack_dc.virtual_available, 5) + self.assertEqual(self.pack_dc.qty_available, 0) + wizard_dict = picking.button_validate() + if wizard_dict is not True: + wizard = Form( + self.env[(wizard_dict.get("res_model"))].with_context( + **wizard_dict["context"] + ) + ).save() + wizard.process() + self.assertEqual(self.pack_dc.virtual_available, 5) + self.assertEqual(self.pack_dc.qty_available, 5) + + def test_pack_with_dont_move_the_parent(self): + """Run a procurement for prod pack products when there are only 5 in stock then + check that MTO is applied on the moves when the rule is set to 'mts_else_mto' + """ + + def create_orderpoint(product, qty_min, qty_max, location, group): + return self.env["stock.warehouse.orderpoint"].create( + { + "name": f"OP/{product.name}", + "product_id": product.id, + "product_min_qty": qty_min, + "product_max_qty": qty_max, + "location_id": location.id, + "group_id": group.id, + } + ) + + pg = self.env["procurement.group"].create({"name": "Test-product Pack"}) + create_orderpoint( + self.pack_dc_with_dm, + 10, + 155, + self.env.ref("stock.stock_location_stock"), + pg, + ) + # self.env["stock.scheduler.compute"].create({}).procure_calculation() + picking_ids = self.env["stock.picking"].search([("group_id", "=", pg.id)]) + # we need to ensure that only the compents of the packs are in the moves. + self.assertFalse(self.pack_dc_with_dm in picking_ids.move_ids.product_id) diff --git a/stock_product_pack/views/product_template_views.xml b/stock_product_pack/views/product_template_views.xml new file mode 100644 index 000000000..5b3414fc2 --- /dev/null +++ b/stock_product_pack/views/product_template_views.xml @@ -0,0 +1,19 @@ + + + + + + product.template.pack.form + product.template + + + + + + + + diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 000000000..0eb1ac89f --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,2 @@ +odoo_test_helper +odoo-addon-product_pack @ git+https://github.com/OCA/product-pack.git@refs/pull/189/head#subdirectory=product_pack