summaryrefslogtreecommitdiff
path: root/addons/hr_expense/tests
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/hr_expense/tests
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff)
initial commit 2
Diffstat (limited to 'addons/hr_expense/tests')
-rw-r--r--addons/hr_expense/tests/__init__.py8
-rw-r--r--addons/hr_expense/tests/common.py54
-rw-r--r--addons/hr_expense/tests/test_expenses.py294
-rw-r--r--addons/hr_expense/tests/test_expenses_access_rights.py115
-rw-r--r--addons/hr_expense/tests/test_expenses_mail_import.py153
-rw-r--r--addons/hr_expense/tests/test_expenses_multi_company.py116
6 files changed, 740 insertions, 0 deletions
diff --git a/addons/hr_expense/tests/__init__.py b/addons/hr_expense/tests/__init__.py
new file mode 100644
index 00000000..54cba710
--- /dev/null
+++ b/addons/hr_expense/tests/__init__.py
@@ -0,0 +1,8 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from . import common
+from . import test_expenses
+from . import test_expenses_access_rights
+from . import test_expenses_mail_import
+from . import test_expenses_multi_company
diff --git a/addons/hr_expense/tests/common.py b/addons/hr_expense/tests/common.py
new file mode 100644
index 00000000..406f8b14
--- /dev/null
+++ b/addons/hr_expense/tests/common.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+
+from odoo.addons.account.tests.common import AccountTestInvoicingCommon
+from odoo.addons.mail.tests.common import mail_new_test_user
+
+
+class TestExpenseCommon(AccountTestInvoicingCommon):
+
+ @classmethod
+ def setUpClass(cls, chart_template_ref=None):
+ super().setUpClass(chart_template_ref=chart_template_ref)
+
+ group_expense_manager = cls.env.ref('hr_expense.group_hr_expense_manager')
+
+ cls.expense_user_employee = mail_new_test_user(
+ cls.env,
+ name='expense_user_employee',
+ login='expense_user_employee',
+ email='expense_user_employee@example.com',
+ notification_type='email',
+ groups='base.group_user',
+ company_ids=[(6, 0, cls.env.companies.ids)],
+ )
+ cls.expense_user_manager = mail_new_test_user(
+ cls.env,
+ name='Expense manager',
+ login='expense_manager_1',
+ email='expense_manager_1@example.com',
+ notification_type='email',
+ groups='base.group_user,hr_expense.group_hr_expense_manager',
+ company_ids=[(6, 0, cls.env.companies.ids)],
+ )
+
+ cls.expense_employee = cls.env['hr.employee'].create({
+ 'name': 'expense_employee',
+ 'user_id': cls.expense_user_employee.id,
+ 'address_home_id': cls.expense_user_employee.partner_id.id,
+ 'address_id': cls.expense_user_employee.partner_id.id,
+ })
+
+ # Allow the current accounting user to access the expenses.
+ cls.env.user.groups_id |= group_expense_manager
+
+ # Create analytic account
+ cls.analytic_account_1 = cls.env['account.analytic.account'].create({
+ 'name': 'analytic_account_1',
+ })
+ cls.analytic_account_2 = cls.env['account.analytic.account'].create({
+ 'name': 'analytic_account_2',
+ })
+
+ # Ensure products can be expensed.
+ (cls.product_a + cls.product_b).write({'can_be_expensed': True})
diff --git a/addons/hr_expense/tests/test_expenses.py b/addons/hr_expense/tests/test_expenses.py
new file mode 100644
index 00000000..566e187b
--- /dev/null
+++ b/addons/hr_expense/tests/test_expenses.py
@@ -0,0 +1,294 @@
+# -*- coding: utf-8 -*-
+# 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
+from odoo import fields
+from odoo.exceptions import UserError
+
+
+@tagged('-at_install', 'post_install')
+class TestExpenses(TestExpenseCommon):
+
+ def test_expense_values(self):
+ """ Checking accounting move entries and analytic entries when submitting expense """
+
+ # The expense employee is able to a create an expense sheet.
+ # The total should be 1725.0 because:
+ # - first line: 1000.0 (unit amount) + 150.0 (tax) = 1150.0
+ # - second line: (1500.0 (unit amount) + 225.0 (tax)) * 1/3 (rate) = 575.0.
+
+ expense_sheet = self.env['hr.expense.sheet'].create({
+ 'name': 'First Expense for employee',
+ 'employee_id': self.expense_employee.id,
+ 'journal_id': self.company_data['default_journal_purchase'].id,
+ 'accounting_date': '2017-01-01',
+ 'expense_line_ids': [
+ (0, 0, {
+ # Expense without foreign currency.
+ 'name': 'expense_1',
+ 'date': '2016-01-01',
+ 'product_id': self.product_a.id,
+ 'unit_amount': 1000.0,
+ 'tax_ids': [(6, 0, self.company_data['default_tax_purchase'].ids)],
+ 'analytic_account_id': self.analytic_account_1.id,
+ 'employee_id': self.expense_employee.id,
+ }),
+ (0, 0, {
+ # Expense with foreign currency (rate 1:3).
+ 'name': 'expense_1',
+ 'date': '2016-01-01',
+ 'product_id': self.product_b.id,
+ 'unit_amount': 1500.0,
+ 'tax_ids': [(6, 0, self.company_data['default_tax_purchase'].ids)],
+ 'analytic_account_id': self.analytic_account_2.id,
+ 'currency_id': self.currency_data['currency'].id,
+ 'employee_id': self.expense_employee.id,
+ }),
+ ],
+ })
+
+ # Check expense sheet values.
+ self.assertRecordValues(expense_sheet, [{'state': 'draft', 'total_amount': 1725.0}])
+
+ expense_sheet.action_submit_sheet()
+ expense_sheet.approve_expense_sheets()
+ expense_sheet.action_sheet_move_create()
+
+ # Check expense sheet journal entry values.
+ self.assertRecordValues(expense_sheet.account_move_id.line_ids.sorted('balance'), [
+ # Receivable line (company currency):
+ {
+ 'debit': 0.0,
+ 'credit': 1150.0,
+ 'amount_currency': -1150.0,
+ 'account_id': self.company_data['default_account_payable'].id,
+ 'product_id': False,
+ 'currency_id': self.company_data['currency'].id,
+ 'tax_line_id': False,
+ 'analytic_account_id': False,
+ },
+ # Receivable line (foreign currency):
+ {
+ 'debit': 0.0,
+ 'credit': 862.5,
+ 'amount_currency': -1725.0,
+ 'account_id': self.company_data['default_account_payable'].id,
+ 'product_id': False,
+ 'currency_id': self.currency_data['currency'].id,
+ 'tax_line_id': False,
+ 'analytic_account_id': False,
+ },
+ # Tax line (foreign currency):
+ {
+ 'debit': 112.5,
+ 'credit': 0.0,
+ 'amount_currency': 225.0,
+ 'account_id': self.company_data['default_account_tax_purchase'].id,
+ 'product_id': False,
+ 'currency_id': self.currency_data['currency'].id,
+ 'tax_line_id': self.company_data['default_tax_purchase'].id,
+ 'analytic_account_id': False,
+ },
+ # Tax line (company currency):
+ {
+ 'debit': 150.0,
+ 'credit': 0.0,
+ 'amount_currency': 150.0,
+ 'account_id': self.company_data['default_account_tax_purchase'].id,
+ 'product_id': False,
+ 'currency_id': self.company_data['currency'].id,
+ 'tax_line_id': self.company_data['default_tax_purchase'].id,
+ 'analytic_account_id': False,
+ },
+ # Product line (foreign currency):
+ {
+ 'debit': 750.0,
+ 'credit': 0.0,
+ 'amount_currency': 1500.0,
+ 'account_id': self.company_data['default_account_expense'].id,
+ 'product_id': self.product_b.id,
+ 'currency_id': self.currency_data['currency'].id,
+ 'tax_line_id': False,
+ 'analytic_account_id': self.analytic_account_2.id,
+ },
+ # Product line (company currency):
+ {
+ 'debit': 1000.0,
+ 'credit': 0.0,
+ 'amount_currency': 1000.0,
+ 'account_id': self.company_data['default_account_expense'].id,
+ 'product_id': self.product_a.id,
+ 'currency_id': self.company_data['currency'].id,
+ 'tax_line_id': False,
+ 'analytic_account_id': self.analytic_account_1.id,
+ },
+ ])
+
+ # Check expense analytic lines.
+ self.assertRecordValues(expense_sheet.account_move_id.line_ids.analytic_line_ids.sorted('amount'), [
+ {
+ 'amount': -1000.0,
+ 'date': fields.Date.from_string('2017-01-01'),
+ 'account_id': self.analytic_account_1.id,
+ 'currency_id': self.company_data['currency'].id,
+ },
+ {
+ 'amount': -750.0,
+ 'date': fields.Date.from_string('2017-01-01'),
+ 'account_id': self.analytic_account_2.id,
+ 'currency_id': self.company_data['currency'].id,
+ },
+ ])
+
+ def test_account_entry_multi_currency(self):
+ """ Checking accounting move entries and analytic entries when submitting expense. With
+ multi-currency. And taxes. """
+
+ # Clean-up the rates
+ self.cr.execute("UPDATE res_company SET currency_id = %s WHERE id = %s", [self.env.ref('base.USD').id, self.env.company.id])
+ self.env['res.currency.rate'].search([]).unlink()
+ self.env['res.currency.rate'].create({
+ 'currency_id': self.env.ref('base.EUR').id,
+ 'company_id': self.env.company.id,
+ 'rate': 2.0,
+ 'name': '2010-01-01',
+ })
+
+ expense = self.env['hr.expense.sheet'].create({
+ 'name': 'Expense for Dick Tracy',
+ 'employee_id': self.expense_employee.id,
+ })
+ tax = self.env['account.tax'].create({
+ 'name': 'Expense 10%',
+ 'amount': 10,
+ 'amount_type': 'percent',
+ 'type_tax_use': 'purchase',
+ 'price_include': True,
+ })
+ expense_line = self.env['hr.expense'].create({
+ 'name': 'Choucroute Saucisse',
+ 'employee_id': self.expense_employee.id,
+ 'product_id': self.product_a.id,
+ 'unit_amount': 700.00,
+ 'tax_ids': [(6, 0, tax.ids)],
+ 'sheet_id': expense.id,
+ 'analytic_account_id': self.analytic_account_1.id,
+ 'currency_id': self.env.ref('base.EUR').id,
+ })
+
+ # State should default to draft
+ self.assertEqual(expense.state, 'draft', 'Expense should be created in Draft state')
+ # Submitted to Manager
+ expense.action_submit_sheet()
+ self.assertEqual(expense.state, 'submit', 'Expense is not in Reported state')
+ # Approve
+ expense.approve_expense_sheets()
+ self.assertEqual(expense.state, 'approve', 'Expense is not in Approved state')
+ # Create Expense Entries
+ expense.action_sheet_move_create()
+ self.assertEqual(expense.state, 'post', 'Expense is not in Waiting Payment state')
+ self.assertTrue(expense.account_move_id.id, 'Expense Journal Entry is not created')
+
+ # Should get this result [(0.0, 350.0, -700.0), (318.18, 0.0, 636.36), (31.82, 0.0, 63.64)]
+ for line in expense.account_move_id.line_ids:
+ if line.credit:
+ self.assertAlmostEqual(line.credit, 350.0)
+ self.assertAlmostEqual(line.amount_currency, -700.0)
+ self.assertEqual(len(line.analytic_line_ids), 0, "The credit move line should not have analytic lines")
+ self.assertFalse(line.product_id, "Product of credit move line should be false")
+ else:
+ if not line.tax_line_id == tax:
+ self.assertAlmostEqual(line.debit, 318.18)
+ self.assertAlmostEqual(line.amount_currency, 636.36)
+ self.assertEqual(len(line.analytic_line_ids), 1, "The debit move line should have 1 analytic lines")
+ self.assertEqual(line.product_id, self.product_a, "Product of debit move line should be the one from the expense")
+ else:
+ self.assertEqual(line.tax_base_amount, 318.18)
+ self.assertAlmostEqual(line.debit, 31.82)
+ self.assertAlmostEqual(line.amount_currency, 63.64)
+ self.assertEqual(len(line.analytic_line_ids), 0, "The tax move line should not have analytic lines")
+ self.assertFalse(line.product_id, "Product of tax move line should be false")
+
+ def test_expenses_with_tax_and_lockdate(self):
+ ''' Test creating a journal entry for multiple expenses using taxes. A lock date is set in order to trigger
+ the recomputation of the taxes base amount.
+ '''
+ self.env.company.tax_lock_date = '2020-02-01'
+
+ expense = self.env['hr.expense.sheet'].create({
+ 'name': 'Expense for John Smith',
+ 'employee_id': self.expense_employee.id,
+ 'accounting_date': '2020-01-01'
+ })
+
+ for i in range(2):
+ expense_line = 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,
+ 'tax_ids': [(6, 0, [self.tax_purchase_a.id])],
+ 'sheet_id': expense.id,
+ 'analytic_account_id': self.analytic_account_1.id,
+ })
+ expense_line._onchange_product_id_date_account_id()
+
+ expense.action_submit_sheet()
+ expense.approve_expense_sheets()
+
+ # Assert not "Cannot create unbalanced journal entry" error.
+ expense.action_sheet_move_create()
+
+ def test_reconcile_payment(self):
+ tax = self.env['account.tax'].create({
+ 'name': 'tax abc',
+ 'type_tax_use': 'purchase',
+ 'amount_type': 'percent',
+ 'amount': 15,
+ 'price_include': False,
+ 'include_base_amount': False,
+ 'tax_exigibility': 'on_payment'
+ })
+ current_assets_type = self.env.ref('account.data_account_type_current_assets')
+ company = self.env.company.id
+ tax.cash_basis_transition_account_id = self.env['account.account'].create({
+ 'name': "test",
+ 'code': 999991,
+ 'reconcile': True,
+ 'user_type_id': current_assets_type.id,
+ 'company_id': company,
+ }).id
+
+ sheet = self.env['hr.expense.sheet'].create({
+ 'company_id': company,
+ 'employee_id': self.expense_employee.id,
+ 'name': 'test sheet',
+ 'expense_line_ids': [
+ (0, 0, {
+ 'name': 'expense_1',
+ 'date': '2016-01-01',
+ 'product_id': self.product_a.id,
+ 'unit_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,
+ 'employee_id': self.expense_employee.id,
+ 'tax_ids': tax
+ }),
+ ],
+ })
+
+
+ #actions
+ sheet.action_submit_sheet()
+ sheet.approve_expense_sheets()
+ sheet.action_sheet_move_create()
+ action_data = sheet.action_register_payment()
+ wizard = Form(self.env['account.payment.register'].with_context(action_data['context'])).save()
+ wizard.action_create_payments()
+ self.assertEqual(sheet.state, 'done', 'all account.move.line linked to expenses must be reconciled after payment')
diff --git a/addons/hr_expense/tests/test_expenses_access_rights.py b/addons/hr_expense/tests/test_expenses_access_rights.py
new file mode 100644
index 00000000..01043a73
--- /dev/null
+++ b/addons/hr_expense/tests/test_expenses_access_rights.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+# Part of Odoo. See LICENSE file for full copyright and licensing details.
+from odoo.addons.hr_expense.tests.common import TestExpenseCommon
+from odoo.exceptions import AccessError, UserError
+from odoo.tests import tagged
+
+
+@tagged('-at_install', 'post_install')
+class TestExpensesAccessRights(TestExpenseCommon):
+
+ def test_expense_access_rights(self):
+ ''' The expense employee can't be able to create an expense for someone else.'''
+
+ expense_employee_2 = self.env['hr.employee'].create({
+ 'name': 'expense_employee_2',
+ 'user_id': self.env.user.id,
+ 'address_home_id': self.env.user.partner_id.id,
+ 'address_id': self.env.user.partner_id.id,
+ })
+
+ with self.assertRaises(AccessError):
+ self.env['hr.expense'].with_user(self.expense_user_employee).create({
+ 'name': "Superboy costume washing",
+ 'employee_id': expense_employee_2.id,
+ 'product_id': self.product_a.id,
+ 'quantity': 1,
+ 'unit_amount': 1,
+ })
+
+ def test_expense_sheet_access_rights_approve(self):
+
+ # The expense employee is able to a create an expense sheet.
+
+ expense_sheet = self.env['hr.expense.sheet'].with_user(self.expense_user_employee).create({
+ 'name': 'First Expense for employee',
+ 'employee_id': self.expense_employee.id,
+ 'journal_id': self.company_data['default_journal_purchase'].id,
+ 'accounting_date': '2017-01-01',
+ 'expense_line_ids': [
+ (0, 0, {
+ # Expense without foreign currency but analytic account.
+ 'name': 'expense_1',
+ 'date': '2016-01-01',
+ 'product_id': self.product_a.id,
+ 'unit_amount': 1000.0,
+ 'employee_id': self.expense_employee.id,
+ }),
+ ],
+ })
+ self.assertRecordValues(expense_sheet, [{'state': 'draft'}])
+
+ # The expense employee is able to submit the expense sheet.
+
+ expense_sheet.with_user(self.expense_user_employee).action_submit_sheet()
+ self.assertRecordValues(expense_sheet, [{'state': 'submit'}])
+
+ # The expense employee is not able to approve itself the expense sheet.
+
+ with self.assertRaises(UserError):
+ expense_sheet.with_user(self.expense_user_employee).approve_expense_sheets()
+ self.assertRecordValues(expense_sheet, [{'state': 'submit'}])
+
+ # An expense manager is required for this step.
+
+ expense_sheet.with_user(self.expense_user_manager).approve_expense_sheets()
+ self.assertRecordValues(expense_sheet, [{'state': 'approve'}])
+
+ # An expense manager is not able to create the journal entry.
+
+ with self.assertRaises(UserError):
+ expense_sheet.with_user(self.expense_user_manager).action_sheet_move_create()
+ self.assertRecordValues(expense_sheet, [{'state': 'approve'}])
+
+ # An expense manager having accounting access rights is able to create the journal entry.
+
+ expense_sheet.with_user(self.env.user).action_sheet_move_create()
+ self.assertRecordValues(expense_sheet, [{'state': 'post'}])
+
+ def test_expense_sheet_access_rights_refuse(self):
+
+ # The expense employee is able to a create an expense sheet.
+
+ expense_sheet = self.env['hr.expense.sheet'].with_user(self.expense_user_employee).create({
+ 'name': 'First Expense for employee',
+ 'employee_id': self.expense_employee.id,
+ 'journal_id': self.company_data['default_journal_purchase'].id,
+ 'accounting_date': '2017-01-01',
+ 'expense_line_ids': [
+ (0, 0, {
+ # Expense without foreign currency but analytic account.
+ 'name': 'expense_1',
+ 'date': '2016-01-01',
+ 'product_id': self.product_a.id,
+ 'unit_amount': 1000.0,
+ 'employee_id': self.expense_employee.id,
+ }),
+ ],
+ })
+ self.assertRecordValues(expense_sheet, [{'state': 'draft'}])
+
+ # The expense employee is able to submit the expense sheet.
+
+ expense_sheet.with_user(self.expense_user_employee).action_submit_sheet()
+ self.assertRecordValues(expense_sheet, [{'state': 'submit'}])
+
+ # The expense employee is not able to refuse itself the expense sheet.
+
+ with self.assertRaises(UserError):
+ expense_sheet.with_user(self.expense_user_employee).refuse_sheet('')
+ self.assertRecordValues(expense_sheet, [{'state': 'submit'}])
+
+ # An expense manager is required for this step.
+
+ expense_sheet.with_user(self.expense_user_manager).refuse_sheet('')
+ self.assertRecordValues(expense_sheet, [{'state': 'cancel'}])
diff --git a/addons/hr_expense/tests/test_expenses_mail_import.py b/addons/hr_expense/tests/test_expenses_mail_import.py
new file mode 100644
index 00000000..9f6db49a
--- /dev/null
+++ b/addons/hr_expense/tests/test_expenses_mail_import.py
@@ -0,0 +1,153 @@
+# -*- coding: utf-8 -*-
+# 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
+
+
+@tagged('-at_install', 'post_install')
+class TestExpensesMailImport(TestExpenseCommon):
+
+ @classmethod
+ def setUpClass(cls, chart_template_ref=None):
+ super().setUpClass(chart_template_ref=chart_template_ref)
+
+ cls.product_a.default_code = 'product_a'
+ cls.product_b.default_code = 'product_b'
+
+ def test_import_expense_from_email(self):
+ message_parsed = {
+ 'message_id': "the-world-is-a-ghetto",
+ 'subject': '%s %s' % (self.product_a.default_code, self.product_a.standard_price),
+ 'email_from': self.expense_user_employee.email,
+ 'to': 'catchall@yourcompany.com',
+ 'body': "Don't you know, that for me, and for you",
+ 'attachments': [],
+ }
+
+ expense = self.env['hr.expense'].message_new(message_parsed)
+
+ self.assertRecordValues(expense, [{
+ 'product_id': self.product_a.id,
+ 'total_amount': 920.0,
+ 'employee_id': self.expense_employee.id,
+ }])
+
+ def test_import_expense_from_email_no_product(self):
+ message_parsed = {
+ 'message_id': "the-world-is-a-ghetto",
+ 'subject': 'no product code 800',
+ 'email_from': self.expense_user_employee.email,
+ 'to': 'catchall@yourcompany.com',
+ 'body': "Don't you know, that for me, and for you",
+ 'attachments': [],
+ }
+
+ expense = self.env['hr.expense'].message_new(message_parsed)
+
+ self.assertRecordValues(expense, [{
+ 'product_id': False,
+ 'total_amount': 800.0,
+ 'employee_id': self.expense_employee.id,
+ }])
+
+ def test_import_expense_from_mail_parsing_subjects(self):
+
+ def assertParsedValues(subject, currencies, exp_description, exp_amount, exp_product):
+ product, amount, currency_id, description = self.env['hr.expense']\
+ .with_user(self.expense_user_employee)\
+ ._parse_expense_subject(subject, currencies)
+
+ self.assertEqual(product, exp_product)
+ self.assertAlmostEqual(amount, exp_amount)
+ self.assertEqual(description, exp_description)
+
+ # Without Multi currency access
+ assertParsedValues(
+ "product_a bar $1205.91 electro wizard",
+ self.company_data['currency'],
+ "bar electro wizard",
+ 1205.91,
+ self.product_a,
+ )
+
+ # subject having other currency then company currency, it should ignore other currency then company currency
+ assertParsedValues(
+ "foo bar %s1406.91 royal giant" % self.currency_data['currency'].symbol,
+ self.company_data['currency'],
+ "foo bar %s royal giant" % self.currency_data['currency'].symbol,
+ 1406.91,
+ self.env['product.product'],
+ )
+
+ # With Multi currency access
+ self.expense_user_employee.groups_id |= self.env.ref('base.group_multi_currency')
+
+ assertParsedValues(
+ "product_a foo bar $2205.92 elite barbarians",
+ self.company_data['currency'],
+ "foo bar elite barbarians",
+ 2205.92,
+ self.product_a,
+ )
+
+ # subject having other currency then company currency, it should accept other currency because multi currency is activated
+ assertParsedValues(
+ "product_a %s2510.90 chhota bheem" % self.currency_data['currency'].symbol,
+ self.company_data['currency'] + self.currency_data['currency'],
+ "chhota bheem",
+ 2510.90,
+ self.product_a,
+ )
+
+ # subject without product and currency, should take company currency and default product
+ assertParsedValues(
+ "foo bar 109.96 spear goblins",
+ self.company_data['currency'] + self.currency_data['currency'],
+ "foo bar spear goblins",
+ 109.96,
+ self.env['product.product'],
+ )
+
+ # subject with currency symbol at end
+ assertParsedValues(
+ "product_a foo bar 2910.94$ inferno dragon",
+ self.company_data['currency'] + self.currency_data['currency'],
+ "foo bar inferno dragon",
+ 2910.94,
+ self.product_a,
+ )
+
+ # subject with no amount and product
+ assertParsedValues(
+ "foo bar mega knight",
+ self.company_data['currency'] + self.currency_data['currency'],
+ "foo bar mega knight",
+ 0.0,
+ self.env['product.product'],
+ )
+
+ # price with a comma
+ assertParsedValues(
+ "foo bar 291,56$ mega knight",
+ self.company_data['currency'] + self.currency_data['currency'],
+ "foo bar mega knight",
+ 291.56,
+ self.env['product.product'],
+ )
+
+ # price without decimals
+ assertParsedValues(
+ "foo bar 291$ mega knight",
+ self.company_data['currency'] + self.currency_data['currency'],
+ "foo bar mega knight",
+ 291.0,
+ self.env['product.product'],
+ )
+
+ assertParsedValues(
+ "product_a foo bar 291.5$ mega knight",
+ self.company_data['currency'] + self.currency_data['currency'],
+ "foo bar mega knight",
+ 291.5,
+ self.product_a,
+ )
diff --git a/addons/hr_expense/tests/test_expenses_multi_company.py b/addons/hr_expense/tests/test_expenses_multi_company.py
new file mode 100644
index 00000000..9ad54908
--- /dev/null
+++ b/addons/hr_expense/tests/test_expenses_multi_company.py
@@ -0,0 +1,116 @@
+# -*- coding: utf-8 -*-
+# 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
+from odoo.exceptions import UserError
+
+
+@tagged('post_install', '-at_install')
+class TestExpenseMultiCompany(TestExpenseCommon):
+
+ def test_expense_sheet_multi_company_approve(self):
+ self.expense_employee.company_id = self.company_data_2['company']
+
+ # The expense employee is able to a create an expense sheet for company_2.
+
+ expense_sheet = self.env['hr.expense.sheet']\
+ .with_user(self.expense_user_employee)\
+ .with_context(allowed_company_ids=self.company_data_2['company'].ids)\
+ .create({
+ 'name': 'First Expense for employee',
+ 'employee_id': self.expense_employee.id,
+ 'journal_id': self.company_data_2['default_journal_purchase'].id,
+ 'accounting_date': '2017-01-01',
+ 'expense_line_ids': [
+ (0, 0, {
+ # Expense without foreign currency but analytic account.
+ 'name': 'expense_1',
+ 'date': '2016-01-01',
+ 'product_id': self.product_a.id,
+ 'unit_amount': 1000.0,
+ 'employee_id': self.expense_employee.id,
+ }),
+ ],
+ })
+ self.assertRecordValues(expense_sheet, [{'company_id': self.company_data_2['company'].id}])
+
+ # The expense employee is able to submit the expense sheet.
+
+ expense_sheet.with_user(self.expense_user_employee).action_submit_sheet()
+
+ # An expense manager is not able to approve without access to company_2.
+
+ with self.assertRaises(UserError):
+ expense_sheet\
+ .with_user(self.expense_user_manager)\
+ .with_context(allowed_company_ids=self.company_data['company'].ids)\
+ .approve_expense_sheets()
+
+ # An expense manager is able to approve with access to company_2.
+
+ expense_sheet\
+ .with_user(self.expense_user_manager)\
+ .with_context(allowed_company_ids=self.company_data_2['company'].ids)\
+ .approve_expense_sheets()
+
+ # An expense manager having accounting access rights is not able to create the journal entry without access
+ # to company_2.
+
+ with self.assertRaises(UserError):
+ expense_sheet\
+ .with_user(self.env.user)\
+ .with_context(allowed_company_ids=self.company_data['company'].ids)\
+ .action_sheet_move_create()
+
+ # An expense manager having accounting access rights is able to create the journal entry with access to
+ # company_2.
+
+ expense_sheet\
+ .with_user(self.env.user)\
+ .with_context(allowed_company_ids=self.company_data_2['company'].ids)\
+ .action_sheet_move_create()
+
+ def test_expense_sheet_multi_company_refuse(self):
+ self.expense_employee.company_id = self.company_data_2['company']
+
+ # The expense employee is able to a create an expense sheet for company_2.
+
+ expense_sheet = self.env['hr.expense.sheet']\
+ .with_user(self.expense_user_employee)\
+ .with_context(allowed_company_ids=self.company_data_2['company'].ids)\
+ .create({
+ 'name': 'First Expense for employee',
+ 'employee_id': self.expense_employee.id,
+ 'journal_id': self.company_data_2['default_journal_purchase'].id,
+ 'accounting_date': '2017-01-01',
+ 'expense_line_ids': [
+ (0, 0, {
+ # Expense without foreign currency but analytic account.
+ 'name': 'expense_1',
+ 'date': '2016-01-01',
+ 'product_id': self.product_a.id,
+ 'unit_amount': 1000.0,
+ 'employee_id': self.expense_employee.id,
+ }),
+ ],
+ })
+ self.assertRecordValues(expense_sheet, [{'company_id': self.company_data_2['company'].id}])
+
+ # The expense employee is able to submit the expense sheet.
+
+ expense_sheet.with_user(self.expense_user_employee).action_submit_sheet()
+
+ # An expense manager is not able to approve without access to company_2.
+
+ with self.assertRaises(UserError):
+ expense_sheet\
+ .with_user(self.expense_user_manager)\
+ .with_context(allowed_company_ids=self.company_data['company'].ids)\
+ .refuse_sheet('')
+
+ # An expense manager is able to approve with access to company_2.
+
+ expense_sheet\
+ .with_user(self.expense_user_manager)\
+ .with_context(allowed_company_ids=self.company_data_2['company'].ids)\
+ .refuse_sheet('')