diff --git a/addons/mrp/models/mrp_production.py b/addons/mrp/models/mrp_production.py
index 0f400253a18..337a7798ad0 100644
--- a/addons/mrp/models/mrp_production.py
+++ b/addons/mrp/models/mrp_production.py
@@ -2,12 +2,11 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
-import math
from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError
-from odoo.tools import float_compare
+from odoo.tools import float_compare, float_round
class MrpProduction(models.Model):
""" Manufacturing Orders """
@@ -497,7 +496,7 @@ class MrpProduction(models.Model):
for operation in bom.routing_id.operation_ids:
# create workorder
- cycle_number = math.ceil(bom_qty / operation.workcenter_id.capacity) # TODO: float_round UP
+ cycle_number = float_round(bom_qty / operation.workcenter_id.capacity, precision_digits=0, rounding_method='UP')
duration_expected = (operation.workcenter_id.time_start +
operation.workcenter_id.time_stop +
cycle_number * operation.time_cycle * 100.0 / operation.workcenter_id.time_efficiency)
diff --git a/addons/mrp/wizard/change_production_qty.py b/addons/mrp/wizard/change_production_qty.py
index 90db7f09481..5c3aeb07dd2 100644
--- a/addons/mrp/wizard/change_production_qty.py
+++ b/addons/mrp/wizard/change_production_qty.py
@@ -4,8 +4,8 @@
from odoo import api, fields, models, _
from odoo.addons import decimal_precision as dp
from odoo.exceptions import UserError
-from odoo.tools import float_is_zero
-import math
+from odoo.tools import float_is_zero, float_round
+
class ChangeProductionQty(models.TransientModel):
_name = 'change.production.qty'
@@ -61,7 +61,7 @@ class ChangeProductionQty(models.TransientModel):
for wo in production.workorder_ids:
operation = wo.operation_id
if operation_bom_qty.get(operation.id):
- cycle_number = math.ceil(operation_bom_qty[operation.id] / operation.workcenter_id.capacity) # TODO: float_round UP
+ cycle_number = float_round(operation_bom_qty[operation.id] / operation.workcenter_id.capacity, precision_digits=0, rounding_method='UP')
wo.duration_expected = (operation.workcenter_id.time_start +
operation.workcenter_id.time_stop +
cycle_number * operation.time_cycle * 100.0 / operation.workcenter_id.time_efficiency)
diff --git a/addons/mrp_bom_cost/__manifest__.py b/addons/mrp_bom_cost/__manifest__.py
index cfddc064353..e1094e5fd33 100644
--- a/addons/mrp_bom_cost/__manifest__.py
+++ b/addons/mrp_bom_cost/__manifest__.py
@@ -2,14 +2,15 @@
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
- "name" : "Product extension to track sales and purchases",
+ "name" : "Product Extension: Compute price from BoM",
"version" : "1.0",
"depends" : ["mrp", "stock_account"],
"category" : "Manufacturing",
"description": """
-Product extension. This module adds:
- * Computes standard price from the BoM of the product with a button on the product variant based
- on the materials in the BoM and the work centers. It can create the necessary accounting entries when necessary.
+Allows to compute the standard price of the product based on its BoM,
+using the prices of its components and the cost linked to the work centers.
+It adds a button on the product itself but also an action in the list view of the products.
+If the automated inventory valuation is active, the required accounting entries will be created.
""",
"init_xml" : [],
"demo_xml" : [],
diff --git a/addons/mrp_bom_cost/models/product.py b/addons/mrp_bom_cost/models/product.py
index 19ef8e08492..c1a276918ae 100644
--- a/addons/mrp_bom_cost/models/product.py
+++ b/addons/mrp_bom_cost/models/product.py
@@ -1,51 +1,72 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
-from odoo import api, models
+from odoo import api, models, _
+from odoo.exceptions import UserError
class ProductTemplate(models.Model):
_name = 'product.template'
_inherit = 'product.template'
- @api.multi
- def compute_price(self):
- for template in self:
- if template.product_variant_count == 1:
- return template.product_variant_id.compute_price()
+ def action_bom_cost(self):
+ templates = self.filtered(lambda t: t.product_variant_count == 1 and t.bom_count > 0)
+ return templates.mapped('product_variant_id').action_bom_cost()
+
+ def button_bom_cost(self):
+ templates = self.filtered(lambda t: t.product_variant_count == 1 and t.bom_count > 0)
+ return templates.mapped('product_variant_id').button_bom_cost()
class ProductProduct(models.Model):
_name = 'product.product'
_inherit = 'product.product'
- @api.multi
- def compute_price(self):
- bom_obj = self.env['mrp.bom']
- action_rec = self.env.ref('stock_account.action_view_change_standard_price')
- for product in self:
- bom = bom_obj._bom_find(product=product)
- if bom:
- price = product._calc_price(bom)
- if action_rec:
- action = action_rec.read([])[0]
- action['context'] = {'default_new_price': price}
- return action
- return True
+ def button_bom_cost(self):
+ self.ensure_one()
+ price = self._get_price_from_bom()
+ if self.valuation == 'real_time':
+ action_rec = self.env.ref('stock_account.action_view_change_standard_price')
+ action = action_rec.read([])[0]
+ action['context'] = {'default_new_price': price}
+ return action
+ else:
+ self.standard_price = price
- def _calc_price(self, bom):
- price = 0.0
- result, result2 = bom.explode(self, 1)
- for sbom, sbom_data in result2:
- price += sbom.product_id.uom_id._compute_price(sbom.product_id.standard_price, sbom.product_uom_id) * sbom_data['qty']
- if bom.routing_id:
- # FIXME master: remove me
- if hasattr(self.env['mrp.workcenter'], 'costs_hour'):
- total_cost = 0.0
- for order in bom.routing_id.operation_ids:
- total_cost += (order.time_cycle/60) * order.workcenter_id.costs_hour
- price += bom.product_uom_id._compute_price(total_cost, bom.product_id.uom_id)
- # Convert on product UoM quantities
- if price > 0:
- price = bom.product_uom_id._compute_price(price / bom.product_qty, self.uom_id)
- return price
+ def action_bom_cost(self):
+ real_time_products = self.filtered(lambda p: p.valuation == 'real_time')
+ if real_time_products:
+ raise UserError(_('The inventory valuation of some products %s is automated. You can only update their cost from the product form.') % (real_time_products.mapped('display_name')))
+
+ boms_to_recompute = self.env['mrp.bom'].search(['|', ('product_id', 'in', self.ids), '&', ('product_id', '=', False), ('product_tmpl_id', 'in', self.mapped('product_tmpl_id').ids)])
+ for product in self:
+ product.standard_price = product._get_price_from_bom(boms_to_recompute)
+
+ def _get_price_from_bom(self, boms_to_recompute=False):
+ self.ensure_one()
+ bom = self.env['mrp.bom']._bom_find(product=self)
+ return self._compute_bom_price(bom, boms_to_recompute=boms_to_recompute)
+
+ def _compute_bom_price(self, bom, boms_to_recompute=False):
+ self.ensure_one()
+ if not boms_to_recompute:
+ boms_to_recompute = []
+ total = 0
+ quant_quantity = bom.product_uom_id._compute_quantity(bom.product_qty, bom.product_tmpl_id.uom_id)
+ for opt in bom.routing_id.operation_ids:
+ duration_expected = (
+ opt.workcenter_id.time_start +
+ opt.workcenter_id.time_stop +
+ quant_quantity * opt.time_cycle)
+ total += (duration_expected / 60) * opt.workcenter_id.costs_hour
+ for line in bom.bom_line_ids:
+ if line._skip_bom_line(self):
+ continue
+
+ # Compute recursive if line has `child_line_ids`
+ if line.child_bom_id and line.child_bom_id in boms_to_recompute:
+ child_total = line.product_id._compute_bom_price(line.child_bom_id, boms_to_recompute=boms_to_recompute)
+ total += line.product_id.uom_id._compute_price(child_total, line.product_uom_id) * line.product_qty
+ else:
+ total += line.product_id.uom_id._compute_price(line.product_id.standard_price, line.product_uom_id) * line.product_qty
+ return bom.product_uom_id._compute_price(total / bom.product_qty, self.uom_id)
diff --git a/addons/mrp_bom_cost/views/product_views.xml b/addons/mrp_bom_cost/views/product_views.xml
index 2b7e4eca022..c51312ea255 100644
--- a/addons/mrp_bom_cost/views/product_views.xml
+++ b/addons/mrp_bom_cost/views/product_views.xml
@@ -7,14 +7,13 @@
-
-
+ class="oe_link oe_read_only"/>
+
@@ -25,34 +24,55 @@
-
-
+
-
+
-
- product.product.view.form.easy.inherit.extended
+
+ product.product.product.view.form.easy.bom.inherit
product.product
-
-
-
-
-
+
+
+
-
+ class="oe_link oe_read_only"/>
+
+
+
+ Compute Price from BoM
+
+
+ code
+
+ if records:
+ records.action_bom_cost()
+
+
+
+
+ Compute Price from BoM
+
+
+ code
+
+ if records:
+ records.action_bom_cost()
+
+