diff --git a/mrp_bom_hierarchy/README.rst b/mrp_bom_hierarchy/README.rst new file mode 100644 index 000000000..f9e0f4ac7 --- /dev/null +++ b/mrp_bom_hierarchy/README.rst @@ -0,0 +1,84 @@ +================= +MRP BoM Hierarchy +================= + +.. !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |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%2Fmanufacture-lightgray.png?logo=github + :target: https://github.com/OCA/manufacture/tree/14.0/mrp_bom_hierarchy + :alt: OCA/manufacture +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/manufacture-14-0/manufacture-14-0-mrp_bom_hierarchy + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runbot-Try%20me-875A7B.png + :target: https://runbot.odoo-community.org/runbot/129/14.0 + :alt: Try me on Runbot + +|badge1| |badge2| |badge3| |badge4| |badge5| + +This module extends the functionality of Bill of Materials +to support users to better maintain the BoM hierarchy. + +The user can navigate from the tree view to child's BoM or parent's BoM, +or to the product's BoM components with a single click. + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +To use this module, you need to go to 'Manufacturing | Products | Bill of +Materials'. + +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 +~~~~~~~ + +* ForgeFlow + +Contributors +~~~~~~~~~~~~ + +* Jordi Ballester Alomar +* Miquel Raïch Regué + +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/manufacture `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/mrp_bom_hierarchy/__init__.py b/mrp_bom_hierarchy/__init__.py new file mode 100644 index 000000000..9186ee3ad --- /dev/null +++ b/mrp_bom_hierarchy/__init__.py @@ -0,0 +1 @@ +from . import model diff --git a/mrp_bom_hierarchy/__manifest__.py b/mrp_bom_hierarchy/__manifest__.py new file mode 100644 index 000000000..4c3556f64 --- /dev/null +++ b/mrp_bom_hierarchy/__manifest__.py @@ -0,0 +1,18 @@ +# Copyright 2015-22 ForgeFlow S.L. (https://www.forgeflow.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). + +{ + "name": "MRP BoM Hierarchy", + "summary": "Make it easy to navigate through BoM hierarchy.", + "version": "15.0.1.0.0", + "author": "ForgeFlow, Odoo Community Association (OCA)", + "category": "Manufacturing", + "depends": ["mrp"], + "website": "https://github.com/OCA/manufacture", + "license": "AGPL-3", + "data": [ + "view/mrp.xml", + ], + "installable": True, + "auto_install": False, +} diff --git a/mrp_bom_hierarchy/i18n/mrp_bom_hierarchy.pot b/mrp_bom_hierarchy/i18n/mrp_bom_hierarchy.pot new file mode 100644 index 000000000..6d3afc7ce --- /dev/null +++ b/mrp_bom_hierarchy/i18n/mrp_bom_hierarchy.pot @@ -0,0 +1,129 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * mrp_bom_hierarchy +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.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: mrp_bom_hierarchy +#: model:ir.model,name:mrp_bom_hierarchy.model_mrp_bom +msgid "Bill of Material" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model:ir.model,name:mrp_bom_hierarchy.model_mrp_bom_line +msgid "Bill of Material Line" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model_terms:ir.ui.view,arch_db:mrp_bom_hierarchy.mrp_bom_hierarchy_tree_view +msgid "Child BoMs" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom__child_bom_ids +msgid "Child Bom" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom__display_name +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom_line__display_name +msgid "Display Name" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model_terms:ir.ui.view,arch_db:mrp_bom_hierarchy.view_mrp_bom_filter +msgid "Has child BoMs" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom__has_child +msgid "Has components" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model_terms:ir.ui.view,arch_db:mrp_bom_hierarchy.view_mrp_bom_filter +msgid "Has parent BoMs" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom_line__has_bom +msgid "Has sub BoM" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom__id +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom_line__id +msgid "ID" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom__product_default_code +msgid "Internal Reference" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom__has_parent +msgid "Is component" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom____last_update +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom_line____last_update +msgid "Last Modified on" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model_terms:ir.ui.view,arch_db:mrp_bom_hierarchy.mrp_bom_hierarchy_tree_view +msgid "Parent BoMs" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom__parent_bom_ids +msgid "Parent Bom" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model:ir.model.fields,field_description:mrp_bom_hierarchy.field_mrp_bom__product_has_other_bom +msgid "Product has other BoMs" +msgstr "" + +#. module: mrp_bom_hierarchy +#: code:addons/mrp_bom_hierarchy/model/mrp_bom.py:0 +#: code:addons/mrp_bom_hierarchy/model/mrp_bom.py:0 +#, python-format +msgid "This operator is not supported" +msgstr "" + +#. module: mrp_bom_hierarchy +#: code:addons/mrp_bom_hierarchy/model/mrp_bom.py:0 +#: code:addons/mrp_bom_hierarchy/model/mrp_bom.py:0 +#, python-format +msgid "Value should be True or False (not %s)" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model_terms:ir.ui.view,arch_db:mrp_bom_hierarchy.mrp_bom_form_view +msgid "View product's BoM" +msgstr "" + +#. module: mrp_bom_hierarchy +#: model_terms:ir.ui.view,arch_db:mrp_bom_hierarchy.mrp_bom_hierarchy_tree_view +msgid "View product's other BoMs" +msgstr "" + +#. module: mrp_bom_hierarchy +#: code:addons/mrp_bom_hierarchy/model/mrp_bom.py:0 +#, python-format +msgid "" +"You should provide either a product or a product template to search a BoM " +"Line" +msgstr "" diff --git a/mrp_bom_hierarchy/model/__init__.py b/mrp_bom_hierarchy/model/__init__.py new file mode 100644 index 000000000..a352efe9b --- /dev/null +++ b/mrp_bom_hierarchy/model/__init__.py @@ -0,0 +1 @@ +from . import mrp_bom diff --git a/mrp_bom_hierarchy/model/mrp_bom.py b/mrp_bom_hierarchy/model/mrp_bom.py new file mode 100644 index 000000000..8fcbb8d3b --- /dev/null +++ b/mrp_bom_hierarchy/model/mrp_bom.py @@ -0,0 +1,264 @@ +# Copyright 2015-22 ForgeFlow S.L. (https://www.forgeflow.com) +# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl.html). +import operator as py_operator + +from odoo import _, api, fields, models +from odoo.exceptions import UserError + + +class MrpBom(models.Model): + _inherit = "mrp.bom" + _order = "sequence, code, product_default_code, id" + + @api.depends("bom_line_ids.bom_id", "product_id", "product_tmpl_id") + def _compute_product_has_other_bom(self): + for bom in self: + if bom.product_id: + bom_ids = self.env["mrp.bom"].search( + [("product_id", "=", bom.product_id.id), ("id", "!=", bom.id)], + ) + else: + bom_ids = self.env["mrp.bom"].search( + [ + ("product_tmpl_id", "=", bom.product_tmpl_id.id), + ("id", "!=", bom.id), + ], + ) + if bom_ids: + bom.product_has_other_bom = True + else: + bom.product_has_other_bom = False + + @api.depends("bom_line_ids.bom_id", "product_id", "product_tmpl_id") + def _compute_parent_bom_ids(self): + for bom in self: + parent_bom_line_ids = self.env["mrp.bom.line"]._bom_line_find( + product_tmpl=bom.product_id.product_tmpl_id or bom.product_tmpl_id, + product=bom.product_id, + ) + if parent_bom_line_ids: + bom.parent_bom_ids = parent_bom_line_ids.bom_id + bom.has_parent = True + else: + bom.parent_bom_ids = False + bom.has_parent = False + + @api.depends("bom_line_ids.bom_id", "bom_line_ids.product_id") + def _compute_child_bom_ids(self): + for bom in self: + bom_line_ids = bom.bom_line_ids + bom.child_bom_ids = bom_line_ids.child_bom_id + bom.has_child = bool(bom.child_bom_ids) + + def _search_has_child(self, operator, value): + if operator not in ["=", "!="]: + raise UserError(_("This operator is not supported")) + if value == "True": + value = True + elif value == "False": + value = False + if not isinstance(value, bool): + raise UserError(_("Value should be True or False (not %s)") % value) + ops = {"=": py_operator.eq, "!=": py_operator.ne} + ids = [] + for bom in self.search([]): + if ops[operator](value, bom.has_child): + ids.append(bom.id) + return [("id", "in", ids)] + + def _search_has_parent(self, operator, value): + if operator not in ["=", "!="]: + raise UserError(_("This operator is not supported")) + if value == "True": + value = True + elif value == "False": + value = False + if not isinstance(value, bool): + raise UserError(_("Value should be True or False (not %s)") % value) + ops = {"=": py_operator.eq, "!=": py_operator.ne} + ids = [] + for bom in self.search([]): + if ops[operator](value, bom.has_parent): + ids.append(bom.id) + return [("id", "in", ids)] + + @api.depends( + "product_id", + "product_id.default_code", + "product_id.product_tmpl_id.default_code", + "product_tmpl_id.default_code", + ) + def _compute_internal_reference(self): + for bom in self: + bom.product_default_code = ( + bom.product_id.default_code + or bom.product_id.product_tmpl_id.default_code + or bom.product_tmpl_id.default_code + ) + + child_bom_ids = fields.One2many("mrp.bom", compute="_compute_child_bom_ids") + parent_bom_ids = fields.One2many("mrp.bom", compute="_compute_parent_bom_ids") + has_child = fields.Boolean( + string="Has components", + compute="_compute_child_bom_ids", + search="_search_has_child", + ) + has_parent = fields.Boolean( + string="Is component", + compute="_compute_parent_bom_ids", + search="_search_has_parent", + ) + product_has_other_bom = fields.Boolean( + string="Product has other BoMs", + compute="_compute_product_has_other_bom", + ) + product_default_code = fields.Char( + string="Internal Reference", + compute="_compute_internal_reference", + store="True", + ) + + def action_open_child_tree_view(self): + self.ensure_one() + res = self.env["ir.actions.actions"]._for_xml_id("mrp.mrp_bom_form_action") + res["context"] = {"default_bom_line_ids": self.bom_line_ids.ids} + if self.child_bom_ids: + res["domain"] = ( + "[('id', 'in', [" + ",".join(map(str, self.child_bom_ids.ids)) + "])]" + ) + return res + + def action_open_parent_tree_view(self): + self.ensure_one() + res = self.env["ir.actions.actions"]._for_xml_id("mrp.mrp_bom_form_action") + if self.parent_bom_ids: + res["domain"] = ( + "[('id', 'in', [" + ",".join(map(str, self.parent_bom_ids.ids)) + "])]" + ) + return res + + def action_open_product_other_bom_tree_view(self): + self.ensure_one() + if self.product_id: + product_bom_ids = self.env["mrp.bom"].search( + [("product_id", "=", self.product_id.id), ("id", "!=", self.id)], + ) + else: + product_bom_ids = self.env["mrp.bom"].search( + [ + ("product_tmpl_id", "=", self.product_tmpl_id.id), + ("id", "!=", self.id), + ], + ) + res = self.env["ir.actions.actions"]._for_xml_id("mrp.mrp_bom_form_action") + if self.product_id: + res["context"] = { + "default_product_id": self.product_id.id, + "default_product_tmpl_id": self.product_id.product_tmpl_id.id, + } + elif self.product_tmpl_id: + res["context"] = { + "default_product_tmpl_id": self.product_tmpl_id.id, + } + res["domain"] = ( + "[('id', 'in', [" + ",".join(map(str, product_bom_ids.ids)) + "])]" + ) + return res + + +class MrpBomLine(models.Model): + _inherit = "mrp.bom.line" + + has_bom = fields.Boolean( + string="Has sub BoM", + compute="_compute_child_bom_id", + ) + + @api.depends("product_id", "bom_id") + def _compute_child_bom_id(self): + res = super()._compute_child_bom_id() + for line in self: + line.has_bom = bool(line.child_bom_id) + return res + + def action_open_product_bom_tree_view(self): + self.ensure_one() + res = self.env["ir.actions.actions"]._for_xml_id("mrp.mrp_bom_form_action") + res["domain"] = ( + "[('id', 'in', [" + ",".join(map(str, self.child_bom_id.ids)) + "])]" + ) + return res + + @api.model + def _bom_line_find_domain( + self, + product_tmpl=None, + product=None, + picking_type=None, + company_id=False, + bom_type=False, + ): + if product: + if not product_tmpl: + product_tmpl = product.product_tmpl_id + domain = [ + "|", + ("product_id", "=", product.id), + "&", + ("product_id", "=", False), + ("product_tmpl_id", "=", product_tmpl.id), + ] + elif product_tmpl: + domain = [("product_tmpl_id", "=", product_tmpl.id)] + else: + # neither product nor template, makes no sense to search + raise UserError( + _( + "You should provide either a product or " + "a product template to search a BoM Line" + ) + ) + if picking_type: + domain += [ + "|", + ("bom_id.picking_type_id", "=", picking_type.id), + ("bom_id.picking_type_id", "=", False), + ] + if company_id or self.env.context.get("company_id"): + domain = domain + [ + "|", + ("company_id", "=", False), + ("company_id", "=", company_id or self.env.context.get("company_id")), + ] + if bom_type: + domain += [("bom_id.type", "=", bom_type)] + # order to prioritize bom line with product_id over the one without + return domain + + @api.model + def _bom_line_find( + self, + product_tmpl=None, + product=None, + picking_type=None, + company_id=False, + bom_type=False, + ): + """Finds BoM lines for particular product, picking and company""" + if ( + product + and product.type == "service" + or product_tmpl + and product_tmpl.type == "service" + ): + return self.env["mrp.bom.line"] + domain = self._bom_line_find_domain( + product_tmpl=product_tmpl, + product=product, + picking_type=picking_type, + company_id=company_id, + bom_type=bom_type, + ) + if domain is False: + return self.env["mrp.bom.line"] + return self.search(domain, order="sequence, product_id") diff --git a/mrp_bom_hierarchy/readme/CONTRIBUTORS.rst b/mrp_bom_hierarchy/readme/CONTRIBUTORS.rst new file mode 100644 index 000000000..67d9a1a59 --- /dev/null +++ b/mrp_bom_hierarchy/readme/CONTRIBUTORS.rst @@ -0,0 +1,2 @@ +* Jordi Ballester Alomar +* Miquel Raïch Regué diff --git a/mrp_bom_hierarchy/readme/DESCRIPTION.rst b/mrp_bom_hierarchy/readme/DESCRIPTION.rst new file mode 100644 index 000000000..966b69c16 --- /dev/null +++ b/mrp_bom_hierarchy/readme/DESCRIPTION.rst @@ -0,0 +1,5 @@ +This module extends the functionality of Bill of Materials +to support users to better maintain the BoM hierarchy. + +The user can navigate from the tree view to child's BoM or parent's BoM, +or to the product's BoM components with a single click. diff --git a/mrp_bom_hierarchy/readme/USAGE.rst b/mrp_bom_hierarchy/readme/USAGE.rst new file mode 100644 index 000000000..2525293d9 --- /dev/null +++ b/mrp_bom_hierarchy/readme/USAGE.rst @@ -0,0 +1,2 @@ +To use this module, you need to go to 'Manufacturing | Products | Bill of +Materials'. diff --git a/mrp_bom_hierarchy/static/description/icon.png b/mrp_bom_hierarchy/static/description/icon.png new file mode 100644 index 000000000..3a0328b51 Binary files /dev/null and b/mrp_bom_hierarchy/static/description/icon.png differ diff --git a/mrp_bom_hierarchy/static/description/index.html b/mrp_bom_hierarchy/static/description/index.html new file mode 100644 index 000000000..c21defba0 --- /dev/null +++ b/mrp_bom_hierarchy/static/description/index.html @@ -0,0 +1,92 @@ +
+
+
+

