[MOV] mrp_account

The module is moved from the enterprise repository as it
was *without* the bom cost report.

task 1831382
This commit is contained in:
Arnaud Baes
2019-04-03 13:51:05 +00:00
committed by Simon Lejeune
parent 7146a4b23b
commit 1019ceee6e
7 changed files with 209 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import models
+19
View File
@@ -0,0 +1,19 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': 'Accounting - MRP',
'version': '1.0',
'category': 'Manufacturing',
'summary': 'Analytic accounting in Manufacturing',
'description': """
Analytic Accounting in MRP
==========================
* Cost structure report
""",
'website': 'https://www.odoo.com/page/manufacturing',
'depends': ['mrp', 'stock_account'],
'installable': True,
'auto_install': True,
}
+4
View File
@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import mrp_workcenter
from . import mrp_production
@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
from odoo.tools import float_is_zero
class MrpProductionWorkcenterLineTime(models.Model):
_inherit = 'mrp.workcenter.productivity'
cost_already_recorded = fields.Boolean('Cost Recorded', help="Technical field automatically checked when a ongoing production posts journal entries for its costs. This way, we can record one production's cost multiple times and only consider new entries in the work centers time lines.")
class MrpProduction(models.Model):
_inherit = 'mrp.production'
def _cal_price(self, consumed_moves):
"""Set a price unit on the finished move according to `consumed_moves`.
"""
super(MrpProduction, self)._cal_price(consumed_moves)
work_center_cost = 0
finished_move = self.move_finished_ids.filtered(lambda x: x.product_id == self.product_id and x.state not in ('done', 'cancel') and x.quantity_done > 0)
if finished_move:
finished_move.ensure_one()
for work_order in self.workorder_ids:
time_lines = work_order.time_ids.filtered(lambda x: x.date_end and not x.cost_already_recorded)
duration = sum(time_lines.mapped('duration'))
time_lines.write({'cost_already_recorded': True})
work_center_cost += (duration / 60.0) * work_order.workcenter_id.costs_hour
if finished_move.product_id.cost_method in ('fifo', 'average'):
qty_done = finished_move.product_uom._compute_quantity(finished_move.quantity_done, finished_move.product_id.uom_id)
finished_move.price_unit = (sum([-m.value for m in consumed_moves]) + work_center_cost) / qty_done
finished_move.value = sum([-m.value for m in consumed_moves]) + work_center_cost
return True
def _prepare_wc_analytic_line(self, wc_line):
wc = wc_line.workcenter_id
hours = wc_line.duration / 60.0
value = hours * wc.costs_hour
account = wc.costs_hour_account_id.id
return {
'name': wc_line.name + ' (H)',
'amount': -value,
'account_id': account,
'ref': wc.code,
'unit_amount': hours,
}
def _costs_generate(self):
""" Calculates total costs at the end of the production.
"""
self.ensure_one()
AccountAnalyticLine = self.env['account.analytic.line'].sudo()
for wc_line in self.workorder_ids.filtered('workcenter_id.costs_hour_account_id'):
vals = self._prepare_wc_analytic_line(wc_line)
precision_rounding = wc_line.workcenter_id.costs_hour_account_id.currency_id.rounding
if not float_is_zero(vals.get('amount', 0.0), precision_rounding=precision_rounding):
# we use SUPERUSER_ID as we do not guarantee an mrp user
# has access to account analytic lines but still should be
# able to produce orders
AccountAnalyticLine.create(vals)
@api.multi
def button_mark_done(self):
self.ensure_one()
res = super(MrpProduction, self).button_mark_done()
self._costs_generate()
return res
@@ -0,0 +1,11 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models
class MrpWorkcenter(models.Model):
_inherit = 'mrp.workcenter'
costs_hour_account_id = fields.Many2one('account.analytic.account', string='Analytic Account',
help="Fill this only if you want automatic analytic accounting entries on production orders.")
+3
View File
@@ -0,0 +1,3 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from . import test_mrp_account
@@ -0,0 +1,101 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo.tests import Form
from odoo.tests import common
class TestMrpAccount(common.TransactionCase):
def setUp(self):
super(TestMrpAccount, self).setUp()
self.categ_standard = self.env['product.category'].create({'name': 'STANDARD',
'property_cost_method': 'standard',},)
self.categ_real = self.env['product.category'].create({'name': 'REAL',
'property_cost_method': 'fifo',})
self.categ_average = self.env['product.category'].create({'name': 'AVERAGE',
'property_cost_method': 'average'})
self.dining_table = self.env.ref("mrp.product_product_computer_desk")
self.dining_table.categ_id = self.categ_real.id
self.product_table_sheet = self.env.ref('mrp.product_product_computer_desk_head')
self.product_table_sheet.categ_id = self.categ_real.id
self.product_table_leg = self.env.ref('mrp.product_product_computer_desk_leg')
self.product_table_leg.categ_id = self.categ_average.id
self.product_bolt = self.env.ref('mrp.product_product_computer_desk_bolt')
self.product_bolt.categ_id = self.categ_standard.id
self.source_location_id = self.ref('stock.stock_location_14')
self.product_screw = self.env.ref('mrp.product_product_computer_desk_screw')
self.product_screw.categ_id = self.categ_standard.id
self.env['stock.move'].search([('product_id', 'in', [self.product_bolt.id, self.product_screw.id])])._do_unreserve()
(self.product_bolt + self.product_screw).write({'type': 'product'})
self.product_desk = self.env.ref('mrp.product_product_computer_desk')
self.product_desk.tracking = 'none'
def test_00_production_order_with_accounting(self):
self.product_table_sheet.standard_price = 20.0
self.product_table_leg.standard_price = 15.0
self.product_bolt.standard_price = 10.0
self.product_table_leg.tracking = 'none'
self.product_table_sheet.tracking = 'none'
inventory = self.env['stock.inventory'].create({
'name': 'Inventory Product Table',
'filter': 'partial',
'line_ids': [(0, 0, {
'product_id': self.product_table_sheet.id, # tracking serial
'product_uom_id': self.product_table_sheet.uom_id.id,
'product_qty': 20,
'location_id': self.source_location_id
}), (0, 0, {
'product_id': self.product_table_leg.id, # tracking lot
'product_uom_id': self.product_table_leg.uom_id.id,
'product_qty': 20,
'location_id': self.source_location_id
}), (0, 0, {
'product_id': self.product_bolt.id,
'product_uom_id': self.product_bolt.uom_id.id,
'product_qty': 20,
'location_id': self.source_location_id
}), (0, 0, {
'product_id': self.product_screw.id,
'product_uom_id': self.product_screw.uom_id.id,
'product_qty': 200000,
'location_id': self.source_location_id
}),
]
})
inventory.action_validate
bom = self.env.ref('mrp.mrp_bom_desk').copy()
bom.routing_id = False # TODO: extend the test later with the necessary operations
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 = 5.0
production_table = production_table_form.save()
production_table.action_confirm()
produce_form = Form(self.env['mrp.product.produce'].with_context({
'active_id': production_table.id,
'active_ids': [production_table.id],
}))
produce_form.qty_producing = 1.0
produce_wizard = produce_form.save()
produce_wizard.do_produce()
production_table.post_inventory()
move_value = production_table.move_finished_ids.filtered(lambda x: x.state == "done").value
# 1 table head at 20 + 4 table leg at 15 + 4 bolt at 10 + 10 screw at 10
self.assertEqual(move_value, 121, 'Thing should have the correct price')
# produce_wizard = self.env['mrp.product.produce'].with_context({
# 'active_id': production_table.id,
# 'active_ids': [production_table.id],
# }).create({
# 'product_qty': 2.0,
# })
# produce_wizard.do_produce()
# production_table.post_inventory()
# move_value = production_table.move_finished_ids.filtered(lambda x: x.state == "done" and x.product_qty == 2.0).value
# # 2 * Real price of the head (quant: 20) + standard price screw (product: 20*10) + standard price bolt (product: 8*10)
# self.assertEqual(move_value, 280, 'Thing should have the correct price')