1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/mrp_account_enterprise/tests/test_report.py
2024-12-10 09:04:09 +07:00

349 lines
15 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from datetime import datetime, timedelta
from odoo.addons.mrp_account.tests.test_mrp_account import TestMrpAccount
from odoo.tests.common import Form
from odoo import Command
from freezegun import freeze_time
class TestReportsCommon(TestMrpAccount):
def test_mrp_cost_structure(self):
""" Check that values of mrp_cost_structure are correctly calculated even when:
1. byproducts with a cost share.
2. multi-company + multi-currency environment.
"""
# create MO with component cost + operations cost
self.product_table_sheet.standard_price = 20.0
self.product_table_leg.standard_price = 5.0
self.product_bolt.standard_price = 1.0
self.product_screw.standard_price = 2.0
self.product_table_leg.tracking = 'none'
self.product_table_sheet.tracking = 'none'
self.mrp_workcenter.costs_hour = 50.0
bom = self.mrp_bom_desk.copy()
production_table_form = Form(self.env['mrp.production'])
production_table_form.product_id = self.dining_table
production_table_form.bom_id = bom
production_table_form.product_qty = 1
production_table = production_table_form.save()
# add a byproduct w/ a non-zero cost share
byproduct_cost_share = 10
byproduct = self.env['product.product'].create({
'name': 'Plank',
'type': 'product',
})
self.env['stock.move'].create({
'name': "Byproduct",
'product_id': byproduct.id,
'product_uom': byproduct.uom_id.id,
'product_uom_qty': 1,
'production_id': production_table.id,
'location_id': self.ref('stock.stock_location_stock'),
'location_dest_id': self.ref('stock.stock_location_output'),
'cost_share': byproduct_cost_share
})
production_table.action_confirm()
mo_form = Form(production_table)
mo_form.qty_producing = 1
production_table = mo_form.save()
# add operation duration otherwise 0 operation cost
self.env['mrp.workcenter.productivity'].create({
'workcenter_id': self.mrp_workcenter.id,
'date_start': datetime.now() - timedelta(minutes=30),
'date_end': datetime.now(),
'loss_id': self.env.ref('mrp.block_reason7').id,
'description': self.env.ref('mrp.block_reason7').name,
'workorder_id': production_table.workorder_ids[0].id
})
# avoid qty done not being updated when enterprise mrp_workorder is installed
for move in production_table.move_raw_ids:
move.quantity = move.product_uom_qty
move.picked = True
production_table._post_inventory()
production_table.button_mark_done()
total_component_cost = sum(move.product_id.standard_price * move.quantity for move in production_table.move_raw_ids)
total_operation_cost = sum(wo.costs_hour * sum(wo.time_ids.mapped('duration')) / 60.0 for wo in production_table.workorder_ids)
report = self.env['report.mrp_account_enterprise.mrp_cost_structure']
self.env.flush_all() # flush to avoid the wo duration not being available in the db in order to correctly build report
report_values = report._get_report_values(docids=production_table.id)['lines'][0]
self.assertEqual(report_values['component_cost_by_product'][self.dining_table], total_component_cost * (100 - byproduct_cost_share) / 100)
self.assertEqual(report_values['operation_cost_by_product'][self.dining_table], total_operation_cost * (100 - byproduct_cost_share) / 100)
self.assertEqual(report_values['component_cost_by_product'][byproduct], total_component_cost * byproduct_cost_share / 100)
self.assertEqual(report_values['operation_cost_by_product'][byproduct], total_operation_cost * byproduct_cost_share / 100)
# create another company w/ different currency + rate
exchange_rate = 4
currency_p = self.env['res.currency'].create({
'name': 'DBL',
'symbol': 'DD',
'rounding': 0.01,
'currency_unit_label': 'Doubloon'
})
company_p = self.env['res.company'].create({'name': 'Pirates R Us', 'currency_id': currency_p.id})
self.env['res.currency.rate'].create({
'name': '2010-01-01',
'rate': exchange_rate,
'currency_id': self.env.company.currency_id.id,
'company_id': company_p.id,
})
user_p = self.env['res.users'].create({
'name': 'pirate',
'login': 'pirate',
'groups_id': [(6, 0, [self.env.ref('base.group_user').id, self.env.ref('mrp.group_mrp_manager').id])],
'company_id': company_p.id,
'company_ids': [(6, 0, [company_p.id, self.env.company.id])]
})
report_values = report.with_user(user_p)._get_report_values(docids=production_table.id)['lines'][0]
self.assertEqual(report_values['component_cost_by_product'][self.dining_table], total_component_cost * (100 - byproduct_cost_share) / 100 / exchange_rate)
self.assertEqual(report_values['operation_cost_by_product'][self.dining_table], total_operation_cost * (100 - byproduct_cost_share) / 100 / exchange_rate)
self.assertEqual(report_values['component_cost_by_product'][byproduct], total_component_cost * byproduct_cost_share / 100 / exchange_rate)
self.assertEqual(report_values['operation_cost_by_product'][byproduct], total_operation_cost * byproduct_cost_share / 100 / exchange_rate)
@freeze_time('2022-05-28')
def test_mrp_avg_cost_calculation(self):
"""
Check that the average cost is calculated based on the quantity produced in each MO
- Final product Bom structure:
- product_4: qty: 2, cost: $20
- product_3: qty: 3, cost: $50
- Work center > costs_hour = $80
1:/ MO1:
- qty to produce: 10 units
- work_order duration: 300
unit_component_cost = ((20 * 2) + (50 * 3)) = 190
unit_duration = 300 / 10 = 30
unit_operation_cost = (80 / 60) * 30'unit_duration' = 40
unit_cost = 190 + 40 = 250
2:/ MO2:
- update product_3 cost to: $30
- qty to produce: 20 units
- work order duration: 600
unit_component_cost = ((20 * 2) + (30 * 3)) = $130
unit_duration = 600 / 20 = 30
unit_operation_cost = (80 / 60) * 30'unit_duration' = 40
unit_cost = 130 + 40 = 170
total_qty_produced = 30
avg_unit_component_cost = ((190 * 10) + (130 * 20)) / 30 = $150
avg_unit_operation_cost = ((40*20) + (40*10)) / 30 = $40
avg_unit_duration = (600 + 300) / 30 = 30
avg_unit_cost = avg_unit_component_cost + avg_unit_operation_cost = $190
"""
# Make some stock and reserve
for product in self.bom_2.bom_line_ids.product_id:
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': product.id,
'inventory_quantity': 1000,
'location_id': self.stock_location_components.id,
})._apply_inventory()
# Change product_4 UOM to unit
self.bom_2.bom_line_ids[0].product_uom_id = self.ref('uom.product_uom_unit')
# Update the work center cost
self.bom_2.operation_ids.workcenter_id.costs_hour = 80
# MO_1
self.product_4.standard_price = 20
self.product_3.standard_price = 50
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_2
production_form.product_qty = 10
mo_1 = production_form.save()
mo_1.action_confirm()
mo_1.button_plan()
wo = mo_1.workorder_ids
wo.button_start()
wo.duration = 300
wo.qty_producing = 10
mo_1.button_mark_done()
# MO_2
self.product_3.standard_price = 30
production_form = Form(self.env['mrp.production'])
production_form.bom_id = self.bom_2
production_form.product_qty = 20
mo_2 = production_form.save()
mo_2.action_confirm()
mo_2.button_plan()
wo = mo_2.workorder_ids
wo.button_start()
wo.duration = 600
wo.qty_producing = 20
mo_2.button_mark_done()
# must flush else SQL request in report is not accurate
self.env.flush_all()
report = self.env['mrp.report']._read_group(
[('product_id', '=', self.bom_2.product_id.id)],
aggregates=['unit_cost:avg', 'unit_component_cost:avg', 'unit_operation_cost:avg', 'unit_duration:avg'],
)[0]
unit_cost, unit_component_cost, unit_operation_cost, unit_duration = report
self.assertEqual(unit_cost, 190)
self.assertEqual(unit_component_cost, 150)
self.assertEqual(unit_operation_cost, 40)
self.assertEqual(unit_duration, 30)
def test_multiple_users_operation(self):
""" Check what happens on the report when two users log on the same operation simultaneously.
"""
self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings')
user_1 = self.env['res.users'].create({
'name': 'Lonie',
'login': 'lonie',
'email': 'lonie@user.com',
'groups_id': [Command.set([self.env.ref('mrp.group_mrp_user').id])],
})
user_2 = self.env['res.users'].create({
'name': 'Doppleganger',
'login': 'dopple',
'email': 'dopple@user.com',
'groups_id': [Command.set([self.env.ref('mrp.group_mrp_user').id])],
})
production_form = Form(self.env['mrp.production'])
production_form.product_id = self.product_4
production = production_form.save()
with Form(production) as mo_form:
with mo_form.workorder_ids.new() as wo:
wo.name = 'Do important stuff'
wo.workcenter_id = self.workcenter_2
production.action_confirm()
# Have both users working simultaneously on the same operation
self.env['mrp.workcenter.productivity'].create({
'workcenter_id': self.workcenter_2.id,
'date_start': datetime.now() - timedelta(minutes=30),
'date_end': datetime.now(),
'loss_id': self.env.ref('mrp.block_reason7').id,
'workorder_id': production.workorder_ids[0].id,
'user_id': user_1.id,
})
self.env['mrp.workcenter.productivity'].create({
'workcenter_id': self.workcenter_2.id,
'date_start': datetime.now() - timedelta(minutes=20),
'date_end': datetime.now() - timedelta(minutes=5),
'loss_id': self.env.ref('mrp.block_reason7').id,
'workorder_id': production.workorder_ids[0].id,
'user_id': user_2.id,
})
production.button_mark_done()
# Need to flush to have the duration correctly set on the workorders for the report.
self.env.flush_all()
cost_analysis = self.env['report.mrp_account_enterprise.mrp_cost_structure'].get_lines(production)[0]
workcenter_times = list(filter(lambda op: op[0] == self.workcenter_2.name and op[2] == production.workorder_ids[0].name, cost_analysis['operations']))
self.assertEqual(len(workcenter_times), 1, "There should be only a single line for the workcenter cost")
self.assertEqual(workcenter_times[0][3], production.workorder_ids[0].duration / 60, "Duration should be the total duration of this operation.")
@freeze_time('2022-05-28')
def test_cost_analysis_mismatch_in_produced_and_planned_quantity(self):
'''
verify that the Cost Analysis report correctly reflects the actual
quantity produced and cost per unit when it differs from the planned quantity.
'''
# enable by-product
self.env.user.groups_id += self.env.ref('mrp.group_mrp_byproducts')
self.product_3.standard_price = 10
self.product_4.standard_price = 10
bom_1 = self.env['mrp.bom'].create({
'product_id': self.product_5.id,
'product_tmpl_id': self.product_5.product_tmpl_id.id,
'product_uom_id': self.uom_unit.id,
'product_qty': 1.0,
'consumption': 'flexible',
'operation_ids': [
],
'type': 'normal',
'bom_line_ids': [
(0, 0, {'product_id': self.product_3.id, 'product_qty': 1}),
(0, 0, {'product_id': self.product_4.id, 'product_qty': 1})
],
'byproduct_ids': [
Command.create({
'product_id': self.product_2.id,
'product_uom_id': self.product_2.uom_id.id,
'product_qty': 1,
}),
],
})
# Make some stock and reserve
for product in bom_1.bom_line_ids.product_id:
self.env['stock.quant'].with_context(inventory_mode=True).create({
'product_id': product.id,
'inventory_quantity': 1000,
'location_id': self.stock_location_components.id,
})._apply_inventory()
# first MO set qty as 1 produce 5
production_form = Form(self.env['mrp.production'])
production_form.bom_id = bom_1
production_form.product_qty = 1
mo = production_form.save()
mo.action_confirm()
mo_form = Form(mo)
mo_form.qty_producing = 5
# 4 dozen of byproduct
with mo_form.move_byproduct_ids.edit(0) as line:
line.product_uom = self.env.ref('uom.product_uom_dozen')
line.quantity = 4
line.cost_share = 48
mo_done = mo_form.save()
mo_done.button_mark_done()
self.env.flush_all()
cost_analysis = self.env['report.mrp_account_enterprise.mrp_cost_structure'].get_lines(mo_done)
self.assertEqual(cost_analysis[0]['mo_qty'], 5)
self.assertEqual(cost_analysis[0]['total_cost'], 100)
# 4 * dozen = 48 units of by product
self.assertEqual(cost_analysis[0]['qty_by_byproduct'][self.product_2], 48)
self.assertEqual(cost_analysis[0]['total_cost_by_product'][self.product_2], 48)
# first MO set qty as 5 produce 1 without a backorder
production_form = Form(self.env['mrp.production'])
production_form.bom_id = bom_1
production_form.product_qty = 5
no_backorder_mo = production_form.save()
no_backorder_mo.action_confirm()
no_backorder_mo_form = Form(no_backorder_mo)
no_backorder_mo_form.qty_producing = 1
no_backorder_mo_done = no_backorder_mo_form.save()
action = no_backorder_mo_done.button_mark_done()
backorder = Form(self.env['mrp.production.backorder'].with_context(**action['context']))
backorder.save().action_close_mo()
self.env.flush_all()
cost_analysis = self.env['report.mrp_account_enterprise.mrp_cost_structure'].get_lines(no_backorder_mo_done)
self.assertEqual(cost_analysis[0]['mo_qty'], 1)
self.assertEqual(cost_analysis[0]['total_cost'], 20)