[FIX] hr_expense: Fix currency_rate handling

The desired flow of currency rate in expense is as followed:
- (default) Use Odoo currency rate
- Allow the user to set a custom rate (to include fees) when changing
  the total amount in company currency
- Revert to the default behaviour when the currency is changed
  or the amount in foreign currency is changed

This aims to fix how currency rate is computed and overridden
- Reorganize all currency rate computation, so it doesn't revert to Odoo rate
  at every compute call
- Deals with a bug where a "total_amount_currency", when changed
  just before calling "action_submit_expenses" would not trigger
  the computation of unit_amount
- Force save when changing currency on expense form view to prevent a bug
  where the first modification of "total_amount_company" would be canceled
  (due to the new behaviour of currency rate computation)
- Removes unit_amount_display from views as it should be removed
  in later versions and is deprecated since 16.0

task-3476569

closes odoo/odoo#137598

Signed-off-by: Laurent Smet (las) <las@odoo.com>
This commit is contained in:
Julien Alardot (jual)
2023-10-05 15:36:24 +00:00
parent 471d8301b5
commit 11a0f8fab7
6 changed files with 175 additions and 112 deletions
+28
View File
@@ -521,12 +521,26 @@ msgstr ""
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_res_config_settings__hr_expense_alias_prefix
#: model:ir.model.fields,field_description:hr_expense.field_res_config_test__hr_expense_alias_prefix
msgid "Default Alias Name for Expenses"
msgstr ""
#. module: hr_expense
#: model_terms:ir.ui.view,arch_db:hr_expense.res_config_settings_view_form
msgid "Default Category"
msgstr ""
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_res_company__expense_product_id
#: model:ir.model.fields,field_description:hr_expense.field_res_config_settings__expense_product_id
#: model:ir.model.fields,field_description:hr_expense.field_res_config_test__expense_product_id
msgid "Default Expense Category"
msgstr ""
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_res_company__expense_journal_id
#: model:ir.model.fields,field_description:hr_expense.field_res_config_settings__expense_journal_id
#: model:ir.model.fields,field_description:hr_expense.field_res_config_test__expense_journal_id
msgid "Default Expense Journal"
msgstr ""
@@ -1155,6 +1169,7 @@ msgstr ""
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_res_config_settings__hr_expense_use_mailgateway
#: model:ir.model.fields,field_description:hr_expense.field_res_config_test__hr_expense_use_mailgateway
msgid "Let your employees record expenses by email"
msgstr ""
@@ -1477,6 +1492,7 @@ msgstr ""
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_res_company__company_expense_allowed_payment_method_line_ids
#: model:ir.model.fields,field_description:hr_expense.field_res_config_settings__company_expense_allowed_payment_method_line_ids
#: model:ir.model.fields,field_description:hr_expense.field_res_config_test__company_expense_allowed_payment_method_line_ids
msgid "Payment methods available for expenses paid by company"
msgstr ""
@@ -1641,6 +1657,7 @@ msgstr ""
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_res_config_settings__module_hr_payroll_expense
#: model:ir.model.fields,field_description:hr_expense.field_res_config_test__module_hr_payroll_expense
msgid "Reimburse Expenses in Payslip"
msgstr ""
@@ -1745,6 +1762,7 @@ msgstr ""
#. module: hr_expense
#: model:ir.model.fields,field_description:hr_expense.field_res_config_settings__module_hr_expense_extract
#: model:ir.model.fields,field_description:hr_expense.field_res_config_test__module_hr_expense_extract
msgid "Send bills to OCR to generate expenses"
msgstr ""
@@ -1907,6 +1925,7 @@ msgstr ""
#. module: hr_expense
#: model:ir.model.fields,help:hr_expense.field_res_company__expense_journal_id
#: model:ir.model.fields,help:hr_expense.field_res_config_settings__expense_journal_id
#: model:ir.model.fields,help:hr_expense.field_res_config_test__expense_journal_id
msgid ""
"The company's default journal used when an employee expense is created."
msgstr ""
@@ -1960,6 +1979,15 @@ msgstr ""
msgid "The sum of after split shut remain the same"
msgstr ""
#. module: hr_expense
#. odoo-python
#: code:addons/hr_expense/models/hr_expense.py:0
#, python-format
msgid ""
"The work email of the employee is required to post the expense report. "
"Please add it on the employee form."
msgstr ""
#. module: hr_expense
#. odoo-javascript
#: code:addons/hr_expense/static/src/js/tours/hr_expense.js:0
+42 -22
View File
@@ -28,7 +28,7 @@ class HrExpense(models.Model):
name = fields.Char('Description', compute='_compute_name', readonly=False, store=True, precompute=True, required=True, copy=True)
date = fields.Date(default=fields.Date.context_today, string="Expense Date")
accounting_date = fields.Date(string="Accounting Date", related='sheet_id.accounting_date', store=True, groups='account.group_account_invoice,account.group_account_readonly')
employee_id = fields.Many2one('hr.employee', compute='_compute_employee_id', string="Employee",
employee_id = fields.Many2one('hr.employee', compute='_compute_employee_id', string="Employee", precompute=True,
store=True, required=True, readonly=False, tracking=True,
default=_default_employee_id, domain=[('filter_for_expense', '=', True)], check_company=True)
# product_id not required to allow create an expense without product via mail alias, but should be required on the view.
@@ -54,15 +54,15 @@ class HrExpense(models.Model):
check_company=True,
domain="[('type_tax_use', '=', 'purchase')]", string='Included taxes',
help="Both price-included and price-excluded taxes will behave as price-included taxes for expenses.")
amount_tax = fields.Monetary(string='Tax amount in Currency', help="Tax amount in currency", compute='_compute_amount_tax', store=True, currency_field='currency_id')
amount_tax_company = fields.Monetary('Tax amount', help="Tax amount in company currency", compute='_compute_total_amount_company', store=True, currency_field='company_currency_id')
total_amount = fields.Monetary("Total In Currency", compute='_compute_amount', store=True, currency_field='currency_id', tracking=True, readonly=False, inverse='_inverse_total_amount')
untaxed_amount = fields.Monetary("Total Untaxed Amount In Currency", compute='_compute_amount_tax', store=True, currency_field='currency_id')
amount_tax = fields.Monetary(string='Tax amount in Currency', help="Tax amount in currency", precompute=True, compute='_compute_amount_tax', store=True, currency_field='currency_id')
amount_tax_company = fields.Monetary('Tax amount', help="Tax amount in company currency", precompute=True, compute='_compute_total_amount_company', store=True, currency_field='company_currency_id')
total_amount = fields.Monetary("Total In Currency", precompute=True, compute='_compute_amount', store=True, currency_field='currency_id', tracking=True, readonly=False, inverse='_inverse_total_amount')
untaxed_amount = fields.Monetary("Total Untaxed Amount In Currency", precompute=True, compute='_compute_amount_tax', store=True, currency_field='currency_id')
company_currency_id = fields.Many2one('res.currency', string="Report Company Currency", related='company_id.currency_id', readonly=True)
total_amount_company = fields.Monetary('Total', tracking=True,
total_amount_company = fields.Monetary('Total', tracking=True, precompute=True,
compute='_compute_total_amount_company', inverse='_inverse_total_amount_company', store=True, currency_field='company_currency_id', readonly=False)
company_id = fields.Many2one('res.company', string='Company', required=True, default=lambda self: self.env.company)
currency_id = fields.Many2one('res.currency', string='Currency', required=True, store=True, readonly=False,
currency_id = fields.Many2one('res.currency', precompute=True, string='Currency', required=True, store=True, readonly=False,
compute='_compute_currency_id', default=lambda self: self.env.company.currency_id)
currency_rate = fields.Float(compute='_compute_currency_rate', tracking=True)
account_id = fields.Many2one(
@@ -121,10 +121,34 @@ class HrExpense(models.Model):
if not self.product_has_cost:
self.quantity = 1
@api.depends('total_amount_company')
@api.depends('currency_id', 'total_amount', 'date')
def _compute_currency_rate(self):
"""
We want the default odoo rate when the following change:
- the currency of the expense
- the total amount in foreign currency
- the date of the expense
this will cause the rate to be recomputed twice with possible changes but we don't have the required fields
to store the override state in stable
"""
date_today = fields.Date.context_today(self)
for expense in self:
expense.currency_rate = expense.total_amount_company / expense.total_amount if expense.total_amount else 1.0
if not expense.same_currency:
if (
expense.currency_id != expense._origin.currency_id
or expense.total_amount != expense._origin.total_amount
or expense.date != expense._origin.date
):
expense.currency_rate = self.env['res.currency']._get_conversion_rate(
from_currency=expense.currency_id,
to_currency=expense.company_currency_id,
company=expense.company_id,
date=expense.date or date_today,
)
else:
expense.currency_rate = expense.total_amount_company / expense.total_amount if expense.total_amount else 1.0
else:
expense.currency_rate = 1.0
@api.depends('currency_id', 'company_currency_id')
def _compute_same_currency(self):
@@ -201,19 +225,9 @@ class HrExpense(models.Model):
'quantity',
)
def _compute_total_amount_company(self):
date_today = fields.Date.context_today(self)
for expense in self:
if expense.company_id and expense.currency_id != expense.company_currency_id:
currency_rate = self.env['res.currency']._get_conversion_rate(
from_currency=expense.currency_id,
to_currency=expense.company_currency_id,
company=expense.company_id,
date=expense.date or date_today,
)
else:
currency_rate = 1.0
base_lines = [expense._convert_to_tax_base_line_dict(
price_unit=expense.total_amount * currency_rate,
price_unit=expense.total_amount * expense.currency_rate,
currency=expense.company_currency_id,
)]
taxes_totals = self.env['account.tax']._compute_taxes(base_lines)['totals'][expense.company_currency_id]
@@ -221,14 +235,19 @@ class HrExpense(models.Model):
expense.amount_tax_company = taxes_totals['amount_tax']
def _inverse_total_amount_company(self):
# Allows to set a custom rate on the expense
for expense in self:
if expense.same_currency:
expense.total_amount = expense.total_amount_company
base_lines = [expense._convert_to_tax_base_line_dict(
price_unit=expense.total_amount_company,
currency=expense.company_currency_id,
)]
taxes_totals = self.env['account.tax']._compute_taxes(base_lines)['totals'][expense.company_currency_id]
expense.amount_tax_company = taxes_totals['amount_tax']
expense.currency_rate = expense.total_amount_company / expense.total_amount if expense.total_amount else 1.0
expense.unit_amount = expense.total_amount_company / expense.quantity if expense.quantity else expense.total_amount_company
@api.depends('currency_rate')
def _compute_label_convert_rate(self):
@@ -275,8 +294,9 @@ class HrExpense(models.Model):
for expense in self:
expense.product_description = not is_html_empty(expense.product_id.description) and expense.product_id.description
@api.depends('unit_amount', 'total_amount_company', 'product_has_cost')
@api.depends('unit_amount')
def _compute_unit_amount_display(self):
# DEPRECATED
for expense in self:
expense.unit_amount_display = expense.unit_amount if expense.product_has_cost else expense.total_amount_company
@@ -641,7 +661,7 @@ class HrExpense(models.Model):
tax_data = self.env['account.tax']._compute_taxes([
self._convert_to_tax_base_line_dict(price_unit=self.total_amount, currency=self.currency_id)
])
rate = abs(self.total_amount / self.total_amount_company)
rate = abs(self.total_amount / self.total_amount_company) if self.total_amount_company else 1.0
base_line_data, to_update = tax_data['base_lines_to_update'][0] # Add base line
amount_currency = to_update['price_subtotal']
expense_name = self.name.split("\n")[0][:64]
+61 -59
View File
@@ -25,15 +25,15 @@ class TestExpenses(TestExpenseCommon):
expense1 = self.env['hr.expense'].create({
'name': 'Dinner with client - Expenses',
'employee_id': self.expense_employee.id,
'product_id': self.product_a.id,
'unit_amount': 350.00,
'product_id': self.product_c.id,
'total_amount': 350.00,
})
expense2 = self.env['hr.expense'].create({
'name': 'Team building at Huy',
'employee_id': employee.id,
'product_id': self.product_a.id,
'unit_amount': 2500.00,
'product_id': self.product_c.id,
'total_amount': 2500.00,
})
expense_sheet = self.env['hr.expense.sheet'].create({
@@ -68,8 +68,9 @@ class TestExpenses(TestExpenseCommon):
'expense_line_ids': [(0, 0, {
'name': 'Car Travel Expenses',
'employee_id': self.expense_employee.id,
'product_id': self.product_a.id,
'unit_amount': 350.00,
'product_id': self.product_c.id,
'total_amount': 350.00,
'tax_ids': [Command.set(self.tax_purchase_a.ids)],
})]
})
@@ -148,8 +149,8 @@ class TestExpenses(TestExpenseCommon):
'expense_line_ids': [(0, 0, {
'name': 'Car Travel Expenses',
'employee_id': self.expense_employee.id,
'product_id': self.product_a.id,
'unit_amount': 350.00,
'product_id': self.product_c.id,
'total_amount': 350.00,
'payment_mode': 'company_account',
})]
})
@@ -182,7 +183,7 @@ class TestExpenses(TestExpenseCommon):
'name': 'expense_company_currency',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 1000.0,
'quantity': 1.25,
'tax_ids': [(6, 0, self.company_data['default_tax_purchase'].ids)],
'analytic_distribution': {self.analytic_account_1.id: 100},
'employee_id': self.expense_employee.id,
@@ -255,7 +256,7 @@ class TestExpenses(TestExpenseCommon):
},
# Product line (company currency):
{
'debit': 869.57, # 1000 * 1:1 (rate) / 1.15 (incl. tax)
'debit': 869.57, # 1000 / 1.15 (incl. tax)
'credit': 0.0,
'amount_currency': 869.57,
'account_id': self.company_data['default_account_expense'].id,
@@ -602,8 +603,8 @@ class TestExpenses(TestExpenseCommon):
self.env['hr.expense'].create({
'name': 'Car Travel Expenses',
'employee_id': self.expense_employee.id,
'product_id': self.product_a.id,
'unit_amount': 350.00,
'product_id': self.product_c.id,
'total_amount': 350.00,
'tax_ids': [(6, 0, [self.tax_purchase_a.id])],
'sheet_id': expense.id,
'analytic_distribution': {str(self.analytic_account_1.id): 100},
@@ -642,16 +643,16 @@ class TestExpenses(TestExpenseCommon):
(0, 0, {
'name': 'expense_1',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 10.0,
'product_id': self.product_c.id,
'total_amount': 10.0,
'employee_id': self.expense_employee.id,
'tax_ids': tax
}),
(0, 0, {
'name': 'expense_2',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 1.0,
'product_id': self.product_c.id,
'total_amount': 1.0,
'employee_id': self.expense_employee.id,
'tax_ids': tax
}),
@@ -677,8 +678,8 @@ class TestExpenses(TestExpenseCommon):
(0, 0, {
'name': 'expense_1',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 10.0,
'product_id': self.product_c.id,
'total_amount': 10.0,
'employee_id': self.expense_employee.id
}),
],
@@ -744,8 +745,9 @@ class TestExpenses(TestExpenseCommon):
'name': 'expense_1',
'date': '2022-01-05',
'account_id': account_expense_1.id,
'product_id': self.product_a.id,
'unit_amount': 115.0,
'product_id': self.product_c.id,
'total_amount': 115.0,
'tax_ids': [Command.set(self.tax_purchase_a.ids)],
'employee_id': self.expense_employee.id,
}),
Command.create({
@@ -753,8 +755,9 @@ class TestExpenses(TestExpenseCommon):
'name': 'expense_2',
'date': '2022-01-08',
'account_id': account_expense_2.id,
'product_id': self.product_a.id,
'unit_amount': 230.0,
'product_id': self.product_c.id,
'total_amount': 230.0,
'tax_ids': [Command.set(self.tax_purchase_a.ids)],
'employee_id': self.expense_employee.id,
}),
],
@@ -807,16 +810,16 @@ class TestExpenses(TestExpenseCommon):
# Expense on Expense Account 1
'name': 'expense_1',
'date': '2022-01-05',
'product_id': self.product_a.id,
'unit_amount': 115.0,
'product_id': self.product_c.id,
'total_amount': 115.0,
'employee_id': self.expense_employee.id,
}),
Command.create({
# Expense on Expense Account 2
'name': 'expense_2',
'date': '2022-01-08',
'product_id': self.product_a.id,
'unit_amount': 230.0,
'product_id': self.product_c.id,
'total_amount': 230.0,
'employee_id': self.expense_employee.id,
}),
],
@@ -844,15 +847,15 @@ class TestExpenses(TestExpenseCommon):
(0, 0, {
'name': 'expense_1',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 10.0,
'product_id': self.product_c.id,
'total_amount': 10.0,
'employee_id': self.expense_employee.id,
}),
(0, 0, {
'name': 'expense_2',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 1.0,
'product_id': self.product_c.id,
'total_amount': 1.0,
'employee_id': self.expense_employee.id,
}),
],
@@ -1008,8 +1011,8 @@ class TestExpenses(TestExpenseCommon):
expense = self.env['hr.expense'].create({
'name': 'Choucroute Saucisse',
'employee_id': self.expense_employee.id,
'product_id': self.product_a.id,
'unit_amount': 700.00,
'product_id': self.product_c.id,
'total_amount': 700.00,
'sheet_id': expense.id,
'analytic_distribution': {
self.analytic_account_1.id: 50,
@@ -1034,8 +1037,8 @@ class TestExpenses(TestExpenseCommon):
'expense_line_ids': [Command.create({
'name': 'Car Travel Expenses',
'employee_id': self.expense_employee.id,
'product_id': self.product_a.id,
'unit_amount': 350.00,
'product_id': self.product_c.id,
'total_amount': 350.00,
'date': '2021-01-01',
})],
})
@@ -1054,7 +1057,6 @@ class TestExpenses(TestExpenseCommon):
'employee_id': self.expense_employee.id,
'product_id': self.product_c.id,
'total_amount': 60,
'unit_amount': 0,
'tax_ids': [self.tax_purchase_a.id, self.tax_purchase_b.id],
'analytic_distribution': {
self.analytic_account_1.id: 50,
@@ -1095,16 +1097,16 @@ class TestExpenses(TestExpenseCommon):
Command.create({
'name': 'expense_1',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 10.0,
'product_id': self.product_c.id,
'total_amount': 10.0,
'payment_mode': 'company_account',
'employee_id': self.expense_employee.id
}),
Command.create({
'name': 'expense_2',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 10.0,
'product_id': self.product_c.id,
'total_amount': 10.0,
'payment_mode': 'company_account',
'employee_id': self.expense_employee.id
}),
@@ -1119,16 +1121,16 @@ class TestExpenses(TestExpenseCommon):
Command.create({
'name': 'expense_3',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 4.0,
'product_id': self.product_c.id,
'total_amount': 4.0,
'payment_mode': 'own_account',
'employee_id': self.expense_employee.id
}),
Command.create({
'name': 'expense_4',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 8.0,
'product_id': self.product_c.id,
'total_amount': 8.0,
'payment_mode': 'own_account',
'employee_id': self.expense_employee.id
}),
@@ -1174,16 +1176,16 @@ class TestExpenses(TestExpenseCommon):
Command.create({
'name': 'expense_1',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 10.0,
'product_id': self.product_c.id,
'total_amount': 10.0,
'payment_mode': 'company_account',
'employee_id': self.expense_employee.id
}),
Command.create({
'name': 'expense_2',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 10.0,
'product_id': self.product_c.id,
'total_amount': 10.0,
'payment_mode': 'company_account',
'employee_id': self.expense_employee.id
}),
@@ -1198,16 +1200,16 @@ class TestExpenses(TestExpenseCommon):
Command.create({
'name': 'expense_3',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 4.0,
'product_id': self.product_c.id,
'total_amount': 4.0,
'payment_mode': 'own_account',
'employee_id': self.expense_employee.id
}),
Command.create({
'name': 'expense_4',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 8.0,
'product_id': self.product_c.id,
'total_amount': 8.0,
'payment_mode': 'own_account',
'employee_id': self.expense_employee.id
}),
@@ -1230,16 +1232,16 @@ class TestExpenses(TestExpenseCommon):
Command.create({
'name': 'expense_1',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 10.0,
'product_id': self.product_c.id,
'total_amount': 10.0,
'payment_mode': 'company_account',
'employee_id': self.expense_employee.id
}),
Command.create({
'name': 'expense_2',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 10.0,
'product_id': self.product_c.id,
'total_amount': 10.0,
'payment_mode': 'company_account',
'employee_id': self.expense_employee.id
}),
@@ -1254,16 +1256,16 @@ class TestExpenses(TestExpenseCommon):
Command.create({
'name': 'expense_3',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 4.0,
'product_id': self.product_c.id,
'total_amount': 4.0,
'payment_mode': 'own_account',
'employee_id': self.expense_employee.id
}),
Command.create({
'name': 'expense_4',
'date': '2016-01-01',
'product_id': self.product_a.id,
'unit_amount': 8.0,
'product_id': self.product_c.id,
'total_amount': 8.0,
'payment_mode': 'own_account',
'employee_id': self.expense_employee.id
}),
+39 -26
View File
@@ -45,34 +45,42 @@
<field name="model">hr.expense</field>
<field name="arch" type="xml">
<tree string="Expenses" multi_edit="1" sample="1" js_class="hr_expense_tree" decoration-info="state == 'draft'">
<field name="company_id" column_invisible="True" readonly="state not in ['draft', 'refused']"/>
<field name="company_id" column_invisible="True"/>
<field name="company_currency_id" column_invisible="True"/>
<field name="attachment_number" column_invisible="True"/>
<field name="is_editable" column_invisible="True"/>
<field name="sheet_is_editable" column_invisible="True"/>
<field name="date" optional="show" readonly="state == 'done'"/>
<field name="product_id" optional="hide" readonly="state == 'done'"/>
<field name="name" readonly="state == 'done'"/>
<field name="employee_id" widget="many2one_avatar_user" readonly="state in ['approved', 'done']"/>
<field name="product_id" optional="hide" readonly="not sheet_is_editable"/>
<field name="name" readonly="not sheet_is_editable"/>
<field name="employee_id" widget="many2one_avatar_user" readonly="not sheet_is_editable"/>
<field name="sheet_id" optional="show" readonly="1" column_invisible="not context.get('show_report', False)"/>
<field name="payment_mode" optional="show" readonly="state in ['approved', 'done', 'reported']"/>
<field name="payment_mode" optional="show" readonly="not sheet_is_editable"/>
<field name="activity_ids" widget="list_activity" optional="show"/>
<field name="accounting_date" optional="hide" groups="account.group_account_invoice,account.group_account_readonly" readonly="0"/>
<field name="reference" optional="hide" groups="account.group_account_readonly"/>
<field name="accounting_date" optional="hide" groups="account.group_account_invoice,account.group_account_readonly"
readonly="not sheet_is_editable"/>
<field name="reference" optional="hide" groups="account.group_account_readonly" readonly="not sheet_is_editable"/>
<field name="analytic_distribution" widget="analytic_distribution"
optional="show"
groups="analytic.group_analytic_accounting"
readonly="not sheet_is_editable"
options="{'product_field': 'product_id', 'business_domain': 'expense'}"/>
<field name="account_id" optional="hide" groups="account.group_account_readonly"/>
<field name="account_id" optional="hide" groups="account.group_account_readonly"
readonly="not sheet_is_editable"/>
<field name="company_id" optional="show" groups="base.group_multi_company" readonly="1"/>
<field name="unit_amount_display" string="Unit Price" optional="hide" widget="monetary" options="{'currency_field': 'company_currency_id'}"/>
<field name="quantity" optional="hide" readonly="state == 'done'"/>
<field name="tax_ids" optional="hide" widget="many2many_tags" groups="account.group_account_invoice,account.group_account_readonly"/>
<field name="unit_amount" string="Unit Price" optional="hide" widget="monetary" readonly="True"
options="{'currency_field': 'company_currency_id'}"/>
<field name="quantity" optional="hide" readonly="not sheet_is_editable"/>
<field name="tax_ids" optional="hide" widget="many2many_tags"
groups="account.group_account_invoice,account.group_account_readonly"
readonly="not sheet_is_editable"/>
<field name="amount_tax_company" sum="Total Taxes"
optional="hide" groups="account.group_account_invoice,account.group_account_readonly"/>
<field name="attachment_number" widget="attachment_number" nolabel="1"/>
<field name="total_amount_company" sum="Total Amount" widget='monetary'
<field name="total_amount_company" sum="Total Amount" widget='monetary' readonly="not sheet_is_editable"
options="{'currency_field': 'company_currency_id'}" decoration-bf="1"/>
<field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}" optional="hide" decoration-bf="1" groups="base.group_multi_currency"/>
<field name="currency_id" optional="hide" groups="base.group_multi_currency" readonly="state in ['approved', 'done', 'reported']"/>
<field name="total_amount" widget='monetary' readonly="not sheet_is_editable" options="{'currency_field': 'currency_id'}" optional="hide" decoration-bf="1" groups="base.group_multi_currency"/>
<field name="currency_id" optional="hide" readonly="True" groups="base.group_multi_currency"/>
<field name="state" optional="show" readonly="1" decoration-info="state in ['draft', 'reported']" decoration-success="state in ['approved', 'done']"
decoration-warning="state == 'submitted'" decoration-danger="state == 'refused'" widget="badge"/>
</tree>
@@ -163,21 +171,24 @@
<div invisible="not product_has_cost">
<div class="o_row">
<field name="quantity" class="oe_inline" readonly="not sheet_is_editable"/>
<field name="product_uom_id" required="1" force_save="1" options="{'no_open': True, 'no_create': True}" groups="uom.group_uom"/>
<field name="product_uom_id" required="1" force_save="1" options="{'no_open': True, 'no_create': True}" groups="uom.group_uom"
readonly="not sheet_is_editable"/>
</div>
</div>
<!-- CASE: product has no cost defined -> user input amount (in other currency if multi-currency) -->
<label for="total_amount" string="Total" invisible="product_has_cost"/>
<div class="o_row" invisible="product_has_cost">
<field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}" readonly="not sheet_is_editable" class="oe_inline"/>
<field name="currency_id" style="max-width: 25%;" groups="base.group_multi_currency" options="{'no_create': True}" readonly="state in ['approved', 'done', 'reported']"/>
<field name="total_amount" widget='monetary' options="{'currency_field': 'currency_id'}" readonly="not sheet_is_editable" class="oe_inline mw-50 me-0"/>
<field name="currency_id" class="mw-25 ms-0" groups="base.group_multi_currency" options="{'no_create': True}"
readonly="not sheet_is_editable"/>
</div>
<!-- CASE: converter when currency is different than the company one -->
<label for="total_amount_company" string="" invisible="same_currency and not product_has_cost"/>
<div class="o_row" invisible="same_currency and not product_has_cost">
<field name="total_amount_company" widget='monetary' options="{'currency_field': 'company_currency_id'}" class="oe_inline"/>
<field name="total_amount_company" widget='monetary' options="{'currency_field': 'company_currency_id'}"
readonly="not sheet_is_editable" class="oe_inline"/>
<field name="label_convert_rate" class="ps-0"/>
</div>
@@ -186,30 +197,32 @@
<field name="tax_ids"
force_save="1"
widget="many2many_tags"
readonly="not is_editable"
readonly="not sheet_is_editable"
options="{'no_create': True}"/>
<field name="amount_tax"/>
</div>
<field name="employee_id" groups="hr_expense.group_hr_expense_team_approver" context="{'default_company_id': company_id}" widget="many2one_avatar_user" options="{'no_create': True}" readonly="state in ['approved', 'done']"/>
<field name="employee_id" groups="hr_expense.group_hr_expense_team_approver"
context="{'default_company_id': company_id}" widget="many2one_avatar_user"
options="{'no_create': True}" readonly="not sheet_is_editable"/>
<label id="lo" for="payment_mode" invisible="product_has_cost"/>
<div id="payment_mode" invisible="product_has_cost">
<field name="payment_mode" widget="radio" readonly="state in ['approved', 'done', 'reported']"/>
<field name="payment_mode" widget="radio" readonly="sheet_id"/>
</div>
</group><group>
<field name="reference" groups="account.group_account_readonly" invisible="product_has_cost" readonly="not is_ref_editable"/>
<field name="date" readonly="not sheet_is_editable"/>
<field name="accounting_date" invisible="not accounting_date or state not in ['approved', 'done']" />
<field name="account_id" options="{'no_create': True}" domain="[('account_type', 'not in', ('asset_receivable','liability_payable','asset_cash','liability_credit_card')), ('company_id', '=', company_id)]" groups="account.group_account_readonly" readonly="not is_editable or not sheet_is_editable" context="{'default_company_id': company_id}"/>
<field name="accounting_date" invisible="not accounting_date or state not in ['approved', 'done']" readonly=" not sheet_is_editable"/>
<field name="account_id" options="{'no_create': True}" domain="[('account_type', 'not in', ('asset_receivable','liability_payable','asset_cash','liability_credit_card')), ('company_id', '=', company_id)]" groups="account.group_account_readonly" readonly="not sheet_is_editable" context="{'default_company_id': company_id}"/>
<field name="sheet_id" invisible="1"/>
<field name="analytic_distribution" widget="analytic_distribution"
groups="analytic.group_analytic_accounting"
options="{'product_field': 'product_id', 'account_field': 'account_id', 'business_domain': 'expense'}"
readonly="not is_editable"/>
<field name="company_id" groups="base.group_multi_company" readonly="state not in ['draft', 'refused']"/>
readonly="not sheet_is_editable"/>
<field name="company_id" groups="base.group_multi_company" readonly="not sheet_is_editable"/>
</group>
</group>
<div>
<field name="description" placeholder="Notes..." readonly="state not in ['draft', 'refused', 'reported']"/>
<field name="description" placeholder="Notes..." readonly="not sheet_is_editable"/>
</div>
</sheet>
<div class="o_attachment_preview o_center_attachment"/>
+3 -3
View File
@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
from odoo import fields, models, api, Command
from odoo.tools import float_compare
@@ -74,11 +74,11 @@ class HrExpenseSplit(models.TransientModel):
'name': self.name,
'product_id': self.product_id.id,
'total_amount': self.total_amount,
'tax_ids': [(6, 0, self.tax_ids.ids)],
'total_amount_company': self.expense_id.currency_id.round(self.expense_id.currency_rate * self.total_amount),
'tax_ids': [Command.set(self.tax_ids.ids)],
'analytic_distribution': self.analytic_distribution,
'employee_id': self.employee_id.id,
'product_uom_id': self.product_id.uom_id.id,
'unit_amount': self.product_id._price_compute('standard_price', currency=self.currency_id)[self.product_id.id]
}
account = self.product_id.product_tmpl_id._get_product_accounts()['expense']
@@ -144,7 +144,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr
)
self.assertDictEqual(
expense_profitability['costs'],
{'id': 'expenses', 'sequence': expense_sequence, 'billed': billed, 'to_bill': 0.0},
{'id': 'expenses', 'sequence': expense_sequence, 'billed': expense.currency_id.round(billed), 'to_bill': 0.0},
)
self.assertDictEqual(
@@ -192,7 +192,7 @@ class TestProjectSaleExpenseProfitability(TestProjectProfitabilityCommon, TestPr
)
self.assertDictEqual(
expense_profitability['costs'],
{'id': 'expenses', 'sequence': expense_sequence, 'billed': billed, 'to_bill': 0.0},
{'id': 'expenses', 'sequence': expense_sequence, 'billed': expense.currency_id.round(billed), 'to_bill': 0.0},
)
expense_sheet._do_refuse('Test Cancel Expense')