From f13d537a42ff1a2713484d0e7c39b73fbb87a7fb Mon Sep 17 00:00:00 2001 From: "Pieter Claeys (clpi)" Date: Fri, 24 Mar 2023 09:26:53 +0000 Subject: [PATCH] [IMP] mrp: New analytic for MRP The (legacy) analytic account field on MOs, BOMs and workcenters has been replaced by the new "analytic distribution" field. The analytic line items are now created based on this distribution for the raw materials, workcenter costs and employee costs. A new analytic applicability (=domain) has been added for manufacturing orders as well. Community PR: https://github.com/odoo/odoo/pull/116477 Enterprise PR: https://github.com/odoo/enterprise/pull/38690 closes odoo/odoo#116477 Task: 3196563 Related: odoo/upgrade#4704 Related: odoo/enterprise#38690 Signed-off-by: Arnold Moyaux (arm) --- addons/analytic/models/analytic_mixin.py | 2 +- addons/mrp_account/models/analytic_account.py | 17 ++- addons/mrp_account/models/mrp_bom.py | 46 ++++++- addons/mrp_account/models/mrp_production.py | 72 +++++----- addons/mrp_account/models/mrp_workcenter.py | 21 ++- addons/mrp_account/models/mrp_workorder.py | 56 ++------ addons/mrp_account/models/stock_move.py | 16 +-- addons/mrp_account/models/stock_rule.py | 4 +- .../tests/test_analytic_account.py | 128 ++++++------------ addons/mrp_account/views/mrp_bom_views.xml | 4 +- .../views/mrp_production_views.xml | 13 +- addons/project_mrp/__manifest__.py | 3 - addons/project_mrp/data/project_mrp_demo.xml | 7 - addons/project_mrp/models/project_project.py | 2 +- addons/stock_account/models/__init__.py | 1 + .../stock_account/models/analytic_account.py | 77 +++++++++++ addons/stock_account/models/stock_move.py | 38 ++---- 17 files changed, 282 insertions(+), 225 deletions(-) delete mode 100644 addons/project_mrp/data/project_mrp_demo.xml create mode 100644 addons/stock_account/models/analytic_account.py diff --git a/addons/analytic/models/analytic_mixin.py b/addons/analytic/models/analytic_mixin.py index 33f3dc4c7f2..1cf4154bcb0 100644 --- a/addons/analytic/models/analytic_mixin.py +++ b/addons/analytic/models/analytic_mixin.py @@ -29,7 +29,7 @@ class AnalyticMixin(models.AbstractModel): FROM information_schema.tables WHERE table_name=%s ''' self.env.cr.execute(query, [self._table]) - if self.env.cr.dictfetchone(): + if self.env.cr.dictfetchone() and self._fields['analytic_distribution'].store: query = f""" CREATE INDEX IF NOT EXISTS {self._table}_analytic_distribution_gin_index ON {self._table} USING gin(analytic_distribution); diff --git a/addons/mrp_account/models/analytic_account.py b/addons/mrp_account/models/analytic_account.py index 1cf3e213eed..1d87481aa5d 100644 --- a/addons/mrp_account/models/analytic_account.py +++ b/addons/mrp_account/models/analytic_account.py @@ -8,11 +8,11 @@ class AccountAnalyticAccount(models.Model): _inherit = 'account.analytic.account' _description = 'Analytic Account' - production_ids = fields.One2many('mrp.production', 'analytic_account_id', string='Manufacturing Orders') + production_ids = fields.Many2many('mrp.production') production_count = fields.Integer("Manufacturing Orders Count", compute='_compute_production_count') - bom_ids = fields.One2many('mrp.bom', 'analytic_account_id', string='Bills of Materials') + bom_ids = fields.Many2many('mrp.bom') bom_count = fields.Integer("BoM Count", compute='_compute_bom_count') - workcenter_ids = fields.One2many('mrp.workcenter', 'costs_hour_account_id', string='Workcenters') + workcenter_ids = fields.Many2many('mrp.workcenter') workorder_count = fields.Integer("Work Order Count", compute='_compute_workorder_count') @api.depends('production_ids') @@ -77,3 +77,14 @@ class AccountAnalyticLine(models.Model): _inherit = 'account.analytic.line' category = fields.Selection(selection_add=[('manufacturing_order', 'Manufacturing Order')]) + +class AccountAnalyticApplicability(models.Model): + _inherit = 'account.analytic.applicability' + _description = "Analytic Plan's Applicabilities" + + business_domain = fields.Selection( + selection_add=[ + ('manufacturing_order', 'Manufacturing Order'), + ], + ondelete={'manufacturing_order': 'cascade'}, + ) diff --git a/addons/mrp_account/models/mrp_bom.py b/addons/mrp_account/models/mrp_bom.py index 8ab3aba569c..7f23d2d1774 100644 --- a/addons/mrp_account/models/mrp_bom.py +++ b/addons/mrp_account/models/mrp_bom.py @@ -1,10 +1,48 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. +import json + +from odoo import models, api, fields -from odoo import fields, models class MrpBom(models.Model): - _inherit = 'mrp.bom' + _name = 'mrp.bom' + _inherit = ['mrp.bom', 'analytic.mixin'] - analytic_account_id = fields.Many2one('account.analytic.account', 'Analytic Account', company_dependent=True, copy=True, - help="Analytic account in which cost and revenue entries will take place for financial management of the manufacturing order.") + # Company dependent JSON fields are not yet supported + analytic_distribution_text = fields.Text(company_dependent=True) + analytic_distribution = fields.Json(inverse="_inverse_analytic_distribution", store=False, precompute=False) + analytic_account_ids = fields.Many2many('account.analytic.account', compute="_compute_analytic_account_ids", copy=True) + + @api.depends_context('company') + @api.depends('analytic_distribution_text') + def _compute_analytic_distribution(self): + for record in self: + record.analytic_distribution = json.loads(record.analytic_distribution_text or '{}') + + def _inverse_analytic_distribution(self): + for record in self: + record.analytic_distribution_text = json.dumps(record.analytic_distribution) + + @api.depends('analytic_distribution') + def _compute_analytic_account_ids(self): + for record in self: + record.analytic_account_ids = list(map(int, record.analytic_distribution.keys())) if record.analytic_distribution else [] + + @api.onchange('product_id') + def _onchange_analytic_distribution(self): + for record in self: + if record.product_id: + record.analytic_distribution = record.env['account.analytic.distribution.model']._get_distribution({ + "product_id": record.product_id.id, + "product_categ_id": record.product_id.categ_id.id, + "company_id": record.company_id.id, + }) + + @api.constrains('analytic_distribution') + def _check_analytic(self): + for record in self: + record.with_context({'validate_analytic': True})._validate_distribution(**{ + 'product': record.product_id.id, + 'company_id': record.company_id.id, + }) diff --git a/addons/mrp_account/models/mrp_production.py b/addons/mrp_account/models/mrp_production.py index 10ee676c9ca..3ebb7d484e4 100644 --- a/addons/mrp_account/models/mrp_production.py +++ b/addons/mrp_account/models/mrp_production.py @@ -1,53 +1,60 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. - from ast import literal_eval from odoo import api, fields, models, _ -from odoo.tools import float_is_zero, float_round +from odoo.tools import float_round class MrpProduction(models.Model): - _inherit = 'mrp.production' + _name = 'mrp.production' + _inherit = ['mrp.production', 'analytic.mixin'] extra_cost = fields.Float(copy=False, string='Extra Unit Cost') show_valuation = fields.Boolean(compute='_compute_show_valuation') - analytic_account_id = fields.Many2one( - 'account.analytic.account', 'Analytic Account', copy=True, - help="Analytic account in which cost and revenue entries will take\ - place for financial management of the manufacturing order.", - compute='_compute_analytic_account_id', store=True, readonly=False) + analytic_account_ids = fields.Many2many('account.analytic.account', compute='_compute_analytic_account_ids', store=True) def _compute_show_valuation(self): for order in self: order.show_valuation = any(m.state == 'done' for m in order.move_finished_ids) - @api.depends('bom_id') - def _compute_analytic_account_id(self): - if self.bom_id.analytic_account_id: - self.analytic_account_id = self.bom_id.analytic_account_id + @api.depends('bom_id', 'product_id') + def _compute_analytic_distribution(self): + for record in self: + if record.bom_id.analytic_distribution: + record.analytic_distribution = record.bom_id.analytic_distribution + else: + record.analytic_distribution = record.env['account.analytic.distribution.model']._get_distribution({ + "product_id": record.product_id.id, + "product_categ_id": record.product_id.categ_id.id, + "company_id": record.company_id.id, + }) + + @api.depends('analytic_distribution') + def _compute_analytic_account_ids(self): + for record in self: + record.analytic_account_ids = list( + map(int, record.analytic_distribution.keys())) if record.analytic_distribution else [] + + @api.constrains('analytic_distribution') + def _check_analytic(self): + for record in self: + params = {'business_domain': 'manufacturing_order', 'company_id': record.company_id.id} + if record.product_id: + params['product'] = record.product_id.id + record.with_context({'validate_analytic': True})._validate_distribution(**params) def write(self, vals): - origin_analytic_account = {production: production.analytic_account_id for production in self} res = super().write(vals) for production in self: if vals.get('name'): - production.move_raw_ids.analytic_account_line_id.ref = production.display_name + production.move_raw_ids.analytic_account_line_ids.ref = production.display_name for workorder in production.workorder_ids: - workorder.mo_analytic_account_line_id.ref = production.display_name - workorder.mo_analytic_account_line_id.name = _("[WC] %s", workorder.display_name) - if 'analytic_account_id' in vals and production.state != 'draft': - if vals['analytic_account_id'] and origin_analytic_account[production]: - # Link the account analytic lines to the new AA - production.move_raw_ids.analytic_account_line_id.write({'account_id': vals['analytic_account_id']}) - production.workorder_ids.mo_analytic_account_line_id.write({'account_id': vals['analytic_account_id']}) - elif vals['analytic_account_id'] and not origin_analytic_account[production]: - # Create the account analytic lines if no AA is set in the MO - production.move_raw_ids._account_analytic_entry_move() - production.workorder_ids._create_or_update_analytic_entry() - else: - production.move_raw_ids.analytic_account_line_id.unlink() - production.workorder_ids.mo_analytic_account_line_id.unlink() + workorder.mo_analytic_account_line_ids.ref = production.display_name + workorder.mo_analytic_account_line_ids.name = _("[WC] %s", workorder.display_name) + if 'analytic_distribution' in vals and production.state != 'draft': + production.move_raw_ids._account_analytic_entry_move() + production.workorder_ids._create_or_update_analytic_entry() return res def action_view_stock_valuation_layers(self): @@ -60,15 +67,14 @@ class MrpProduction(models.Model): context['search_default_group_by_product_id'] = False return dict(action, domain=domain, context=context) - def action_view_analytic_account(self): + def action_view_analytic_accounts(self): self.ensure_one() return { "type": "ir.actions.act_window", "res_model": "account.analytic.account", - 'res_id': self.analytic_account_id.id, - "context": {"create": False}, - "name": _("Analytic Account"), - 'view_mode': 'form', + 'domain': [('id', 'in', self.analytic_account_ids.ids)], + "name": _("Analytic Accounts"), + 'view_mode': 'tree,form', } def _cal_price(self, consumed_moves): diff --git a/addons/mrp_account/models/mrp_workcenter.py b/addons/mrp_account/models/mrp_workcenter.py index 3cddf595e66..6f56856beaa 100644 --- a/addons/mrp_account/models/mrp_workcenter.py +++ b/addons/mrp_account/models/mrp_workcenter.py @@ -1,12 +1,23 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import fields, models +from odoo import api, models, fields class MrpWorkcenter(models.Model): - _inherit = 'mrp.workcenter' + _name = 'mrp.workcenter' + _inherit = ['mrp.workcenter', 'analytic.mixin'] - costs_hour_account_id = fields.Many2one( - 'account.analytic.account', string='Analytic Account', - help="Posts analytical accounting entries in real time for both component and operational costs.") + costs_hour_account_ids = fields.Many2many('account.analytic.account', compute="_compute_costs_hour_account_ids", store=True) + + @api.depends('analytic_distribution') + def _compute_costs_hour_account_ids(self): + for record in self: + record.costs_hour_account_ids = list(map(int, record.analytic_distribution.keys())) if record.analytic_distribution else [] + + @api.constrains('analytic_distribution') + def _check_analytic(self): + for record in self: + record.with_context({'validate_analytic': True})._validate_distribution(**{ + 'company_id': record.company_id.id, + }) diff --git a/addons/mrp_account/models/mrp_workorder.py b/addons/mrp_account/models/mrp_workorder.py index 89c7e8a2fab..9cd2f7e285c 100644 --- a/addons/mrp_account/models/mrp_workorder.py +++ b/addons/mrp_account/models/mrp_workorder.py @@ -2,14 +2,13 @@ # Part of Odoo. See LICENSE file for full copyright and licensing details. from odoo import fields, models, _ -from odoo.tools import float_is_zero class MrpWorkorder(models.Model): _inherit = 'mrp.workorder' - mo_analytic_account_line_id = fields.Many2one('account.analytic.line', copy=False) - wc_analytic_account_line_id = fields.Many2one('account.analytic.line', copy=False) + mo_analytic_account_line_ids = fields.Many2many('account.analytic.line', 'mrp_workorder_mo_analytic_rel', copy=False) + wc_analytic_account_line_ids = fields.Many2many('account.analytic.line', 'mrp_workorder_wc_analytic_rel', copy=False) def _compute_duration(self): res = super()._compute_duration() @@ -22,15 +21,15 @@ class MrpWorkorder(models.Model): return res def action_cancel(self): - (self.mo_analytic_account_line_id | self.wc_analytic_account_line_id).unlink() + (self.mo_analytic_account_line_ids | self.wc_analytic_account_line_ids).unlink() return super().action_cancel() - def _prepare_analytic_line_values(self, account, unit_amount, amount): + def _prepare_analytic_line_values(self, account, amount, unit_amount): self.ensure_one() return { 'name': _("[WC] %s", self.display_name), 'amount': amount, - 'account_id': account.id, + 'account_id': account, 'unit_amount': unit_amount, 'product_id': self.product_id.id, 'product_uom_id': self.env.ref('uom.product_uom_hour').id, @@ -40,43 +39,18 @@ class MrpWorkorder(models.Model): } def _create_or_update_analytic_entry(self): - wo_to_link_mo_analytic_line = self.env['mrp.workorder'] - wo_to_link_wc_analytic_line = self.env['mrp.workorder'] - mo_analytic_line_vals_list = [] - wc_analytic_line_vals_list = [] - for wo in self.filtered(lambda wo: wo.production_id.analytic_account_id or wo.workcenter_id.costs_hour_account_id): + for wo in self.filtered(lambda wo: wo.id and (wo.production_id.analytic_distribution or wo.workcenter_id.analytic_distribution or wo.wc_analytic_account_line_ids or wo.mo_analytic_account_line_ids)): hours = wo.duration / 60.0 value = -hours * wo.workcenter_id.costs_hour - mo_account = wo.production_id.analytic_account_id - wc_account = wo.workcenter_id.costs_hour_account_id - if mo_account: - mo_currency = mo_account.currency_id or wo.company_id.currency_id - is_zero = float_is_zero(value, precision_rounding=mo_currency.rounding) - if wo.mo_analytic_account_line_id: - wo.mo_analytic_account_line_id.write({ - 'unit_amount': hours, - 'amount': value if not is_zero else 0, - }) - elif not is_zero: - wo_to_link_mo_analytic_line += wo - mo_analytic_line_vals_list.append(wo._prepare_analytic_line_values(mo_account, hours, value)) - if wc_account and wc_account != mo_account: - is_zero = float_is_zero(value, precision_rounding=wc_account.currency_id.rounding) - if wo.wc_analytic_account_line_id: - wo.wc_analytic_account_line_id.write({ - 'unit_amount': hours, - 'amount': value if not is_zero else 0, - }) - elif not is_zero: - wo_to_link_wc_analytic_line += wo - wc_analytic_line_vals_list.append(wo._prepare_analytic_line_values(wc_account, hours, value)) - analytic_lines = self.env['account.analytic.line'].sudo().create(mo_analytic_line_vals_list + wc_analytic_line_vals_list) - mo_analytic_lines, wc_analytic_lines = analytic_lines[:len(wo_to_link_mo_analytic_line)], analytic_lines[len(wo_to_link_mo_analytic_line):] - for wo, analytic_line in zip(wo_to_link_mo_analytic_line, mo_analytic_lines): - wo.mo_analytic_account_line_id = analytic_line - for wo, analytic_line in zip(wo_to_link_wc_analytic_line, wc_analytic_lines): - wo.wc_analytic_account_line_id = analytic_line + + mo_analytic_line_vals = self.env['account.analytic.account']._perform_analytic_distribution(wo.production_id.analytic_distribution, value, hours, wo.mo_analytic_account_line_ids, wo) + if mo_analytic_line_vals: + wo.mo_analytic_account_line_ids += self.env['account.analytic.line'].sudo().create(mo_analytic_line_vals) + + wc_analytic_line_vals = self.env['account.analytic.account']._perform_analytic_distribution(wo.workcenter_id.analytic_distribution, value, hours, wo.wc_analytic_account_line_ids, wo) + if wc_analytic_line_vals: + wo.wc_analytic_account_line_ids += self.env['account.analytic.line'].sudo().create(wc_analytic_line_vals) def unlink(self): - (self.mo_analytic_account_line_id | self.wc_analytic_account_line_id).unlink() + (self.mo_analytic_account_line_ids | self.wc_analytic_account_line_ids).unlink() return super().unlink() diff --git a/addons/mrp_account/models/stock_move.py b/addons/mrp_account/models/stock_move.py index e13ff82be58..4b22c852fa6 100644 --- a/addons/mrp_account/models/stock_move.py +++ b/addons/mrp_account/models/stock_move.py @@ -12,19 +12,19 @@ class StockMove(models.Model): res += self.filtered(lambda m: m.bom_line_id.bom_id.product_tmpl_id.id == product.product_tmpl_id.id) return res - def _generate_analytic_lines_data(self, unit_amount, amount): - vals = super()._generate_analytic_lines_data(unit_amount, amount) - if self.raw_material_production_id.analytic_account_id: + def _generate_analytic_lines_data(self, unit_amount, amount, distribution, account, distribution_on_each_plan): + vals = super()._generate_analytic_lines_data(unit_amount, amount, distribution, account, distribution_on_each_plan) + if self._get_analytic_distribution(): vals['name'] = _('[Raw] %s', self.product_id.display_name) vals['ref'] = self.raw_material_production_id.display_name vals['category'] = 'manufacturing_order' return vals - def _get_analytic_account(self): - account = self.raw_material_production_id.analytic_account_id - if account: - return account - return super()._get_analytic_account() + def _get_analytic_distribution(self): + distribution = self.raw_material_production_id.analytic_distribution + if distribution: + return distribution + return super()._get_analytic_distribution() def _ignore_automatic_valuation(self): return bool(self.raw_material_production_id) diff --git a/addons/mrp_account/models/stock_rule.py b/addons/mrp_account/models/stock_rule.py index f10a8bc17f6..5c1624eccdf 100644 --- a/addons/mrp_account/models/stock_rule.py +++ b/addons/mrp_account/models/stock_rule.py @@ -9,6 +9,6 @@ class StockRule(models.Model): def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values, bom): res = super()._prepare_mo_vals(product_id, product_qty, product_uom, location_id, name, origin, company_id, values, bom) - if not bom.analytic_account_id and values.get('analytic_account_id'): - res['analytic_account_id'] = values.get('analytic_account_id').id + if not bom.analytic_distribution and values.get('analytic_distribution'): + res['analytic_distribution'] = values.get('analytic_distribution') return res diff --git a/addons/mrp_account/tests/test_analytic_account.py b/addons/mrp_account/tests/test_analytic_account.py index ee25eb56ac1..8c9488eacb9 100644 --- a/addons/mrp_account/tests/test_analytic_account.py +++ b/addons/mrp_account/tests/test_analytic_account.py @@ -64,32 +64,30 @@ class TestAnalyticAccount(TestMrpAnalyticAccount): mo_form.product_id = self.product mo_form.bom_id = self.bom mo_form.product_qty = 10.0 - mo_form.analytic_account_id = self.analytic_account + mo_form.analytic_distribution = {str(self.analytic_account.id): 100.0} mo = mo_form.save() mo.action_confirm() self.assertEqual(mo.state, 'confirmed') - self.assertEqual(len(mo.move_raw_ids.analytic_account_line_id), 0) + self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 0) # increase qty_producing to 5.0 mo_form = Form(mo) mo_form.qty_producing = 5.0 mo_form.save() self.assertEqual(mo.state, 'progress') - self.assertEqual(mo.move_raw_ids.analytic_account_line_id.amount, -50.0) + self.assertEqual(mo.move_raw_ids.analytic_account_line_ids.amount, -50.0) # increase qty_producing to 10.0 mo_form = Form(mo) mo_form.qty_producing = 10.0 mo_form.save() - # Hack to bypass test doing strange things - mo._set_qty_producing() mo.workorder_ids.button_finish() self.assertEqual(mo.state, 'to_close') - self.assertEqual(mo.move_raw_ids.analytic_account_line_id.amount, -100.0) + self.assertEqual(mo.move_raw_ids.analytic_account_line_ids.amount, -100.0) # mark as done mo.button_mark_done() self.assertEqual(mo.state, 'done') - self.assertEqual(mo.move_raw_ids.analytic_account_line_id.amount, -100.0) + self.assertEqual(mo.move_raw_ids.analytic_account_line_ids.amount, -100.0) def test_mo_analytic_backorder(self): """Test the analytic lines are correctly posted when backorder. @@ -99,65 +97,23 @@ class TestAnalyticAccount(TestMrpAnalyticAccount): mo_form.product_id = self.product mo_form.bom_id = self.bom mo_form.product_qty = 10.0 - mo_form.analytic_account_id = self.analytic_account + mo_form.analytic_distribution = {str(self.analytic_account.id): 100.0} mo = mo_form.save() mo.action_confirm() self.assertEqual(mo.state, 'confirmed') - self.assertEqual(len(mo.move_raw_ids.analytic_account_line_id), 0) + self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 0) # increase qty_producing to 5.0 mo_form = Form(mo) mo_form.qty_producing = 5.0 mo_form.save() self.assertEqual(mo.state, 'progress') - self.assertEqual(mo.move_raw_ids.analytic_account_line_id.amount, -50.0) + self.assertEqual(mo.move_raw_ids.analytic_account_line_ids.amount, -50.0) backorder_wizard_dict = mo.button_mark_done() Form(self.env[(backorder_wizard_dict.get('res_model'))].with_context(backorder_wizard_dict['context'])).save().action_backorder() self.assertEqual(mo.state, 'done') - self.assertEqual(mo.move_raw_ids.analytic_account_line_id.amount, -50.0) - - def test_workcenter_same_analytic_account(self): - """Test when workcenter and MO are using the same analytic account, no - duplicated lines will be post. - """ - # Required for `workorder_ids` to be visible in the view - self.env.user.groups_id += self.env.ref('mrp.group_mrp_routings') - # set wc analytic account to be the same of the one on the bom - self.workcenter.costs_hour_account_id = self.analytic_account - - # create a mo - mo_form = Form(self.env['mrp.production']) - mo_form.product_id = self.product - mo_form.bom_id = self.bom - mo_form.product_qty = 10.0 - mo_form.analytic_account_id = self.analytic_account - mo = mo_form.save() - mo.action_confirm() - self.assertEqual(len(mo.workorder_ids.wc_analytic_account_line_id), 0) - - # change duration to 60 - mo_form = Form(mo) - with mo_form.workorder_ids.edit(0) as line_edit: - line_edit.duration = 60.0 - mo_form.save() - self.assertEqual(mo.workorder_ids.mo_analytic_account_line_id.amount, -10.0) - self.assertEqual(len(mo.workorder_ids.wc_analytic_account_line_id), 0) - - # change duration to 120 - with mo_form.workorder_ids.edit(0) as line_edit: - line_edit.duration = 120.0 - mo_form.save() - self.assertEqual(mo.workorder_ids.mo_analytic_account_line_id.amount, -20.0) - self.assertEqual(len(mo.workorder_ids.wc_analytic_account_line_id), 0) - - # mark as done - mo_form.qty_producing = 10.0 - mo_form.save() - mo.button_mark_done() - self.assertEqual(mo.state, 'done') - self.assertEqual(mo.workorder_ids.mo_analytic_account_line_id.amount, -20.0) - self.assertEqual(len(mo.workorder_ids.wc_analytic_account_line_id), 0) + self.assertEqual(mo.move_raw_ids.analytic_account_line_ids.amount, -50.0) def test_workcenter_different_analytic_account(self): """Test when workcenter and MO are using the same analytic account, no @@ -168,46 +124,46 @@ class TestAnalyticAccount(TestMrpAnalyticAccount): # set wc analytic account to be different from the one on the bom analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False}) wc_analytic_account = self.env['account.analytic.account'].create({'name': 'wc_analytic_account', 'plan_id': analytic_plan.id}) - self.workcenter.costs_hour_account_id = wc_analytic_account + self.workcenter.analytic_distribution = {str(wc_analytic_account.id): 100.0} # create a mo mo_form = Form(self.env['mrp.production']) mo_form.product_id = self.product mo_form.bom_id = self.bom mo_form.product_qty = 10.0 - mo_form.analytic_account_id = self.analytic_account + mo_form.analytic_distribution = {str(self.analytic_account.id): 100.0} mo = mo_form.save() mo.action_confirm() - self.assertEqual(len(mo.workorder_ids.wc_analytic_account_line_id), 0) + self.assertEqual(len(mo.workorder_ids.wc_analytic_account_line_ids), 0) # change duration to 60 mo_form = Form(mo) with mo_form.workorder_ids.edit(0) as line_edit: line_edit.duration = 60.0 mo_form.save() - self.assertEqual(mo.workorder_ids.mo_analytic_account_line_id.amount, -10.0) - self.assertEqual(mo.workorder_ids.mo_analytic_account_line_id.account_id, self.analytic_account) - self.assertEqual(mo.workorder_ids.wc_analytic_account_line_id.amount, -10.0) - self.assertEqual(mo.workorder_ids.wc_analytic_account_line_id.account_id, wc_analytic_account) + self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids.amount, -10.0) + self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids.account_id, self.analytic_account) + self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids.amount, -10.0) + self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids.account_id, wc_analytic_account) # change duration to 120 with mo_form.workorder_ids.edit(0) as line_edit: line_edit.duration = 120.0 mo_form.save() - self.assertEqual(mo.workorder_ids.mo_analytic_account_line_id.amount, -20.0) - self.assertEqual(mo.workorder_ids.mo_analytic_account_line_id.account_id, self.analytic_account) - self.assertEqual(mo.workorder_ids.wc_analytic_account_line_id.amount, -20.0) - self.assertEqual(mo.workorder_ids.wc_analytic_account_line_id.account_id, wc_analytic_account) + self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids.amount, -20.0) + self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids.account_id, self.analytic_account) + self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids.amount, -20.0) + self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids.account_id, wc_analytic_account) # mark as done mo_form.qty_producing = 10.0 mo_form.save() mo.button_mark_done() self.assertEqual(mo.state, 'done') - self.assertEqual(mo.workorder_ids.mo_analytic_account_line_id.amount, -20.0) - self.assertEqual(mo.workorder_ids.mo_analytic_account_line_id.account_id, self.analytic_account) - self.assertEqual(mo.workorder_ids.wc_analytic_account_line_id.amount, -20.0) - self.assertEqual(mo.workorder_ids.wc_analytic_account_line_id.account_id, wc_analytic_account) + self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids.amount, -20.0) + self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids.account_id, self.analytic_account) + self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids.amount, -20.0) + self.assertEqual(mo.workorder_ids.wc_analytic_account_line_ids.account_id, wc_analytic_account) def test_changing_mo_analytic_account(self): """ Check if the MO account analytic lines are correctly updated @@ -220,46 +176,46 @@ class TestAnalyticAccount(TestMrpAnalyticAccount): mo_form.product_id = self.product mo_form.bom_id = self.bom mo_form.product_qty = 1 - mo_form.analytic_account_id = self.analytic_account + mo_form.analytic_distribution = {str(self.analytic_account.id): 100.0} mo = mo_form.save() mo.action_confirm() self.assertEqual(mo.state, 'confirmed') - self.assertEqual(len(mo.move_raw_ids.analytic_account_line_id), 0) - self.assertEqual(len(mo.workorder_ids.mo_analytic_account_line_id), 0) + self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 0) + self.assertEqual(len(mo.workorder_ids.mo_analytic_account_line_ids), 0) # Change duration to 60 mo_form = Form(mo) with mo_form.workorder_ids.edit(0) as line_edit: line_edit.duration = 60.0 mo_form.save() - self.assertEqual(mo.workorder_ids.mo_analytic_account_line_id.account_id, self.analytic_account) + self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids.account_id, self.analytic_account) # Mark as done mo.button_mark_done() self.assertEqual(mo.state, 'done') - self.assertEqual(len(mo.move_raw_ids.analytic_account_line_id), 1) + self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 1) # Create a new analytic account analytic_plan = self.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False}) new_analytic_account = self.env['account.analytic.account'].create({'name': 'test_analytic_account_2', 'plan_id': analytic_plan.id}) # Change the MO analytic account - mo.analytic_account_id = new_analytic_account - self.assertEqual(mo.move_raw_ids.analytic_account_line_id.account_id.id, new_analytic_account.id) - self.assertEqual(mo.workorder_ids.mo_analytic_account_line_id.account_id.id, new_analytic_account.id) + mo.analytic_distribution = {str(new_analytic_account.id): 100.0} + self.assertEqual(mo.move_raw_ids.analytic_account_line_ids.account_id.id, new_analytic_account.id) + self.assertEqual(mo.workorder_ids.mo_analytic_account_line_ids.account_id.id, new_analytic_account.id) #Get the MO analytic account lines - mo_analytic_account_raw_lines = mo.move_raw_ids.analytic_account_line_id - mo_analytic_account_wc_lines = mo.workorder_ids.mo_analytic_account_line_id - mo.analytic_account_id = False + mo_analytic_account_raw_lines = mo.move_raw_ids.analytic_account_line_ids + mo_analytic_account_wc_lines = mo.move_raw_ids.analytic_account_line_ids + mo.analytic_distribution = {} # Check that the MO analytic account lines are deleted - self.assertEqual(len(mo.move_raw_ids.analytic_account_line_id), 0) - self.assertEqual(len(mo.workorder_ids.mo_analytic_account_line_id), 0) + self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 0) + self.assertEqual(len(mo.workorder_ids.mo_analytic_account_line_ids), 0) self.assertFalse(mo_analytic_account_raw_lines.exists()) self.assertFalse(mo_analytic_account_wc_lines.exists()) # Check that the AA lines are recreated correctly if we delete the AA, save the MO, and assign a new one - mo.analytic_account_id = self.analytic_account - self.assertEqual(len(mo.move_raw_ids.analytic_account_line_id), 1) - self.assertEqual(len(mo.workorder_ids.mo_analytic_account_line_id), 1) + mo.analytic_distribution = {str(self.analytic_account.id): 100.0} + self.assertEqual(len(mo.move_raw_ids.analytic_account_line_ids), 1) + self.assertEqual(len(mo.workorder_ids.mo_analytic_account_line_ids), 1) def test_add_remove_wo_analytic_no_company(self): """Test the addition and removal of work orders to a MO linked to @@ -275,7 +231,7 @@ class TestAnalyticAccount(TestMrpAnalyticAccount): # Create a mo linked to an analytic account with no associated company mo_no_company = self.env['mrp.production'].create({ 'product_id': self.product.id, - 'analytic_account_id': analytic_account_no_company.id, + 'analytic_distribution': {str(analytic_account_no_company.id): 100.0}, 'product_uom_id': self.bom.product_uom_id.id, }) @@ -289,7 +245,7 @@ class TestAnalyticAccount(TestMrpAnalyticAccount): }) mo_no_c_form.save() self.assertTrue(mo_no_company.workorder_ids) - self.assertEqual(wo.production_id.analytic_account_id, analytic_account_no_company) + self.assertEqual(wo.production_id.analytic_account_ids, analytic_account_no_company) self.assertEqual(len(analytic_account_no_company.line_ids), 1) mo_no_company.workorder_ids.unlink() self.assertEqual(len(analytic_account_no_company.line_ids), 0) diff --git a/addons/mrp_account/views/mrp_bom_views.xml b/addons/mrp_account/views/mrp_bom_views.xml index 5e42b78483e..24ef39867d8 100644 --- a/addons/mrp_account/views/mrp_bom_views.xml +++ b/addons/mrp_account/views/mrp_bom_views.xml @@ -6,7 +6,9 @@ - + diff --git a/addons/mrp_account/views/mrp_production_views.xml b/addons/mrp_account/views/mrp_production_views.xml index 4b68de29983..381c88e2678 100644 --- a/addons/mrp_account/views/mrp_production_views.xml +++ b/addons/mrp_account/views/mrp_production_views.xml @@ -5,7 +5,10 @@ - + @@ -29,9 +32,9 @@