[FIX] mrp : BoM report
Current behavior: When creating a BOM with quantity different than 1 (here 12) with operation duration 1 minute and work center capacity 23 The BoM Structure & Cost report only changes the time for the operation when the quantity is > 276 However, when planning a MO the expected duration changes to 2 minutes when the quantity is > 23 Expected behavior: Expected duration should be the same in the MO and BoM report Steps to reproduce: - BOM with quantity different than 1 (here 12) - operation duration 1 minute - work center capacity 23 - go in BoM report closes odoo/odoo#80048 X-original-commit: cf90943875da25456cde4faf967ce27d886c0851 Signed-off-by: William Henrotin (whe) <whe@odoo.com>
This commit is contained in:
@@ -124,7 +124,7 @@ class ReportBomStructure(models.AbstractModel):
|
||||
# Use the product template instead of the variant
|
||||
price = bom.product_tmpl_id.uom_id._compute_price(bom.product_tmpl_id.with_company(company).standard_price, bom.product_uom_id) * bom_quantity
|
||||
attachments = self.env['mrp.document'].search([('res_model', '=', 'product.template'), ('res_id', '=', bom.product_tmpl_id.id)])
|
||||
operations = self._get_operation_line(product, bom, float_round(bom_quantity / bom.product_qty, precision_rounding=1, rounding_method='UP'), 0)
|
||||
operations = self._get_operation_line(product, bom, float_round(bom_quantity, precision_rounding=1, rounding_method='UP'), 0)
|
||||
lines = {
|
||||
'bom': bom,
|
||||
'bom_qty': bom_quantity,
|
||||
@@ -162,7 +162,7 @@ class ReportBomStructure(models.AbstractModel):
|
||||
company = bom.company_id or self.env.company
|
||||
price = line.product_id.uom_id._compute_price(line.product_id.with_company(company).standard_price, line.product_uom_id) * line_quantity
|
||||
if line.child_bom_id:
|
||||
factor = line.product_uom_id._compute_quantity(line_quantity, line.child_bom_id.product_uom_id) / line.child_bom_id.product_qty
|
||||
factor = line.product_uom_id._compute_quantity(line_quantity, line.child_bom_id.product_uom_id)
|
||||
sub_total = self._get_price(line.child_bom_id, factor, line.product_id)
|
||||
byproduct_cost_share = sum(line.child_bom_id.byproduct_ids.mapped('cost_share'))
|
||||
if byproduct_cost_share:
|
||||
@@ -220,7 +220,7 @@ class ReportBomStructure(models.AbstractModel):
|
||||
if operation._skip_operation_line(product):
|
||||
continue
|
||||
operation_cycle = float_round(qty / operation.workcenter_id.capacity, precision_rounding=1, rounding_method='UP')
|
||||
duration_expected = operation_cycle * operation.time_cycle + operation.workcenter_id.time_stop + operation.workcenter_id.time_start
|
||||
duration_expected = operation_cycle * (operation.time_cycle + (operation.workcenter_id.time_stop + operation.workcenter_id.time_start))
|
||||
total = ((duration_expected / 60.0) * operation.workcenter_id.costs_hour)
|
||||
operations.append({
|
||||
'level': level or 0,
|
||||
@@ -248,14 +248,14 @@ class ReportBomStructure(models.AbstractModel):
|
||||
if line._skip_bom_line(product):
|
||||
continue
|
||||
if line.child_bom_id:
|
||||
qty = line.product_uom_id._compute_quantity(line.product_qty * factor, line.child_bom_id.product_uom_id) / line.child_bom_id.product_qty
|
||||
qty = line.product_uom_id._compute_quantity(line.product_qty * (factor / bom.product_qty), line.child_bom_id.product_uom_id) / line.child_bom_id.product_qty
|
||||
sub_price = self._get_price(line.child_bom_id, qty, line.product_id)
|
||||
byproduct_cost_share = sum(line.child_bom_id.byproduct_ids.mapped('cost_share'))
|
||||
if byproduct_cost_share:
|
||||
sub_price *= float_round(1 - byproduct_cost_share / 100, precision_rounding=0.0001)
|
||||
price += sub_price
|
||||
else:
|
||||
prod_qty = line.product_qty * factor
|
||||
prod_qty = line.product_qty * factor / bom.product_qty
|
||||
company = bom.company_id or self.env.company
|
||||
not_rounded_price = line.product_id.uom_id._compute_price(line.product_id.with_context(force_comany=company.id).standard_price, line.product_uom_id) * prod_qty
|
||||
price += company.currency_id.round(not_rounded_price)
|
||||
|
||||
@@ -454,11 +454,10 @@ class TestBoM(TestMrpCommon):
|
||||
|
||||
# TEST BOM STRUCTURE VALUE WITH BOM QUANTITY
|
||||
report_values = self.env['report.mrp.report_bom_structure']._get_report_data(bom_id=bom_crumble.id, searchQty=11, searchVariant=False)
|
||||
# 5 min 'Prepare biscuits' + 3 min 'Prepare butter' + 5 min 'Mix manually' = 13 minutes
|
||||
self.assertEqual(report_values['lines']['operations_time'], 13.0, 'Operation time should be the same for 1 unit or for the batch')
|
||||
# 5 min 'Prepare biscuits' + 3 min 'Prepare butter' + 5 min 'Mix manually' = 13 minutes for 1 biscuits so 13 * 11 = 143 minutes
|
||||
self.assertEqual(report_values['lines']['operations_time'], 143.0, 'Operation time should be the same for 1 unit or for the batch')
|
||||
# Operation cost is the sum of operation line.
|
||||
operation_cost = float_round(5 / 60 * 10, precision_digits=2) * 2 + float_round(3 / 60 * 10, precision_digits=2)
|
||||
self.assertEqual(float_compare(report_values['lines']['operations_cost'], operation_cost, precision_digits=2), 0, '13 minute for 10$/hours -> 2.16')
|
||||
self.assertEqual(float_compare(report_values['lines']['operations_cost'], 23.84, precision_digits=2), 0, '143 minute for 10$/hours -> 23.84')
|
||||
|
||||
for component_line in report_values['lines']['components']:
|
||||
# standard price * bom line quantity * current quantity / bom finished product quantity
|
||||
@@ -468,9 +467,9 @@ class TestBoM(TestMrpCommon):
|
||||
if component_line['prod_id'] == biscuit.id:
|
||||
# 6 kg of biscuits at 1.50$ for 11kg of crumble -> 9$
|
||||
self.assertEqual(float_compare(component_line['total'], (1.5 * 6), precision_digits=2), 0)
|
||||
# total price = 35.05 + 9 + operation_cost(0.83 + 0.83 + 0.5 = 2.16) = 46,21
|
||||
self.assertEqual(float_compare(report_values['lines']['total'], 46.21, precision_digits=2), 0, 'Product Bom Price is not correct')
|
||||
self.assertEqual(float_compare(report_values['lines']['total'] / 11.0, 4.20, precision_digits=2), 0, 'Product Unit Bom Price is not correct')
|
||||
# total price = 35.05 + 9 + operation_cost(23.84) = 67.89
|
||||
self.assertEqual(float_compare(report_values['lines']['total'], 67.89, precision_digits=2), 0, 'Product Bom Price is not correct')
|
||||
self.assertEqual(float_compare(report_values['lines']['total'] / 11.0, 6.17, precision_digits=2), 0, 'Product Unit Bom Price is not correct')
|
||||
|
||||
# TEST BOM STRUCTURE VALUE BY UNIT
|
||||
report_values = self.env['report.mrp.report_bom_structure']._get_report_data(bom_id=bom_crumble.id, searchQty=1, searchVariant=False)
|
||||
@@ -494,13 +493,9 @@ class TestBoM(TestMrpCommon):
|
||||
# TEST OPERATION COST WHEN PRODUCED QTY > BOM QUANTITY
|
||||
report_values_12 = self.env['report.mrp.report_bom_structure']._get_report_data(bom_id=bom_crumble.id, searchQty=12, searchVariant=False)
|
||||
report_values_22 = self.env['report.mrp.report_bom_structure']._get_report_data(bom_id=bom_crumble.id, searchQty=22, searchVariant=False)
|
||||
operation_cost = float_round(10 / 60 * 10, precision_digits=2) * 2 + float_round(6 / 60 * 10, precision_digits=2)
|
||||
# Both needs 2 operation cycle
|
||||
self.assertEqual(report_values_12['lines']['operations_cost'], report_values_22['lines']['operations_cost'])
|
||||
self.assertEqual(report_values_22['lines']['operations_cost'], operation_cost)
|
||||
report_values_23 = self.env['report.mrp.report_bom_structure']._get_report_data(bom_id=bom_crumble.id, searchQty=23, searchVariant=False)
|
||||
operation_cost = float_round(15 / 60 * 10, precision_digits=2) * 2 + float_round(9 / 60 * 10, precision_digits=2)
|
||||
self.assertEqual(report_values_23['lines']['operations_cost'], operation_cost)
|
||||
|
||||
#Operation cost = 47.66 € = 256 (min) * 10€/h
|
||||
self.assertEqual(float_compare(report_values_22['lines']['operations_cost'], 47.66, precision_digits=2), 0, 'Operation cost is not correct')
|
||||
|
||||
# Create a more complex BoM with a sub product
|
||||
cheese_cake = self.env['product.product'].create({
|
||||
@@ -547,13 +542,12 @@ class TestBoM(TestMrpCommon):
|
||||
operation.time_cycle_manual = 5
|
||||
operation.bom_id = bom_cheese_cake
|
||||
|
||||
|
||||
# TEST CHEESE BOM STRUCTURE VALUE WITH BOM QUANTITY
|
||||
report_values = self.env['report.mrp.report_bom_structure']._get_report_data(bom_id=bom_cheese_cake.id, searchQty=60, searchVariant=False)
|
||||
self.assertEqual(report_values['lines']['operations_time'], 40.0, 'Operation time should be the same for 1 unit or for the batch')
|
||||
# Operation cost is the sum of operation line.
|
||||
operation_cost = float_round(10 / 60 * 10, precision_digits=2) + float_round(30 / 60 * 20, precision_digits=2)
|
||||
self.assertEqual(float_compare(report_values['lines']['operations_cost'], operation_cost, precision_digits=2), 0)
|
||||
#Operation time = 15 min * 60 + time_start + time_stop = 925
|
||||
self.assertEqual(report_values['lines']['operations_time'], 2400.0, 'Operation time should be the same for 1 unit or for the batch')
|
||||
# Operation cost is the sum of operation line : 10 min * 60 * 10€/hr = 100€ + 30min * 60 * 20€/hr = 700€
|
||||
self.assertEqual(float_compare(report_values['lines']['operations_cost'], 700, precision_digits=2), 0)
|
||||
|
||||
for component_line in report_values['lines']['components']:
|
||||
# standard price * bom line quantity * current quantity / bom finished product quantity
|
||||
@@ -564,8 +558,8 @@ class TestBoM(TestMrpCommon):
|
||||
# 5.4 kg of crumble at the cost of a batch.
|
||||
crumble_cost = self.env['report.mrp.report_bom_structure']._get_report_data(bom_id=bom_crumble.id, searchQty=5.4, searchVariant=False)['lines']['total']
|
||||
self.assertEqual(float_compare(component_line['total'], crumble_cost, precision_digits=2), 0)
|
||||
# total price = 15.51 + crumble_cost + operation_cost(10 + 1.67 = 11.67) = 27.18 + crumble_cost
|
||||
self.assertEqual(float_compare(report_values['lines']['total'], 27.18 + crumble_cost, precision_digits=2), 0, 'Product Bom Price is not correct')
|
||||
# total price = Cream (15.51€) + crumble_cost (34.63 €) + operation_cost(700€) = 750.14€
|
||||
self.assertEqual(float_compare(report_values['lines']['total'], 715.51 + crumble_cost, precision_digits=2), 0, 'Product Bom Price is not correct')
|
||||
|
||||
def test_bom_report_dozens(self):
|
||||
""" Simulate a drawer bom with dozens as bom units
|
||||
@@ -611,7 +605,7 @@ class TestBoM(TestMrpCommon):
|
||||
# TEST BOM STRUCTURE VALUE WITH BOM QUANTITY
|
||||
report_values = self.env['report.mrp.report_bom_structure']._get_report_data(bom_id=bom_drawer.id, searchQty=11, searchVariant=False)
|
||||
# 5 min 'Prepare biscuits' + 3 min 'Prepare butter' + 5 min 'Mix manually' = 13 minutes
|
||||
self.assertEqual(report_values['lines']['operations_time'], 60.0, 'Operation time should be the same for 1 unit or for the batch')
|
||||
self.assertEqual(report_values['lines']['operations_time'], 660.0, 'Operation time should be the same for 1 unit or for the batch')
|
||||
|
||||
|
||||
def test_21_bom_report_variant(self):
|
||||
@@ -908,7 +902,7 @@ class TestBoM(TestMrpCommon):
|
||||
|
||||
report_values = self.env['report.mrp.report_bom_structure']._get_report_data(bom_id=bom_finished.id, searchQty=80)
|
||||
|
||||
self.assertAlmostEqual(report_values['lines']['total'], 2.92)
|
||||
self.assertAlmostEqual(report_values['lines']['total'], 0.58)
|
||||
|
||||
def test_validate_no_bom_line_with_same_product(self):
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user