[IMP] mrp_subcontracting: improve mrp bom report
- The "BoM Structure & Cost" takes now in account the subcontracting cost. The subcontracting cost depends of the best supplier info in product and subcontractor register in the BoM. - Also add this subcontracting cost (to the cost field) when we click on "Compute Price from BoM" in the product view. task-2486811 Part-of: odoo/odoo#75041
This commit is contained in:
@@ -19,7 +19,7 @@ class ReportBomStructure(models.AbstractModel):
|
||||
quantity = float(data.get('quantity', 1))
|
||||
for product_variant_id in candidates.ids:
|
||||
if data and data.get('childs'):
|
||||
doc = self._get_pdf_line(bom_id, product_id=product_variant_id, qty=quantity, child_bom_ids=json.loads(data.get('childs')))
|
||||
doc = self._get_pdf_line(bom_id, product_id=product_variant_id, qty=quantity, child_bom_ids=set(json.loads(data.get('childs'))))
|
||||
else:
|
||||
doc = self._get_pdf_line(bom_id, product_id=product_variant_id, qty=quantity, unfolded=True)
|
||||
doc['report_type'] = 'pdf'
|
||||
@@ -27,7 +27,7 @@ class ReportBomStructure(models.AbstractModel):
|
||||
docs.append(doc)
|
||||
if not candidates:
|
||||
if data and data.get('childs'):
|
||||
doc = self._get_pdf_line(bom_id, qty=quantity, child_bom_ids=json.loads(data.get('childs')))
|
||||
doc = self._get_pdf_line(bom_id, qty=quantity, child_bom_ids=set(json.loads(data.get('childs'))))
|
||||
else:
|
||||
doc = self._get_pdf_line(bom_id, qty=quantity, unfolded=True)
|
||||
doc['report_type'] = 'pdf'
|
||||
@@ -260,48 +260,46 @@ class ReportBomStructure(models.AbstractModel):
|
||||
price += company.currency_id.round(not_rounded_price)
|
||||
return price
|
||||
|
||||
def _get_pdf_line(self, bom_id, product_id=False, qty=1, child_bom_ids=[], unfolded=False):
|
||||
|
||||
def get_sub_lines(bom, product_id, line_qty, line_id, level):
|
||||
data = self._get_bom(bom_id=bom.id, product_id=product_id, line_qty=line_qty, line_id=line_id, level=level)
|
||||
bom_lines = data['components']
|
||||
lines = []
|
||||
for bom_line in bom_lines:
|
||||
lines.append({
|
||||
'name': bom_line['prod_name'],
|
||||
'type': 'bom',
|
||||
'quantity': bom_line['prod_qty'],
|
||||
'uom': bom_line['prod_uom'],
|
||||
'prod_cost': bom_line['prod_cost'],
|
||||
'bom_cost': bom_line['total'],
|
||||
'level': bom_line['level'],
|
||||
'code': bom_line['code'],
|
||||
'child_bom': bom_line['child_bom'],
|
||||
'prod_id': bom_line['prod_id']
|
||||
})
|
||||
if bom_line['child_bom'] and (unfolded or bom_line['child_bom'] in child_bom_ids):
|
||||
line = self.env['mrp.bom.line'].browse(bom_line['line_id'])
|
||||
lines += (get_sub_lines(line.child_bom_id, line.product_id.id, bom_line['prod_qty'], line, level + 1))
|
||||
if data['operations']:
|
||||
lines.append({
|
||||
'name': _('Operations'),
|
||||
'type': 'operation',
|
||||
'quantity': data['operations_time'],
|
||||
'uom': _('minutes'),
|
||||
'bom_cost': data['operations_cost'],
|
||||
'level': level,
|
||||
})
|
||||
for operation in data['operations']:
|
||||
if unfolded or 'operation-' + str(bom.id) in child_bom_ids:
|
||||
lines.append({
|
||||
'name': operation['name'],
|
||||
'type': 'operation',
|
||||
'quantity': operation['duration_expected'],
|
||||
'uom': _('minutes'),
|
||||
'bom_cost': operation['total'],
|
||||
'level': level + 1,
|
||||
})
|
||||
if data['byproducts']:
|
||||
def _get_sub_lines(self, bom, product_id, line_qty, line_id, level, child_bom_ids, unfolded):
|
||||
data = self._get_bom(bom_id=bom.id, product_id=product_id, line_qty=line_qty, line_id=line_id, level=level)
|
||||
bom_lines = data['components']
|
||||
lines = []
|
||||
for bom_line in bom_lines:
|
||||
lines.append({
|
||||
'name': bom_line['prod_name'],
|
||||
'type': 'bom',
|
||||
'quantity': bom_line['prod_qty'],
|
||||
'uom': bom_line['prod_uom'],
|
||||
'prod_cost': bom_line['prod_cost'],
|
||||
'bom_cost': bom_line['total'],
|
||||
'level': bom_line['level'],
|
||||
'code': bom_line['code'],
|
||||
'child_bom': bom_line['child_bom'],
|
||||
'prod_id': bom_line['prod_id']
|
||||
})
|
||||
if bom_line['child_bom'] and (unfolded or bom_line['child_bom'] in child_bom_ids):
|
||||
line = self.env['mrp.bom.line'].browse(bom_line['line_id'])
|
||||
lines += (self._get_sub_lines(line.child_bom_id, line.product_id.id, bom_line['prod_qty'], line, level + 1, child_bom_ids, unfolded))
|
||||
if data['operations']:
|
||||
lines.append({
|
||||
'name': _('Operations'),
|
||||
'type': 'operation',
|
||||
'quantity': data['operations_time'],
|
||||
'uom': _('minutes'),
|
||||
'bom_cost': data['operations_cost'],
|
||||
'level': level,
|
||||
})
|
||||
for operation in data['operations']:
|
||||
if unfolded or 'operation-' + str(bom.id) in child_bom_ids:
|
||||
lines.append({
|
||||
'name': operation['name'],
|
||||
'type': 'operation',
|
||||
'quantity': operation['duration_expected'],
|
||||
'uom': _('minutes'),
|
||||
'bom_cost': operation['total'],
|
||||
'level': level + 1,
|
||||
})
|
||||
if data['byproducts']:
|
||||
lines.append({
|
||||
'name': _('Byproducts'),
|
||||
'type': 'byproduct',
|
||||
@@ -321,12 +319,16 @@ class ReportBomStructure(models.AbstractModel):
|
||||
'bom_cost': byproduct['bom_cost'],
|
||||
'level': level + 1,
|
||||
})
|
||||
return lines
|
||||
return lines
|
||||
|
||||
def _get_pdf_line(self, bom_id, product_id=False, qty=1, child_bom_ids=None, unfolded=False):
|
||||
if child_bom_ids is None:
|
||||
child_bom_ids = set()
|
||||
|
||||
bom = self.env['mrp.bom'].browse(bom_id)
|
||||
product_id = product_id or bom.product_id.id or bom.product_tmpl_id.product_variant_id.id
|
||||
data = self._get_bom(bom_id=bom_id, product_id=product_id, line_qty=qty)
|
||||
pdf_lines = get_sub_lines(bom, product_id, qty, False, 1)
|
||||
data = self._get_bom(bom_id=bom_id, product_id=product_id, line_qty=qty, )
|
||||
pdf_lines = self._get_sub_lines(bom, product_id, qty, False, 1, child_bom_ids, unfolded)
|
||||
data['components'] = []
|
||||
data['lines'] = pdf_lines
|
||||
data['extra_column_count'] = self._get_extra_column_count()
|
||||
|
||||
@@ -148,7 +148,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
</t>
|
||||
<t t-if="data['operations']">
|
||||
<t t-if="data['operations']" name="operations">
|
||||
<t t-set="space_td" t-value="'margin-left: '+ str(data['level'] * 20) + 'px;'"/>
|
||||
<tr class="o_mrp_bom_report_line o_mrp_bom_cost" t-att-data-product_id="data['product'].id" t-att-data-id="'operation-' + str(data['bom'].id)" t-att-data-bom-id="data['bom'].id" t-att-parent_id="data['bom'].id" t-att-data-qty="data['bom_qty']" t-att-data-level="data['level']">
|
||||
<td name="td_opr">
|
||||
|
||||
@@ -6,7 +6,7 @@ from odoo.tests import common, Form
|
||||
from odoo.tools.float_utils import float_round, float_compare
|
||||
|
||||
|
||||
class TestBom(common.TransactionCase):
|
||||
class TestBomPrice(common.TransactionCase):
|
||||
|
||||
def _create_product(self, name, price):
|
||||
return self.Product.create({
|
||||
@@ -16,11 +16,9 @@ class TestBom(common.TransactionCase):
|
||||
})
|
||||
|
||||
def setUp(self):
|
||||
super(TestBom, self).setUp()
|
||||
super(TestBomPrice, self).setUp()
|
||||
self.Product = self.env['product.product']
|
||||
self.Bom = self.env['mrp.bom']
|
||||
#self.Routing = self.env['mrp.routing']
|
||||
self.operation = self.env['mrp.routing.workcenter']
|
||||
|
||||
# Products.
|
||||
self.dining_table = self._create_product('Dining Table', 1000)
|
||||
|
||||
@@ -4,6 +4,7 @@ from odoo import SUPERUSER_ID, api
|
||||
|
||||
from . import models
|
||||
from . import wizard
|
||||
from . import report
|
||||
|
||||
|
||||
def uninstall_hook(cr, registry):
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
'views/product_views.xml',
|
||||
'views/mrp_production_views.xml',
|
||||
'wizard/stock_picking_return_views.xml',
|
||||
'report/mrp_report_bom_structure.xml',
|
||||
],
|
||||
'demo': [
|
||||
'data/mrp_subcontracting_demo.xml',
|
||||
|
||||
@@ -13,5 +13,14 @@ class SupplierInfo(models.Model):
|
||||
def _compute_is_subcontractor(self):
|
||||
for supplier in self:
|
||||
boms = supplier.product_id.variant_bom_ids
|
||||
boms |= supplier.product_tmpl_id.bom_ids.filtered(lambda b: not b.product_id)
|
||||
boms |= supplier.product_tmpl_id.bom_ids.filtered(lambda b: not b.product_id or b.product_id in (supplier.product_id or supplier.product_tmpl_id.product_variant_ids))
|
||||
supplier.is_subcontractor = supplier.name in boms.subcontractor_ids
|
||||
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
def _prepare_sellers(self, params=False):
|
||||
if params and params.get('subcontractor_ids'):
|
||||
return super()._prepare_sellers(params=params).filtered(lambda s: s.name in params.get('subcontractor_ids'))
|
||||
return super()._prepare_sellers(params=params)
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import mrp_report_bom_structure
|
||||
@@ -0,0 +1,63 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models, _
|
||||
|
||||
|
||||
class ReportBomStructure(models.AbstractModel):
|
||||
_inherit = 'report.mrp.report_bom_structure'
|
||||
|
||||
def _get_subcontracting_line(self, bom, seller, level, bom_quantity):
|
||||
ratio_uom_seller = seller.product_uom.ratio / bom.product_uom_id.ratio
|
||||
return {
|
||||
'name': seller.name.display_name,
|
||||
'partner_id': seller.name.id,
|
||||
'quantity': bom_quantity,
|
||||
'uom': bom.product_uom_id.name,
|
||||
'prod_cost': seller.price / ratio_uom_seller,
|
||||
'bom_cost': seller.price / ratio_uom_seller * bom_quantity,
|
||||
'level': level or 0
|
||||
}
|
||||
|
||||
def _get_price(self, bom, factor, product):
|
||||
price = super()._get_price(bom, factor, product)
|
||||
if bom and bom.type == 'subcontract':
|
||||
bom_quantity = bom.product_qty * factor
|
||||
seller = product._select_seller(quantity=bom_quantity, uom_id=bom.product_uom_id, params={'subcontractor_ids': bom.subcontractor_ids})
|
||||
if seller:
|
||||
price += seller.product_uom._compute_price(seller.price, product.uom_id)
|
||||
return price
|
||||
|
||||
def _get_bom(self, bom_id=False, product_id=False, line_qty=False, line_id=False, level=False):
|
||||
res = super(ReportBomStructure, self)._get_bom(bom_id, product_id, line_qty, line_id, level)
|
||||
bom = res['bom']
|
||||
if bom and bom.type == 'subcontract':
|
||||
bom_quantity = line_qty
|
||||
if line_id:
|
||||
current_line = self.env['mrp.bom.line'].browse(int(line_id))
|
||||
bom_quantity = current_line.product_uom_id._compute_quantity(line_qty, bom.product_uom_id)
|
||||
|
||||
seller = res['product']._select_seller(quantity=bom_quantity, uom_id=bom.product_uom_id, params={'subcontractor_ids': bom.subcontractor_ids})
|
||||
if seller:
|
||||
res['subcontracting'] = self._get_subcontracting_line(bom, seller, level, bom_quantity)
|
||||
res['total'] += res['subcontracting']['bom_cost']
|
||||
return res
|
||||
|
||||
def _get_sub_lines(self, bom, product_id, line_qty, line_id, level, child_bom_ids, unfolded):
|
||||
res = super()._get_sub_lines(bom, product_id, line_qty, line_id, level, child_bom_ids, unfolded)
|
||||
if bom and bom.type == 'subcontract':
|
||||
product = self.env['product.product'].browse(product_id)
|
||||
|
||||
bom_quantity = line_qty
|
||||
if line_id:
|
||||
current_line = self.env['mrp.bom.line'].browse(int(line_id))
|
||||
bom_quantity = current_line.product_uom_id._compute_quantity(line_qty, bom.product_uom_id)
|
||||
|
||||
seller = product._select_seller(quantity=bom_quantity, uom_id=bom.product_uom_id, params={'subcontractor_ids': bom.subcontractor_ids})
|
||||
if seller:
|
||||
values_sub = self._get_subcontracting_line(bom, seller, level, bom_quantity)
|
||||
values_sub['type'] = 'bom'
|
||||
values_sub['name'] = _("Subcontracting: ") + values_sub['name']
|
||||
res.append(values_sub)
|
||||
|
||||
return res
|
||||
@@ -0,0 +1,31 @@
|
||||
<odoo>
|
||||
<template id="report_mrp_bom_line_inherit_mrp_subcontracting" inherit_id="mrp.report_mrp_bom_line">
|
||||
<xpath expr="//t[@name='operations']" position="after">
|
||||
<t t-if="data.get('subcontracting')">
|
||||
<t t-set="space_td" t-value="'margin-left: '+ str(data['subcontracting']['level'] * 20) + 'px;'"/>
|
||||
<td name="td_subcontracting">
|
||||
<div t-att-style="space_td">
|
||||
<div class="o_mrp_bom_no_fold">
|
||||
Subcontracting: <a href="#" class="o_mrp_bom_action" t-att-data-res-id="data['subcontracting']['partner_id']" t-att-data-model="'res.partner'"><t t-esc="data['subcontracting']['name']"/></a>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td/>
|
||||
<t t-foreach="range(data.get('extra_column_count', 0))" t-as="index">
|
||||
<td/>
|
||||
</t>
|
||||
<td class="text-right">
|
||||
<span t-esc="data['subcontracting']['quantity']" t-options='{"widget": "float", "decimal_precision": "Product Unit of Measure"}'/>
|
||||
</td>
|
||||
<td groups="uom.group_uom"><span t-esc="data['subcontracting']['uom']"/></td>
|
||||
<td class="o_mrp_prod_cost text-right">
|
||||
<span t-esc="data['subcontracting']['prod_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
|
||||
</td>
|
||||
<td class="o_mrp_bom_cost text-right">
|
||||
<span t-esc="data['subcontracting']['bom_cost']" t-options='{"widget": "monetary", "display_currency": currency}'/>
|
||||
</td>
|
||||
<td/>
|
||||
</t>
|
||||
</xpath>
|
||||
</template>
|
||||
</odoo>
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import Command
|
||||
from odoo.tests import Form
|
||||
from odoo.tests.common import TransactionCase
|
||||
from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon
|
||||
@@ -492,6 +493,103 @@ class TestSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
mo = self.env['mrp.production'].search([('bom_id', '=', self.bom.id)])
|
||||
self.assertEqual(len(mo), 1)
|
||||
|
||||
def test_flow_flexible_bom_1(self):
|
||||
""" Record Component for a bom subcontracted with a flexible and flexible + warning consumption """
|
||||
self.bom.consumption = 'flexible'
|
||||
# Create a receipt picking from the subcontractor
|
||||
picking_form = Form(self.env['stock.picking'])
|
||||
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
||||
picking_form.partner_id = self.subcontractor_partner1
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished
|
||||
move.product_uom_qty = 1
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_confirm()
|
||||
|
||||
self.assertEqual(picking_receipt.display_action_record_components, 'facultative')
|
||||
action = picking_receipt.action_record_components()
|
||||
mo = self.env['mrp.production'].browse(action['res_id'])
|
||||
mo_form = Form(mo.with_context(**action['context']), view=action['view_id'])
|
||||
mo_form.qty_producing = 1
|
||||
with mo_form.move_line_raw_ids.edit(0) as ml:
|
||||
self.assertEqual(ml.product_id, self.comp1)
|
||||
self.assertEqual(ml.qty_done, 1)
|
||||
ml.qty_done = 2
|
||||
mo = mo_form.save()
|
||||
mo.subcontracting_record_component()
|
||||
self.assertEqual(mo.move_raw_ids[0].move_line_ids.qty_done, 2)
|
||||
|
||||
# We should not be able to call the 'record_components' button
|
||||
self.assertEqual(picking_receipt.display_action_record_components, 'hide')
|
||||
|
||||
picking_receipt.button_validate()
|
||||
self.assertEqual(mo.state, 'done')
|
||||
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
||||
self.assertEqual(avail_qty_comp1, -2)
|
||||
|
||||
def test_flow_warning_bom_1(self):
|
||||
""" Record Component for a bom subcontracted with a flexible and flexible + warning consumption """
|
||||
self.bom.consumption = 'warning'
|
||||
# Create a receipt picking from the subcontractor
|
||||
picking_form = Form(self.env['stock.picking'])
|
||||
picking_form.picking_type_id = self.env.ref('stock.picking_type_in')
|
||||
picking_form.partner_id = self.subcontractor_partner1
|
||||
with picking_form.move_ids_without_package.new() as move:
|
||||
move.product_id = self.finished
|
||||
move.product_uom_qty = 1
|
||||
picking_receipt = picking_form.save()
|
||||
picking_receipt.action_confirm()
|
||||
|
||||
self.assertEqual(picking_receipt.display_action_record_components, 'facultative')
|
||||
action = picking_receipt.action_record_components()
|
||||
mo = self.env['mrp.production'].browse(action['res_id'])
|
||||
mo_form = Form(mo.with_context(**action['context']), view=action['view_id'])
|
||||
mo_form.qty_producing = 1
|
||||
with mo_form.move_line_raw_ids.edit(0) as ml:
|
||||
self.assertEqual(ml.product_id, self.comp1)
|
||||
self.assertEqual(ml.qty_done, 1)
|
||||
ml.qty_done = 2
|
||||
mo = mo_form.save()
|
||||
action_warning = mo.subcontracting_record_component()
|
||||
warning = Form(self.env['mrp.consumption.warning'].with_context(**action_warning['context']))
|
||||
warning = warning.save()
|
||||
warning.action_cancel()
|
||||
|
||||
action_warning = mo.subcontracting_record_component()
|
||||
warning = Form(self.env['mrp.consumption.warning'].with_context(**action_warning['context']))
|
||||
warning = warning.save()
|
||||
warning.action_confirm()
|
||||
|
||||
self.assertEqual(mo.move_raw_ids[0].move_line_ids.qty_done, 2)
|
||||
|
||||
# We should not be able to call the 'record_components' button
|
||||
self.assertEqual(picking_receipt.display_action_record_components, 'hide')
|
||||
|
||||
picking_receipt.button_validate()
|
||||
self.assertEqual(mo.state, 'done')
|
||||
avail_qty_comp1 = self.env['stock.quant']._get_available_quantity(self.comp1, self.subcontractor_partner1.property_stock_subcontractor, allow_negative=True)
|
||||
self.assertEqual(avail_qty_comp1, -2)
|
||||
|
||||
def test_mrp_report_bom_structure_subcontracting(self):
|
||||
self.comp2_bom.write({'type': 'subcontract', 'subcontractor_ids': [Command.link(self.subcontractor_partner1.id)]})
|
||||
self.env['product.supplierinfo'].create({
|
||||
'product_tmpl_id': self.finished.product_tmpl_id.id,
|
||||
'name': self.subcontractor_partner1.id,
|
||||
'price': 10,
|
||||
})
|
||||
supplier = self.env['product.supplierinfo'].create({
|
||||
'product_tmpl_id': self.comp2.product_tmpl_id.id,
|
||||
'name': self.subcontractor_partner1.id,
|
||||
'price': 5,
|
||||
})
|
||||
self.assertTrue(supplier.is_subcontractor)
|
||||
self.comp1.standard_price = 5
|
||||
report_values = self.env['report.mrp.report_bom_structure']._get_report_data(self.bom.id, searchQty=1, searchVariant=False)
|
||||
subcontracting_values = report_values['lines']['subcontracting']
|
||||
self.assertEqual(subcontracting_values['name'], self.subcontractor_partner1.display_name)
|
||||
self.assertEqual(subcontracting_values['bom_cost'], 10)
|
||||
self.assertEqual(report_values['lines']['total'], 20) # 10 For subcontracting + 5 for comp1 + 5 for subcontracting of comp2_bom
|
||||
|
||||
|
||||
class TestSubcontractingTracking(TransactionCase):
|
||||
def setUp(self):
|
||||
|
||||
@@ -2,3 +2,4 @@
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from . import stock_picking
|
||||
from . import product_product
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import models
|
||||
|
||||
class ProductProduct(models.Model):
|
||||
_inherit = 'product.product'
|
||||
|
||||
def _compute_bom_price(self, bom, boms_to_recompute=False, byproduct_bom=False):
|
||||
""" Add the price of the subcontracting supplier if it exists with the bom configuration.
|
||||
"""
|
||||
price = super()._compute_bom_price(bom, boms_to_recompute, byproduct_bom)
|
||||
if bom and bom.type == 'subcontract':
|
||||
seller = self._select_seller(quantity=bom.product_qty, uom_id=bom.product_uom_id, params={'subcontractor_ids': bom.subcontractor_ids})
|
||||
if seller:
|
||||
price += seller.product_uom._compute_price(seller.price, self.uom_id)
|
||||
return price
|
||||
@@ -1,8 +1,12 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon
|
||||
from odoo import Command
|
||||
from odoo.tests.common import Form
|
||||
from odoo.tools.float_utils import float_round, float_compare
|
||||
|
||||
from odoo.addons.mrp_subcontracting.tests.common import TestMrpSubcontractingCommon
|
||||
from odoo.addons.mrp_account.tests.test_bom_price import TestBomPrice
|
||||
|
||||
class TestAccountSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
def test_subcontracting_account_flow_1(self):
|
||||
@@ -132,3 +136,58 @@ class TestAccountSubcontractingFlows(TestMrpSubcontractingCommon):
|
||||
self.assertEqual(len(f_layers), 4)
|
||||
for layer in f_layers:
|
||||
self.assertEqual(layer.value, 100 + 50)
|
||||
|
||||
class TestBomPriceSubcontracting(TestBomPrice):
|
||||
|
||||
def test_01_compute_price_subcontracting_cost(self):
|
||||
"""Test calculation of bom cost with subcontracting."""
|
||||
self.table_head.uom_po_id = self.dozen
|
||||
partner = self.env['res.partner'].create({
|
||||
'name': 'A name can be a Many2one...'
|
||||
})
|
||||
(self.bom_1 | self.bom_2).write({
|
||||
'type': 'subcontract',
|
||||
'subcontractor_ids': [Command.link(partner.id)]
|
||||
})
|
||||
suppliers = self.env['product.supplierinfo'].create([
|
||||
{
|
||||
'name': partner.id,
|
||||
'product_tmpl_id': self.dining_table.product_tmpl_id.id,
|
||||
'price': 150.0,
|
||||
}, {
|
||||
'name': partner.id,
|
||||
'product_tmpl_id': self.table_head.product_tmpl_id.id,
|
||||
'price': 120.0, # 10 by Unit because uom_po_id is in dozen
|
||||
}
|
||||
])
|
||||
self.assertEqual(suppliers.mapped('is_subcontractor'), [True, True])
|
||||
|
||||
# -----------------------------------------------------------------
|
||||
# Cost of BoM (Dining Table 1 Unit)
|
||||
# -----------------------------------------------------------------
|
||||
# Component Cost = Table Head 1 Unit * 300 = 300 (478.75 from it's components)
|
||||
# Screw 5 Unit * 10 = 50
|
||||
# Leg 4 Unit * 25 = 100
|
||||
# Glass 1 Unit * 100 = 100
|
||||
# Subcontracting 1 Unit * 150 = 150
|
||||
# Total = 700 [878.75 if components of Table Head considered] (for 1 Unit)
|
||||
# -----------------------------------------------------------------
|
||||
self.assertEqual(self.dining_table.standard_price, 1000, "Initial price of the Product should be 1000")
|
||||
self.dining_table.button_bom_cost()
|
||||
self.assertEqual(float_round(self.dining_table.standard_price, precision_digits=2), 700.0, "After computing price from BoM price should be 700")
|
||||
|
||||
# Cost of BoM (Table Head 1 Dozen)
|
||||
# -----------------------------------------------------------------
|
||||
# Component Cost = Plywood Sheet 12 Unit * 200 = 2400
|
||||
# Bolt 60 Unit * 10 = 600
|
||||
# Colour 12 Unit * 100 = 1200
|
||||
# Corner Slide 57 Unit * 25 = 1425
|
||||
# Subcontracting 1 Dozen * 120 = 120
|
||||
# Total = 5745
|
||||
# 1 Unit price (5745/12) = 478.75
|
||||
# -----------------------------------------------------------------
|
||||
|
||||
self.assertEqual(self.table_head.standard_price, 300, "Initial price of the Product should be 300")
|
||||
self.Product.browse([self.dining_table.id, self.table_head.id]).action_bom_cost()
|
||||
self.assertEqual(float_compare(self.table_head.standard_price, 478.75, precision_digits=2), 0, "After computing price from BoM price should be 878.75")
|
||||
self.assertEqual(float_compare(self.dining_table.standard_price, 878.75, precision_digits=2), 0, "After computing price from BoM price should be 878.75")
|
||||
|
||||
Reference in New Issue
Block a user