diff --git a/addons/hr_expense/i18n/hr_expense.pot b/addons/hr_expense/i18n/hr_expense.pot
index e4a7a5add31..47ef63fda3b 100644
--- a/addons/hr_expense/i18n/hr_expense.pot
+++ b/addons/hr_expense/i18n/hr_expense.pot
@@ -2015,6 +2015,15 @@ msgstr ""
msgid "The sum of after split shut remain the same"
msgstr ""
+#. module: hr_expense
+#. odoo-python
+#: code:addons/hr_expense/models/product_product.py:0
+#, python-format
+msgid ""
+"There are unposted expenses linked to this category. Updating the category "
+"cost will change expense amounts. Make sure it is what you want to do."
+msgstr ""
+
#. module: hr_expense
#. odoo-python
#: code:addons/hr_expense/models/hr_expense_sheet.py:0
diff --git a/addons/hr_expense/models/__init__.py b/addons/hr_expense/models/__init__.py
index 85fd0506c63..00625929f9f 100644
--- a/addons/hr_expense/models/__init__.py
+++ b/addons/hr_expense/models/__init__.py
@@ -8,6 +8,7 @@ from . import account_tax
from . import hr_department
from . import hr_expense
from . import hr_expense_sheet
+from . import product_product
from . import product_template
from . import res_config_settings
from . import account_journal_dashboard
diff --git a/addons/hr_expense/models/product_product.py b/addons/hr_expense/models/product_product.py
new file mode 100644
index 00000000000..fe628d03520
--- /dev/null
+++ b/addons/hr_expense/models/product_product.py
@@ -0,0 +1,26 @@
+from odoo import api, fields, models, _
+
+
+class ProductProduct(models.Model):
+ _inherit = "product.product"
+
+ standard_price_update_warning = fields.Char(compute="_compute_standard_price_update_warning")
+
+ @api.onchange('standard_price')
+ def _compute_standard_price_update_warning(self):
+ undone_expenses = self.env['hr.expense']._read_group(
+ domain=[('state', 'in', ['draft', 'reported', 'approved']), ('product_id', 'in', self.ids)],
+ groupby=['price_unit'],
+ )
+ # The following list is composed of all the unit_amounts of expenses that use this product and should NOT trigger a warning.
+ # Those are the amounts of any undone expense using this product and 0.0 which is the default unit_amount.
+ unit_amounts_no_warning = [self.env.company.currency_id.round(row[0]) for row in undone_expenses]
+ for product in self:
+ product.standard_price_update_warning = False
+ if undone_expenses:
+ rounded_price = self.env.company.currency_id.round(product.standard_price)
+ if rounded_price and (len(unit_amounts_no_warning) > 1 or (len(unit_amounts_no_warning) == 1 and rounded_price not in unit_amounts_no_warning)):
+ product.standard_price_update_warning = _(
+ "There are unposted expenses linked to this category. Updating the category cost will change expense amounts. "
+ "Make sure it is what you want to do."
+ )
diff --git a/addons/hr_expense/tests/__init__.py b/addons/hr_expense/tests/__init__.py
index 699673b16c3..01f8b5dcd31 100644
--- a/addons/hr_expense/tests/__init__.py
+++ b/addons/hr_expense/tests/__init__.py
@@ -7,3 +7,4 @@ from . import test_expenses_access_rights
from . import test_expenses_mail_import
from . import test_expenses_multi_company
from . import test_expenses_tax
+from . import test_expenses_standard_price_update_warning
diff --git a/addons/hr_expense/tests/test_expenses_standard_price_update_warning.py b/addons/hr_expense/tests/test_expenses_standard_price_update_warning.py
new file mode 100644
index 00000000000..0d08293dc35
--- /dev/null
+++ b/addons/hr_expense/tests/test_expenses_standard_price_update_warning.py
@@ -0,0 +1,56 @@
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo.addons.hr_expense.tests.common import TestExpenseCommon
+from odoo.tests import tagged, Form
+
+
+@tagged('post_install', '-at_install')
+class TestExpenseStandardPriceUpdateWarning(TestExpenseCommon):
+ def test_expense_standard_price_update_warning(self):
+ self.expense_cat_A = self.env['product.product'].create({
+ 'name': 'Category A',
+ 'default_code': 'CA',
+ 'standard_price': 0.0,
+ })
+ self.expense_cat_B = self.env['product.product'].create({
+ 'name': 'Category B',
+ 'default_code': 'CB',
+ 'standard_price': 0.0,
+ })
+ self.expense_cat_C = self.env['product.product'].create({
+ 'name': 'Category C',
+ 'default_code': 'CC',
+ 'standard_price': 0.0,
+ })
+ self.expense_1 = self.env['hr.expense'].create({
+ 'employee_id': self.expense_employee.id,
+ 'name': 'Expense 1',
+ 'product_id': self.expense_cat_A.id,
+ 'total_amount': 1,
+ })
+ self.expense_2 = self.env['hr.expense'].create({
+ 'employee_id': self.expense_employee.id,
+ 'name': 'Expense 2',
+ 'product_id': self.expense_cat_B.id,
+ 'total_amount': 5,
+ })
+
+ # At first, there is no warning message on the categories because their prices are 0
+ self.assertFalse(self.expense_cat_A.standard_price_update_warning)
+ self.assertFalse(self.expense_cat_B.standard_price_update_warning)
+ self.assertFalse(self.expense_cat_C.standard_price_update_warning)
+
+ # When modifying the price of the first category, a message should appear as a an expense will be modified.
+ with Form(self.expense_cat_A, view="hr_expense.product_product_expense_form_view") as form:
+ form.standard_price = 5
+ self.assertTrue(form.standard_price_update_warning)
+
+ # When modifying the price of the second category, no message should appear as the price of the linked
+ # expense is the price of the category that is going to be saved.
+ with Form(self.expense_cat_B, view="hr_expense.product_product_expense_form_view") as form:
+ form.standard_price = 5
+ self.assertFalse(form.standard_price_update_warning)
+
+ # When modifying the price of the thirs category, no message should appear as no expense is linked to it.
+ with Form(self.expense_cat_C, view="hr_expense.product_product_expense_form_view") as form:
+ form.standard_price = 5
+ self.assertFalse(form.standard_price_update_warning)
diff --git a/addons/hr_expense/views/hr_expense_views.xml b/addons/hr_expense/views/hr_expense_views.xml
index 2430fe85dbf..08834baf26e 100644
--- a/addons/hr_expense/views/hr_expense_views.xml
+++ b/addons/hr_expense/views/hr_expense_views.xml
@@ -550,6 +550,9 @@
product.product