MRP BoM Hierarchy

+

+This module was written to extend the functionality of Bill of +Materials to support users to better maintain the BoM hierarchy. + +This module replaces the existing BoM tree views with a new one, from +which the user can create a complete BoM hierarchy. + +The user can navigate from the tree view to child BoM's, or to the +product's BoM components with a single click. + +The user can now search using the field 'Complete Reference' (or Name) to +find all the BoM hierarchy associated to a particular BoM Reference (or +Name) at once. +

+
+
+
+ +
+
+
+

Installation

+
+
+

No specific installation steps are required.

+
+
+
+ +
+
+
+

Configuration

+
+
+

No specific configuration steps are required.

+
+
+
+ +
+
+
+

Usage

+
+
+

To use this module, you need to go to 'Manufacturing | Products | Bill of +Materials Hierarchy' +

+
+
+
+ +
+
+
+

Known issues / Roadmap

+
+
+

No issues have been identified with this module. +

+
+
+
+ +
+
+
+

Credits

+
+
+

Contributors

+ +
+
+

Maintainer

+

+ This module is maintained by the OCA.
+ 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.
+ To contribute to this module, please visit http://odoo-community.org.
+ +

+
+
+
diff --git a/mrp_bom_hierarchy/view/mrp.xml b/mrp_bom_hierarchy/view/mrp.xml new file mode 100644 index 000000000..fff266ab5 --- /dev/null +++ b/mrp_bom_hierarchy/view/mrp.xml @@ -0,0 +1,88 @@ + + + + + mrp.bom.form + mrp.bom + + + + +