summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSanjay Jamod <sja@odoo.com>2018-02-22 11:40:14 +0530
committerArnold Moyaux <arm@odoo.com>2018-04-09 15:39:19 +0200
commit87f6c16dcf532efd7a61eeb84b931e06be06370d (patch)
tree319c5e07bd41147526ea856e3d324674725997c7
parent8f268469d52b34e4d09333eec56f2afbfd7f7ec6 (diff)
[IMP] mrp_bom_cost: Compute price from BoM
Purpose ======= Compute cost of product(s) based on it's BoM Specification ============= real_time: not with FIFO otherwise open the wizard with the bom price as default new value periodic: directly set the cost on the form view Add a server action in order to directly set the price on selected product. When getting price from a bom with a sub-bom it will take the cost of the sub bom except if the sub-bom is set with the bom itself in the server action (in this case the sub bom cost will be used). Capacity is not used for this functionality task-39561
-rw-r--r--addons/mrp/models/mrp_production.py5
-rw-r--r--addons/mrp/wizard/change_production_qty.py6
-rw-r--r--addons/mrp_bom_cost/__manifest__.py9
-rw-r--r--addons/mrp_bom_cost/models/product.py91
-rw-r--r--addons/mrp_bom_cost/views/product_views.xml68
5 files changed, 110 insertions, 69 deletions
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')
+ 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 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:
- 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 _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
+ 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 @@
<field name="inherit_id" ref="product.product_template_only_form_view" />
<field name="groups_id" eval="[(4, ref('mrp.group_mrp_user'))]"/>
<field name="arch" type="xml">
- <div name="standard_price_uom" position="after">
- <button name="compute_price"
- string="Compute from BOM" type="object"
- attrs="{'invisible': ['|', '|', ('cost_method', '!=', 'standard'), ('bom_count', '=', 0), ('product_variant_count', '!=', 1)]}"
+ <xpath expr="//div[@name='update_cost_price']" position="inside">
+ <button name="button_bom_cost"
+ string="Compute Price from BoM" type="object"
+ attrs="{'invisible': ['|', ('bom_count', '=', 0), '&amp;', ('valuation', '=', 'real_time'), ('cost_method', '=', 'fifo')]}"
help="Compute the price of the product using products and operations of related bill of materials, for manufactured products only."
- class="oe_link oe_read_only"
- colspan="2"/>
- </div>
+ class="oe_link oe_read_only"/>
+ </xpath>
</field>
</record>
@@ -25,34 +24,55 @@
<field name="inherit_id" ref="product.product_normal_form_view"/>
<field name="groups_id" eval="[(4, ref('mrp.group_mrp_user'))]"/>
<field name="arch" type="xml">
- <div name="standard_price_uom" position="after">
- <button name="compute_price"
- string="Compute from BOM" type="object"
- attrs="{'invisible': ['|', ('cost_method', '!=', 'standard'), ('bom_count', '=', 0)]}"
+ <xpath expr="//div[@name='update_cost_price']" position="inside">
+ <button name="button_bom_cost"
+ string="Compute Price from BoM" type="object"
+ attrs="{'invisible': ['|', ('bom_count', '=', 0), '&amp;', ('valuation', '=', 'real_time'), ('cost_method', '=', 'fifo')]}"
help="Compute the price of the product using products and operations of related bill of materials, for manufactured products only."
class="oe_link oe_read_only"
colspan="2"/>
- </div>
+ </xpath>
</field>
</record>
- <record id="product_product_view_form_easy_inherit_extended" model="ir.ui.view">
- <field name="name">product.product.view.form.easy.inherit.extended</field>
+ <record id="product_variant_easy_edit_view_bom_inherit" model="ir.ui.view">
+ <field name="name">product.product.product.view.form.easy.bom.inherit</field>
<field name="model">product.product</field>
<field name="inherit_id" ref="product.product_variant_easy_edit_view"/>
- <field name="groups_id" eval="[(4, ref('mrp.group_mrp_user'))]"/>
<field name="arch" type="xml">
- <field name="standard_price" position="after">
- <field name="cost_method" invisible='1'/>
- <field name="bom_count" invisible='1'/>
- <button name="compute_price"
- string="Compute from BOM" type="object"
- attrs="{'invisible': ['|', ('cost_method', '!=', 'standard'), ('bom_count', '=', 0)]}"
+ <data>
+ <xpath expr="//div[@name='update_cost_price']" position="inside">
+ <field name="bom_count" invisible="1"/>
+ <button name="button_bom_cost"
+ string="Compute Price from BoM"
+ type="object"
+ attrs="{'invisible': ['|', ('bom_count', '=', 0), '&amp;', ('valuation', '=', 'real_time'), ('cost_method', '=', 'fifo')]}"
help="Compute the price of the product using products and operations of related bill of materials, for manufactured products only."
- class="oe_link oe_read_only"
- colspan="2"/>
- </field>
+ class="oe_link oe_read_only"/>
+ </xpath>
+ </data>
</field>
</record>
+ <record id="action_compute_price_bom_template" model="ir.actions.server">
+ <field name="name">Compute Price from BoM</field>
+ <field name="model_id" ref="product.model_product_template"/>
+ <field name="binding_model_id" ref="product.model_product_template"/>
+ <field name="state">code</field>
+ <field name="code">
+ if records:
+ records.action_bom_cost()
+ </field>
+ </record>
+
+ <record id="action_compute_price_bom_product" model="ir.actions.server">
+ <field name="name">Compute Price from BoM</field>
+ <field name="model_id" ref="product.model_product_product"/>
+ <field name="binding_model_id" ref="product.model_product_product"/>
+ <field name="state">code</field>
+ <field name="code">
+ if records:
+ records.action_bom_cost()
+ </field>
+ </record>
</odoo>