diff options
| author | Sanjay Jamod <sja@odoo.com> | 2018-02-22 11:40:14 +0530 |
|---|---|---|
| committer | Arnold Moyaux <arm@odoo.com> | 2018-04-09 15:39:19 +0200 |
| commit | 87f6c16dcf532efd7a61eeb84b931e06be06370d (patch) | |
| tree | 319c5e07bd41147526ea856e3d324674725997c7 | |
| parent | 8f268469d52b34e4d09333eec56f2afbfd7f7ec6 (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.py | 5 | ||||
| -rw-r--r-- | addons/mrp/wizard/change_production_qty.py | 6 | ||||
| -rw-r--r-- | addons/mrp_bom_cost/__manifest__.py | 9 | ||||
| -rw-r--r-- | addons/mrp_bom_cost/models/product.py | 91 | ||||
| -rw-r--r-- | addons/mrp_bom_cost/views/product_views.xml | 68 |
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), '&', ('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), '&', ('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), '&', ('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> |
