From 8bf6bee1538337f5de26a6814b86bcc0ab946db3 Mon Sep 17 00:00:00 2001 From: gawa-odoo Date: Tue, 25 Oct 2022 15:12:56 +0000 Subject: [PATCH] [IMP] analytic, *: block validation with mandatory plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * = {account, sale, purchase, hr_expense} When a plan is mandatory for SOL, POL or expenses, the flow should still be blocked when pressing the button, and not only when creating analytic lines. We add the info in the context of the button, so automatic flows are still not blocked. When the applicability rule requires a mandatory analytic distribution, raise an error upon : - confirming PO, - sending or confirming SO - approving Expense Report by manager. t-3040929 closes odoo/odoo#107166 X-original-commit: 8623a7850288828759db125b1054b0fed6b6b135 Signed-off-by: William André (wan) --- addons/account/models/account_move_line.py | 23 +++++-------------- addons/analytic/models/analytic_mixin.py | 21 ++++++++++++++--- .../analytic/tests/test_analytic_account.py | 2 +- addons/hr_expense/models/hr_expense.py | 9 ++++++++ addons/hr_expense/views/hr_expense_views.xml | 1 + addons/purchase/models/purchase.py | 9 ++++++++ addons/purchase/views/purchase_views.xml | 4 ++-- addons/sale/models/sale_order.py | 3 +++ addons/sale/models/sale_order_line.py | 8 +++++++ addons/sale/views/sale_order_views.xml | 12 +++++----- 10 files changed, 63 insertions(+), 29 deletions(-) diff --git a/addons/account/models/account_move_line.py b/addons/account/models/account_move_line.py index 64c9eef2044..9e5cd312f03 100644 --- a/addons/account/models/account_move_line.py +++ b/addons/account/models/account_move_line.py @@ -6,7 +6,7 @@ from functools import lru_cache from odoo import api, fields, models, Command, _ from odoo.exceptions import ValidationError, UserError -from odoo.tools import frozendict, formatLang, format_date, float_is_zero, float_compare +from odoo.tools import frozendict, formatLang, format_date, float_is_zero from odoo.tools.sql import create_index from odoo.addons.web.controllers.utils import clean_action @@ -2352,32 +2352,21 @@ class AccountMoveLine(models.Model): # ANALYTIC # ------------------------------------------------------------------------- - def _validate_distribution(self): + def _validate_analytic_distribution(self): for line in self.filtered(lambda line: line.display_type == 'product'): - mandatory_plans_ids = [plan['id'] for plan in self.env['account.analytic.plan'].sudo().get_relevant_plans(**{ + line._validate_distribution(**{ 'product': line.product_id.id, 'account': line.account_id.id, 'business_domain': line.move_id.move_type in ['out_invoice', 'out_refund', 'out_receipt'] and 'invoice' or line.move_id.move_type in ['in_invoice', 'in_refund', 'in_receipt'] and 'bill' or 'general', - 'company_id': self.company_id.id, - }) if plan['applicability'] == 'mandatory'] - if not mandatory_plans_ids: - continue - distribution_by_root_plan = {} - for analytic_account_id, percentage in (line.analytic_distribution or {}).items(): - root_plan = self.env['account.analytic.account'].browse(int(analytic_account_id)).root_plan_id - distribution_by_root_plan[root_plan.id] = distribution_by_root_plan.get(root_plan.id, 0) + percentage - - for plan_id in mandatory_plans_ids: - if float_compare(distribution_by_root_plan.get(plan_id, 0), 100, precision_digits=2) != 0: - raise ValidationError(_("One or more lines require a 100% analytic distribution.")) + 'company_id': line.company_id.id, + }) def _create_analytic_lines(self): """ Create analytic items upon validation of an account.move.line having an analytic distribution. """ - if self.env.context.get('validate_analytic', False): - self._validate_distribution() + self._validate_analytic_distribution() analytic_line_vals = [] for line in self: analytic_line_vals.extend(line._prepare_analytic_lines()) diff --git a/addons/analytic/models/analytic_mixin.py b/addons/analytic/models/analytic_mixin.py index abea3663027..7247d93b89e 100644 --- a/addons/analytic/models/analytic_mixin.py +++ b/addons/analytic/models/analytic_mixin.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. -from odoo import models, fields, api -from odoo.tools.float_utils import float_round - +from odoo import models, fields, api, _ +from odoo.tools.float_utils import float_round, float_compare +from odoo.exceptions import ValidationError class AnalyticMixin(models.AbstractModel): _name = 'analytic.mixin' @@ -47,6 +47,21 @@ class AnalyticMixin(models.AbstractModel): vals_list = [self._sanitize_values(vals, decimal_precision) for vals in vals_list] return super().create(vals_list) + def _validate_distribution(self, **kwargs): + if self.env.context.get('validate_analytic', False): + mandatory_plans_ids = [plan['id'] for plan in self.env['account.analytic.plan'].sudo().get_relevant_plans(**kwargs) if plan['applicability'] == 'mandatory'] + if not mandatory_plans_ids: + return + decimal_precision = self.env['decimal.precision'].precision_get('Percentage Analytic') + distribution_by_root_plan = {} + for analytic_account_id, percentage in (self.analytic_distribution or {}).items(): + root_plan = self.env['account.analytic.account'].browse(int(analytic_account_id)).root_plan_id + distribution_by_root_plan[root_plan.id] = distribution_by_root_plan.get(root_plan.id, 0) + percentage + + for plan_id in mandatory_plans_ids: + if float_compare(distribution_by_root_plan.get(plan_id, 0), 100, precision_digits=decimal_precision) != 0: + raise ValidationError(_("One or more lines require a 100% analytic distribution.")) + def _sanitize_values(self, vals, decimal_precision): """ Normalize the float of the distribution """ if 'analytic_distribution' in vals: diff --git a/addons/analytic/tests/test_analytic_account.py b/addons/analytic/tests/test_analytic_account.py index dbb15093f5e..4dfd0cfda4a 100644 --- a/addons/analytic/tests/test_analytic_account.py +++ b/addons/analytic/tests/test_analytic_account.py @@ -100,7 +100,7 @@ class TestAnalyticAccount(TransactionCase): plans_json = self.env['account.analytic.plan'].get_relevant_plans(**kwargs) self.assertEqual(1, len(plans_json), "Plan 1 should be unavailable") - kwargs = {'business_domain': 'purchase'} + kwargs = {'business_domain': 'purchase_order'} plans_json = self.env['account.analytic.plan'].get_relevant_plans(**kwargs) self.assertEqual(2, len(plans_json), "Both plans should be available") diff --git a/addons/hr_expense/models/hr_expense.py b/addons/hr_expense/models/hr_expense.py index c9943c38419..53badbf1991 100644 --- a/addons/hr_expense/models/hr_expense.py +++ b/addons/hr_expense/models/hr_expense.py @@ -1154,6 +1154,7 @@ class HrExpenseSheet(models.Model): def approve_expense_sheets(self): self._check_can_approve() + self._validate_analytic_distribution() duplicates = self.expense_line_ids.duplicate_expense_ids.filtered(lambda exp: exp.state in ['approved', 'done']) if duplicates: action = self.env["ir.actions.act_window"]._for_xml_id('hr_expense.hr_expense_approve_duplicate_action') @@ -1161,6 +1162,14 @@ class HrExpenseSheet(models.Model): return action self._do_approve() + def _validate_analytic_distribution(self): + for line in self.expense_line_ids: + line._validate_distribution(**{ + 'account': line.account_id.id, + 'business_domain': 'expense', + 'company_id': line.company_id.id, + }) + def _do_approve(self): self._check_can_approve() diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml index 9088fba41b7..b4029474ca2 100644 --- a/addons/hr_expense/views/hr_expense_views.xml +++ b/addons/hr_expense/views/hr_expense_views.xml @@ -662,6 +662,7 @@ string="Approve" type="object" data-hotkey="v" + context="{'validate_analytic': True}" attrs="{'invisible': ['|', ('can_approve', '=', False), ('state', '!=', 'submit')]}" class="oe_highlight o_expense_sheet_approve"/>