diff --git a/mrp_hook/README.rst b/mrp_hook/README.rst new file mode 100644 index 000000000..20ead183a --- /dev/null +++ b/mrp_hook/README.rst @@ -0,0 +1,29 @@ +=============================== +Hooks for enabling advanced MRP +=============================== + +Technical module that provides the proper framework infrastructure (hooks, +fallback, etc) to enable advanced functionality in the manufacturing area, +as https://github.com/odoo/odoo/pull/8885 hasn't been accepted for v8. + +* Hooks in *_bom_explode* method for returning dictionary for consume and + workcenter lines. +* Provide product and template. + +By itself it doesn't provide anything, but serves as base for others modules +to develop its features. + +Known issues / Roadmap +====================== + +* This module fully overwrites _bom_explode method, so any other module + inheriting this method should take that into account. +* On v9, this module can be removed. + +Credits +======= + +Contributors +------------ + +* Pedro M. Baeza diff --git a/mrp_hook/__init__.py b/mrp_hook/__init__.py new file mode 100644 index 000000000..721dbe492 --- /dev/null +++ b/mrp_hook/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# (c) 2015 Serv. Tecnol. Avanzados - Pedro M. Baeza +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import models diff --git a/mrp_hook/__openerp__.py b/mrp_hook/__openerp__.py new file mode 100644 index 000000000..7c573e9ae --- /dev/null +++ b/mrp_hook/__openerp__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# (c) 2015 Serv. Tecnol. Avanzados - Pedro M. Baeza +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +{ + "name": "MRP Hooks", + "version": "8.0.1.0.0", + "category": "Hidden", + "author": "OdooMRP team, " + "AvanzOSC, " + "Serv. Tecnol. Avanzados - Pedro M. Baeza", + "website": "http://www.odoomrp.com", + "contributors": [ + "Pedro M. Baeza ", + ], + "depends": [ + "mrp", + ], + "data": [ + ], + "installable": True +} diff --git a/mrp_hook/models/__init__.py b/mrp_hook/models/__init__.py new file mode 100644 index 000000000..29373fdb1 --- /dev/null +++ b/mrp_hook/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +# (c) 2015 Serv. Tecnol. Avanzados - Pedro M. Baeza +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from . import mrp_bom diff --git a/mrp_hook/models/mrp_bom.py b/mrp_hook/models/mrp_bom.py new file mode 100644 index 000000000..8035d77c9 --- /dev/null +++ b/mrp_hook/models/mrp_bom.py @@ -0,0 +1,150 @@ +# -*- coding: utf-8 -*- +# (c) 2015 Serv. Tecnol. Avanzados - Pedro M. Baeza +# License AGPL-3 - See http://www.gnu.org/licenses/agpl-3.0.html + +from openerp import models, api, tools, _ +from openerp.exceptions import Warning as UserError +from openerp.addons.product import _common + + +class MrpBom(models.Model): + _inherit = 'mrp.bom' + + @api.model + def _factor(self, factor, product_efficiency, product_rounding): + factor = factor / (product_efficiency or 1.0) + factor = _common.ceiling(factor, product_rounding) + if factor < product_rounding: + factor = product_rounding + return factor + + @api.multi + def _prepare_wc_line(self, wc_use, level=0, factor=1): + self.ensure_one() + wc = wc_use.workcenter_id + d, m = divmod(factor, wc_use.workcenter_id.capacity_per_cycle) + mult = (d + (m and 1.0 or 0.0)) + cycle = mult * wc_use.cycle_nbr + return { + 'name': (tools.ustr(wc_use.name) + ' - ' + + tools.ustr(self.product_tmpl_id.name_get()[0][1])), + 'workcenter_id': wc.id, + 'sequence': level + (wc_use.sequence or 0), + 'cycle': cycle, + 'hour': float( + wc_use.hour_nbr * mult + + (wc.time_start or 0.0) + + (wc.time_stop or 0.0) + + cycle * (wc.time_cycle or 0.0) * (wc.time_efficiency or 1.0)), + } + + @api.model + def _prepare_consume_line(self, bom_line, quantity, factor=1): + uos_qty = (bom_line.product_uos and + self._factor( + bom_line.product_uos_qty * factor, + bom_line.product_efficiency, bom_line.product_rounding)) + return { + 'name': bom_line.product_id.name, + 'product_id': bom_line.product_id.id, + 'product_qty': quantity, + 'product_uom': bom_line.product_uom.id, + 'product_uos_qty': uos_qty or False, + 'product_uos': bom_line.product_uos.id, + } + + @api.model + def _bom_find_prepare(self, bom_line, properties=None): + return self._bom_find( + product_id=bom_line.product_id.id, properties=properties) + + @api.model + def _get_bom_product_name(self, bom_line): + return bom_line.product_id.name_get()[0][1] + + @api.v7 + def _bom_explode(self, cr, uid, bom, product, factor, properties=None, + level=0, routing_id=False, previous_products=None, + master_bom=None, context=None): + return bom._bom_explode( + product, factor, properties=properties, level=level, + routing_id=routing_id, previous_products=previous_products, + master_bom=master_bom) + + @api.v8 + def _bom_explode(self, product, factor, properties=None, level=0, + routing_id=False, previous_products=None, + master_bom=None): + """ Finds Products and Work Centers for related BoM for manufacturing + order. + @param bom: BoM of particular product template. + @param product: Select a particular variant of the BoM. If False use + BoM without variants. + @param factor: Factor represents the quantity, but in UoM of the BoM, + taking into account the numbers produced by the BoM + @param properties: A List of properties Ids. + @param level: Depth level to find BoM lines starts from 10. + @param previous_products: List of product previously use by bom + explore to avoid recursion + @param master_bom: When recursion, used to display the name of the + master bom + @return: result: List of dictionaries containing product details. + result2: List of dictionaries containing Work Center details. + """ + self.ensure_one() + bom = self + uom_obj = self.env["product.uom"] + routing_obj = self.env['mrp.routing'] + master_bom = master_bom or bom + factor = self._factor( + factor, bom.product_efficiency, bom.product_rounding) + result = [] + result2 = [] + routing = ((routing_id and routing_obj.browse(routing_id)) or + bom.routing_id or False) + if routing: + for wc_use in routing.workcenter_lines: + result2.append(self._prepare_wc_line( + wc_use, level=level, factor=factor)) + for bom_line_id in bom.bom_line_ids: + if self._skip_bom_line(bom_line_id, product): + continue + if (set(map(int, bom_line_id.property_ids or [])) - + set(properties or [])): + continue + product_tmpl_id = bom_line_id.product_id.product_tmpl_id.id + if (previous_products and + product_tmpl_id in previous_products): + raise UserError( + _('BoM "%s" contains a BoM line with a product recursion: ' + '"%s".') % (master_bom.name, + bom_line_id.product_id.name_get()[0][1])) + quantity = self._factor( + bom_line_id.product_qty * factor, + bom_line_id.product_efficiency, bom_line_id.product_rounding) + bom_id = self._bom_find_prepare(bom_line_id, properties=properties) + # If BoM should not behave like PhantoM, just add the product, + # otherwise explode further + if (bom_line_id.type != "phantom" and + (not bom_id or self.browse(bom_id).type != "phantom")): + result.append( + self._prepare_consume_line(bom_line_id, quantity, factor)) + elif bom_id: + all_prod = [bom.product_tmpl_id.id] + (previous_products or []) + bom2 = self.browse(bom_id) + # We need to convert to units/UoM of chosen BoM + factor2 = uom_obj._compute_qty( + bom_line_id.product_uom.id, quantity, bom2.product_uom.id) + quantity2 = factor2 / bom2.product_qty + res = bom2._bom_explode( + bom_line_id.product_id, quantity2, properties=properties, + level=level + 10, previous_products=all_prod, + master_bom=master_bom) + result = result + res[0] + result2 = result2 + res[1] + else: + raise UserError( + _('BoM "%s" contains a phantom BoM line but the product ' + '"%s" does not have any BoM defined.') % + (master_bom.name, self._get_bom_product_name(bom_line_id))) + return result, result2