Skip to content

Commit 68a08c3

Browse files
author
Cyril Gaudin
committed
New module: mrp_bom_dismantling
1 parent e6fc68e commit 68a08c3

14 files changed

+667
-1
lines changed

mrp_bom_dismantling/README.rst

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
.. image:: https://img.shields.io/badge/licence-AGPL--3-blue.svg
2+
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
3+
:alt: License: AGPL-3
4+
5+
===============
6+
BOM Dismantling
7+
===============
8+
9+
This module adds the ability to create a dismantling BOM by reversing a BOM.
10+
11+
12+
Usage
13+
=====
14+
15+
16+
* On BOM form view, click on "Create dismantling BOM" button and it will reverse your BOM.
17+
* In Manufacturing -> Products, there is a new menu "Dismantling".
18+
* In dismantling tree view, you can search by dismantled product.
19+
* On BOM form view, there is a new button "Create Manufacturing Order".
20+
21+
.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas
22+
:alt: Try me on Runbot
23+
:target: https://runbot.odoo-community.org/runbot/129/9.0
24+
25+
26+
Bug Tracker
27+
===========
28+
29+
Bugs are tracked on `GitHub Issues
30+
<https://github.com/OCA/manufacture/issues>`_. In case of trouble, please
31+
check there if your issue has already been reported. If you spotted it first,
32+
help us smashing it by providing a detailed and welcomed `feedback
33+
<https://github.com/OCA/
34+
manufacture/issues/new?body=module:%20
35+
mrp_bom_dismantling%0Aversion:%20
36+
9.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.
37+
38+
Credits
39+
=======
40+
41+
Contributors
42+
------------
43+
44+
* Camptocamp - Cyril Gaudin <[email protected]>
45+
46+
Maintainer
47+
----------
48+
49+
.. image:: https://odoo-community.org/logo.png
50+
:alt: Odoo Community Association
51+
:target: https://odoo-community.org
52+
53+
This module is maintained by the OCA.
54+
55+
OCA, or the Odoo Community Association, is a nonprofit organization whose
56+
mission is to support the collaborative development of Odoo features and
57+
promote its widespread use.
58+
59+
To contribute to this module, please visit https://odoo-community.org.

mrp_bom_dismantling/__init__.py

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# -*- coding: utf-8 -*-
2+
# © 2016 Cyril Gaudin (Camptocamp)
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
from . import models

mrp_bom_dismantling/__openerp__.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# -*- coding: utf-8 -*-
2+
# © 2016 Cyril Gaudin (Camptocamp)
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
{
5+
"name": "BOM Dismantling",
6+
"summary": "Ability to create a dismantling BOM by reversing a BOM.",
7+
"version": "9.0.1.0.0",
8+
"category": "Manufacturing",
9+
"website": "http://www.camptocamp.com/",
10+
"author": "Camptocamp, Odoo Community Association (OCA)",
11+
"license": "AGPL-3",
12+
"application": False,
13+
"installable": True,
14+
"depends": [
15+
'mrp_byproduct',
16+
"stock_available_mrp",
17+
],
18+
"data": [
19+
"views/mrp_bom.xml",
20+
"views/product_template.xml",
21+
],
22+
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# -*- coding: utf-8 -*-
2+
# © 2016 Cyril Gaudin (Camptocamp)
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
from . import mrp_bom
6+
from . import product_product
7+
from . import product_template

mrp_bom_dismantling/models/mrp_bom.py

+139
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# -*- coding: utf-8 -*-
2+
# © 2016 Cyril Gaudin (Camptocamp)
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
from openerp import _, api, exceptions, fields, models
6+
7+
8+
class MrpBom(models.Model):
9+
_inherit = 'mrp.bom'
10+
11+
dismantling = fields.Boolean(string='Dismantling', default=False)
12+
dismantled_product_id = fields.Many2one(
13+
comodel_name='product.product',
14+
string='Dismantled product'
15+
)
16+
17+
_sql_constraints = [
18+
('bom_dismantled_product_id',
19+
'CHECK(dismantled_product_id is not null = dismantling)',
20+
"Dismantling BoM should have a dismantled product."),
21+
]
22+
23+
@api.multi
24+
def create_mrp_production(self):
25+
""" Create a manufacturing order from this BoM
26+
"""
27+
self.ensure_one()
28+
29+
product = self._get_bom_product()
30+
31+
production = self.env['mrp.production'].create({
32+
'bom_id': self.id,
33+
'product_id': product.id,
34+
'product_qty': self.product_qty,
35+
'product_uom': self.product_uom.id,
36+
})
37+
38+
return self._get_form_view('mrp.production', production)
39+
40+
@api.multi
41+
def create_dismantling_bom(self):
42+
""" Create a dismantling BoM based on this BoM
43+
"""
44+
self.ensure_one()
45+
46+
self._check_bom_validity(check_dismantling=True)
47+
48+
product = self._get_bom_product()
49+
components = self._get_components_tuples()
50+
51+
# Create the BoM on first component (sorted by Id)
52+
first_component, first_component_needs = components.pop(0)
53+
dismantling_bom = self.create({
54+
'product_tmpl_id': first_component.product_tmpl_id.id,
55+
'product_id': first_component.id,
56+
'dismantling': True,
57+
'dismantled_product_id': product.id,
58+
'product_qty': first_component_needs,
59+
})
60+
61+
# Create BoM line for self.product_tmpl_id
62+
self.env['mrp.bom.line'].create({
63+
'bom_id': dismantling_bom.id,
64+
'product_id': product.id,
65+
'product_qty': self.product_qty,
66+
'product_uom': self.product_uom.id,
67+
})
68+
69+
# Add others component as By-products
70+
subproduct_model = self.env['mrp.subproduct']
71+
for component, needs in components:
72+
subproduct_model.create({
73+
'bom_id': dismantling_bom.id,
74+
'product_id': component.id,
75+
'product_qty': needs,
76+
'product_uom': self.env.ref('product.product_uom_unit').id,
77+
})
78+
79+
return self._get_form_view('mrp.bom', dismantling_bom)
80+
81+
def _get_form_view(self, model_name, entity):
82+
return {
83+
'type': 'ir.actions.act_window',
84+
'view_type': 'form',
85+
'view_mode': 'form',
86+
'res_model': model_name,
87+
'target': 'current',
88+
'res_id': entity.id,
89+
'context': self.env.context
90+
}
91+
92+
def _check_bom_validity(self, check_dismantling=False):
93+
""" Ensure this BoM can be use for creating a dismantling BoM
94+
or a manufacturing order.
95+
96+
:type check_dismantling: bool
97+
:raise exceptions.UserError: If this BoM is not valid.
98+
"""
99+
warning = None
100+
if check_dismantling and self.dismantling:
101+
warning = 'This BoM is already a dismantling Bom.'
102+
103+
if not len(self.bom_line_ids):
104+
warning = 'This BoM does not have components.'
105+
106+
if not self.product_id \
107+
and len(self.product_tmpl_id.product_variant_ids) > 1:
108+
warning = 'This product has several variants: ' \
109+
'you need to specify one.'
110+
111+
if warning:
112+
raise exceptions.UserError(_(warning))
113+
114+
def _get_components_tuples(self):
115+
""" Return this BoM components and their needed qties
116+
sorted by component id.
117+
118+
The result is like [(component_1, 1), (component_2, 5), ...]
119+
120+
:rtype: list of tuple
121+
"""
122+
components = self.product_id._get_components_needs(
123+
product=self.product_id, bom=self
124+
)
125+
components = sorted(components.items(), key=lambda t: t[0].id)
126+
return components
127+
128+
def _get_bom_product(self):
129+
""" Get the product of this BoM.
130+
131+
If BoM does not have product_id, return first template variant.
132+
133+
:rtype: product_product
134+
"""
135+
if not self.product_id:
136+
product = self.product_tmpl_id.product_variant_ids[0]
137+
else:
138+
product = self.product_id
139+
return product
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# -*- coding: utf-8 -*-
2+
# © 2016 Cyril Gaudin (Camptocamp)
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
from openerp import models
6+
7+
8+
class ProductProduct(models.Model):
9+
_inherit = 'product.product'
10+
11+
def action_view_bom(self, cr, uid, ids, context=None):
12+
""" Override parent method to add a domain which filter out
13+
dismantling BoM
14+
"""
15+
result = super(ProductProduct, self).action_view_bom(
16+
cr, uid, ids, context
17+
)
18+
result['domain'] = [('dismantling', '=', False)]
19+
return result
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# -*- coding: utf-8 -*-
2+
# © 2016 Cyril Gaudin (Camptocamp)
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
from openerp import api, fields, models
6+
7+
8+
class ProductTemplate(models.Model):
9+
_inherit = 'product.template'
10+
11+
bom_count = fields.Integer(compute='_bom_count',
12+
string='# Bill of Material')
13+
14+
@api.multi
15+
def _bom_count(self):
16+
""" Override parent method to filter out dismantling bom.
17+
"""
18+
for template in self:
19+
template.bom_count = self.env['mrp.bom'].search_count([
20+
('product_tmpl_id', '=', template.id),
21+
('dismantling', '=', False),
22+
])

mrp_bom_dismantling/tests/__init__.py

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# -*- coding: utf-8 -*-
2+
# © 2016 Cyril Gaudin (Camptocamp)
3+
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl).
4+
5+
6+
from . import test_bom
7+
from . import test_product
8+
from . import test_template

0 commit comments

Comments
 (0)