[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:
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
}),
|
||||
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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')
|
||||
|
||||
Reference in New Issue
Block a user