Merge PR #809 into 14.0

Signed-off-by LoisRForgeFlow
This commit is contained in:
OCA-git-bot
2022-06-21 13:30:47 +00:00
13 changed files with 479 additions and 0 deletions

View File

View File

@@ -0,0 +1 @@
from . import model

View File

@@ -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": "14.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,
}

View File

@@ -0,0 +1 @@
from . import mrp_bom

View File

@@ -0,0 +1,263 @@
# 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):
super()._compute_child_bom_id()
for line in self:
line.has_bom = bool(line.child_bom_id)
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")

View File

@@ -0,0 +1,2 @@
* Jordi Ballester Alomar <jordi.ballester@forgeflow.com>
* Miquel Raïch Regué <miquel.raich@forgeflow.com>

View File

@@ -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.

View File

@@ -0,0 +1,2 @@
To use this module, you need to go to 'Manufacturing | Products | Bill of
Materials'.

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@@ -0,0 +1,92 @@
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">MRP BoM Hierarchy</h2>
<p>
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.
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Installation</h2>
</div>
<div class="oe_span6">
<p class="oe_mt32">No specific installation steps are required.</p>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Configuration</h2>
</div>
<div class="oe_span6">
<p class="oe_mt32">No specific configuration steps are required.</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Usage</h2>
</div>
<div class="oe_span6">
<p class="oe_mt32">To use this module, you need to go to 'Manufacturing | Products | Bill of
Materials Hierarchy'
</p>
</div>
</div>
</section>
<section class="oe_container">
<div class="oe_row oe_spaced">
<div class="oe_span12">
<h2 class="oe_slogan">Known issues / Roadmap</h2>
</div>
<div class="oe_span6">
<p class="oe_mt32">No issues have been identified with this module.
</p>
</div>
</div>
</section>
<section class="oe_container oe_dark">
<div class="oe_row">
<div class="oe_span12">
<h2 class="oe_slogan">Credits</h2>
</div>
<div class="oe_span12">
<h3>Contributors</h3>
<ul>
<li>Jordi Ballester Alomar &lt;<a
href="mailto:jordi.ballester@forgeflow.com">jordi.ballester@forgeflow.com</a>&gt;</li>
</ul>
</div>
<div class="oe_span12">
<h3>Maintainer</h3>
<p>
This module is maintained by the OCA.<br/>
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.<br/>
To contribute to this module, please visit <a href="http://odoo-community.org">http://odoo-community.org</a>.<br/>
<a href="http://odoo-community.org"><img class="oe_picture oe_centered" src="http://odoo-community.org/logo.png"></a>
</p>
</div>
</div>
</section>

View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="mrp_bom_form_view" model="ir.ui.view">
<field name="name">mrp.bom.form</field>
<field name="model">mrp.bom</field>
<field name="inherit_id" ref="mrp.mrp_bom_form_view" />
<field name="arch" type="xml">
<xpath
expr="//notebook/page/field[@name='bom_line_ids']/tree/field[@name='product_tmpl_id']"
position="after"
>
<field name="has_bom" invisible="1" />
<button
title="View product's BoM"
name="action_open_product_bom_tree_view"
type="object"
class="btn btn-link"
icon="fa-folder-open"
attrs="{'invisible': [('has_bom', '!=', True)]}"
/>
</xpath>
</field>
</record>
<record id="view_mrp_bom_filter" model="ir.ui.view">
<field name="name">mrp.bom.select</field>
<field name="model">mrp.bom</field>
<field name="inherit_id" ref="mrp.view_mrp_bom_filter" />
<field name="arch" type="xml">
<xpath expr="//separator[1]" position="before">
<field name="has_parent" />
<field name="has_child" />
<separator />
<filter
string="Has parent BoMs"
name="parent_bom"
domain="[('has_parent','=','True')]"
/>
<filter
string="Has child BoMs"
name="child_bom"
domain="[('has_child','=','True')]"
/>
</xpath>
</field>
</record>
<record id="mrp_bom_hierarchy_tree_view" model="ir.ui.view">
<field name="name">mrp.bom.tree</field>
<field name="model">mrp.bom</field>
<field name="inherit_id" ref="mrp.mrp_bom_tree_view" />
<field name="arch" type="xml">
<field name="active" position="before">
<button
title="Parent BoMs"
name="action_open_parent_tree_view"
type="object"
class="btn btn-link"
icon="fa-level-up"
attrs="{'invisible': [('has_parent', '!=', True)]}"
/>
<button
title="Child BoMs"
name="action_open_child_tree_view"
type="object"
class="btn btn-link"
icon="fa-level-down"
attrs="{'invisible': [('has_child', '!=', True)]}"
/>
<field name="has_parent" invisible="1" />
<field name="has_child" invisible="1" />
</field>
<field name="type" position="before">
<field name="product_has_other_bom" invisible="1" />
<button
title="View product's other BoMs"
name="action_open_product_other_bom_tree_view"
type="object"
class="btn btn-link"
icon="fa-folder-open"
attrs="{'invisible': [('product_has_other_bom', '!=', True)]}"
/>
</field>
</field>
</record>
</odoo>

View File

@@ -0,0 +1 @@
../../../../mrp_bom_hierarchy

View File

@@ -0,0 +1,6 @@
import setuptools
setuptools.setup(
setup_requires=['setuptools-odoo'],
odoo_addon=True,
)