mirror of
https://github.com/bringout/oca-ocb-hr.git
synced 2026-04-23 16:12:01 +02:00
19.0 vanilla
This commit is contained in:
parent
a1137a1456
commit
e1d89e11e3
2789 changed files with 1093187 additions and 605897 deletions
|
|
@ -4,7 +4,6 @@ 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
|
||||
from . import test_expenses_standard_price_update_warning
|
||||
from . import test_expenses_states
|
||||
from . import test_ui
|
||||
from . import test_expenses_tour
|
||||
|
|
|
|||
|
|
@ -1,7 +1,9 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import Command
|
||||
from freezegun import freeze_time
|
||||
|
||||
from odoo import Command, fields
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
from odoo.addons.mail.tests.common import mail_new_test_user
|
||||
|
|
@ -10,8 +12,11 @@ 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)
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
|
||||
cls.company_data_2 = cls.setup_other_company()
|
||||
cls.other_currency = cls.setup_other_currency('HRK')
|
||||
|
||||
group_expense_manager = cls.env.ref('hr_expense.group_hr_expense_manager')
|
||||
|
||||
|
|
@ -22,7 +27,7 @@ class TestExpenseCommon(AccountTestInvoicingCommon):
|
|||
email='expense_user_employee@example.com',
|
||||
notification_type='email',
|
||||
groups='base.group_user',
|
||||
company_ids=[(6, 0, cls.env.companies.ids)],
|
||||
company_ids=[Command.set(cls.env.companies.ids)],
|
||||
)
|
||||
cls.expense_user_manager = mail_new_test_user(
|
||||
cls.env,
|
||||
|
|
@ -31,38 +36,39 @@ class TestExpenseCommon(AccountTestInvoicingCommon):
|
|||
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.accountant_user = mail_new_test_user(
|
||||
cls.env,
|
||||
name='Accountant',
|
||||
login='accountant_1',
|
||||
email='accountant_1@example.com',
|
||||
notification_type='email',
|
||||
groups='account.group_account_user',
|
||||
company_ids=[(6, 0, cls.env.companies.ids)],
|
||||
company_ids=[Command.set(cls.env.companies.ids)],
|
||||
)
|
||||
|
||||
cls.expense_employee = cls.env['hr.employee'].create({
|
||||
cls.expense_user_manager_2 = mail_new_test_user(
|
||||
cls.env,
|
||||
name='Expense manager',
|
||||
login='expense_manager_2',
|
||||
email='expense_manager_2@example.com',
|
||||
notification_type='email',
|
||||
groups='base.group_user,hr_expense.group_hr_expense_manager',
|
||||
company_ids=[Command.set(cls.env.companies.ids)],
|
||||
)
|
||||
|
||||
cls.expense_employee = cls.env['hr.employee'].sudo().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,
|
||||
})
|
||||
|
||||
cls.product_zero_cost = cls.env['product.product'].create({
|
||||
'name': 'General',
|
||||
'default_code': 'EXP_GEN',
|
||||
'standard_price': 0.0,
|
||||
'can_be_expensed': True,
|
||||
})
|
||||
'expense_manager_id': cls.expense_user_manager.id,
|
||||
'work_contact_id': cls.expense_user_employee.partner_id.id,
|
||||
'bank_account_ids': [
|
||||
Command.create({
|
||||
'acc_number': 'BE68539007547034',
|
||||
'allow_out_payment': True,
|
||||
'partner_id': cls.expense_user_employee.partner_id.id,
|
||||
})]
|
||||
}).sudo(False)
|
||||
|
||||
cls.expense_employee.user_partner_id.parent_id = cls.env.company.partner_id
|
||||
|
||||
# Allow the current accounting user to access the expenses.
|
||||
cls.env.user.groups_id |= group_expense_manager
|
||||
cls.env.user.group_ids |= group_expense_manager
|
||||
|
||||
# Create analytic account
|
||||
cls.analytic_plan = cls.env['account.analytic.plan'].create({'name': 'Plan Test', 'company_id': False})
|
||||
cls.analytic_plan = cls.env['account.analytic.plan'].create({'name': 'Expense Plan Test'})
|
||||
cls.analytic_account_1 = cls.env['account.analytic.account'].create({
|
||||
'name': 'analytic_account_1',
|
||||
'plan_id': cls.analytic_plan.id,
|
||||
|
|
@ -72,6 +78,7 @@ class TestExpenseCommon(AccountTestInvoicingCommon):
|
|||
'plan_id': cls.analytic_plan.id,
|
||||
})
|
||||
|
||||
# Create product without cost
|
||||
cls.product_c = cls.env['product.product'].create({
|
||||
'name': 'product_c with no cost',
|
||||
'uom_id': cls.env.ref('uom.product_uom_dozen').id,
|
||||
|
|
@ -81,7 +88,64 @@ class TestExpenseCommon(AccountTestInvoicingCommon):
|
|||
'taxes_id': [Command.set((cls.tax_sale_a + cls.tax_sale_b).ids)],
|
||||
'supplier_taxes_id': [Command.set((cls.tax_purchase_a + cls.tax_purchase_b).ids)],
|
||||
'can_be_expensed': True,
|
||||
'default_code': 'product_c',
|
||||
})
|
||||
|
||||
# Ensure products can be expensed.
|
||||
# Ensure Invoicing tests products can be expensed and their code is properly set.
|
||||
(cls.product_a + cls.product_b).write({'can_be_expensed': True})
|
||||
cls.product_a.default_code = 'product_a'
|
||||
cls.product_b.default_code = 'product_b'
|
||||
|
||||
cls.frozen_today = datetime(year=2022, month=1, day=25, hour=0, minute=0, second=0)
|
||||
|
||||
# create expense account
|
||||
cls.expense_account = cls.env['account.account'].create({
|
||||
'code': '610010',
|
||||
'name': 'Expense Account 1'
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def create_expenses(cls, values=None):
|
||||
if values is None or isinstance(values, dict):
|
||||
values = [values or {}]
|
||||
|
||||
default_values = {
|
||||
'employee_id': cls.expense_employee.id,
|
||||
'date': cls.frozen_today.isoformat(),
|
||||
'company_id': cls.company_data['company'].id,
|
||||
'currency_id': cls.company_data['currency'].id,
|
||||
}
|
||||
|
||||
default_product_values = {
|
||||
'product_id': cls.product_c.id,
|
||||
'total_amount_currency': 1000.00,
|
||||
}
|
||||
create_values = []
|
||||
for value_dict in values:
|
||||
if 'product_id' not in value_dict:
|
||||
default_values.update(default_product_values)
|
||||
value_dict = {**default_values, **(value_dict or {})}
|
||||
create_values.append(value_dict)
|
||||
return cls.env['hr.expense'].create(create_values).sorted()
|
||||
|
||||
@classmethod
|
||||
def post_expenses_with_wizard(cls, expenses, journal=None, date=None):
|
||||
action = expenses.action_post()
|
||||
if action:
|
||||
wizard = expenses.env['hr.expense.post.wizard'].with_context(action['context']).browse(action['res_id'])
|
||||
if journal:
|
||||
wizard.employee_journal_id = journal.id
|
||||
wizard.accounting_date = date or fields.Date.context_today(expenses)
|
||||
wizard.action_post_entry()
|
||||
|
||||
def get_new_payment(self, expenses, amount):
|
||||
""" Helper to create payments """
|
||||
ctx = {'active_model': 'account.move', 'active_ids': expenses.account_move_id.ids}
|
||||
with freeze_time(self.frozen_today):
|
||||
payment_register = self.env['account.payment.register'].with_context(**ctx).create({
|
||||
'amount': amount,
|
||||
'journal_id': self.company_data['default_journal_bank'].id,
|
||||
'payment_method_line_id': self.inbound_payment_method_line.id,
|
||||
})
|
||||
self.assertEqual(payment_register.partner_bank_id.partner_id, expenses.employee_id.work_contact_id)
|
||||
return payment_register._create_payments()
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,22 +1,23 @@
|
|||
# -*- 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 import Command
|
||||
from odoo.exceptions import AccessError, UserError
|
||||
from odoo.tests import tagged
|
||||
from odoo.tests import HttpCase, tagged, new_test_user
|
||||
|
||||
from odoo.addons.hr_expense.tests.common import TestExpenseCommon
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestExpensesAccessRights(TestExpenseCommon):
|
||||
class TestExpensesAccessRights(TestExpenseCommon, HttpCase):
|
||||
|
||||
def test_expense_access_rights(self):
|
||||
''' The expense employee can't be able to create an expense for someone else.'''
|
||||
""" The expense employee can't be able to create an expense for someone else. """
|
||||
|
||||
expense_employee_2 = self.env['hr.employee'].create({
|
||||
expense_employee_2 = self.env['hr.employee'].sudo().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,
|
||||
})
|
||||
'work_contact_id': self.env.user.partner_id.id,
|
||||
}).sudo(False)
|
||||
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['hr.expense'].with_user(self.expense_user_employee).create({
|
||||
|
|
@ -24,127 +25,80 @@ class TestExpensesAccessRights(TestExpenseCommon):
|
|||
'employee_id': expense_employee_2.id,
|
||||
'product_id': self.product_a.id,
|
||||
'quantity': 1,
|
||||
'unit_amount': 1,
|
||||
})
|
||||
|
||||
expense = self.env['hr.expense'].with_user(self.expense_user_employee).create({
|
||||
'name': 'expense_1',
|
||||
'date': '2016-01-01',
|
||||
'product_id': self.product_a.id,
|
||||
'unit_amount': 10.0,
|
||||
'quantity': 10.0,
|
||||
'employee_id': self.expense_employee.id,
|
||||
})
|
||||
|
||||
# The expense employee shouldn't be able to bypass the submit state.
|
||||
with self.assertRaises(UserError):
|
||||
expense.with_user(self.expense_user_employee).state = 'approve'
|
||||
expense.with_user(self.expense_user_employee).state = 'approved'
|
||||
expense.with_user(self.expense_user_employee).state = 'draft' # Should not raise
|
||||
|
||||
# Employee can report & submit their expense
|
||||
expense_sheet = self.env['hr.expense.sheet'].with_user(self.expense_user_employee).create({
|
||||
'name': 'expense sheet for employee',
|
||||
'expense_line_ids': expense,
|
||||
'payment_mode': expense.payment_mode,
|
||||
})
|
||||
expense_sheet.with_user(self.expense_user_employee).action_submit_sheet()
|
||||
self.assertEqual(expense.state, 'reported') # Todo change in 17.0+
|
||||
expense.with_user(self.expense_user_employee).action_submit()
|
||||
self.assertEqual(expense.state, 'submitted')
|
||||
|
||||
# Employee can also revert from the submitted state to a draft state
|
||||
expense_sheet.with_user(self.expense_user_employee).reset_expense_sheets()
|
||||
self.assertEqual(expense.state, 'draft') # Todo change in 17.0+
|
||||
expense.with_user(self.expense_user_employee).action_reset()
|
||||
self.assertEqual(expense.state, 'draft')
|
||||
|
||||
def test_expense_sheet_access_rights_approve(self):
|
||||
def test_expense_access_rights_user(self):
|
||||
# The expense base user (without other rights) is able to create and read sheet
|
||||
|
||||
# The expense employee is able to a create an expense sheet.
|
||||
user = new_test_user(self.env, login='test-expense', groups='base.group_user')
|
||||
expense_employee = self.env['hr.employee'].sudo().create({
|
||||
'name': 'expense_employee_base_user',
|
||||
'user_id': user.id,
|
||||
'work_contact_id': user.partner_id.id,
|
||||
'expense_manager_id': self.expense_user_manager.id,
|
||||
'address_id': user.partner_id.id,
|
||||
}).sudo(False)
|
||||
|
||||
expense_sheet = self.env['hr.expense.sheet'].with_user(self.expense_user_employee).create({
|
||||
expense = self.env['hr.expense'].with_user(user).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,
|
||||
}),
|
||||
],
|
||||
'employee_id': expense_employee.id,
|
||||
# Expense without foreign currency but analytic account.
|
||||
'product_id': self.product_a.id,
|
||||
'price_unit': 1000.0,
|
||||
})
|
||||
self.env.flush_all()
|
||||
self.start_tour("/odoo", 'hr_expense_access_rights_test_tour', login="test-expense")
|
||||
self.assertRecordValues(expense, [{'state': 'submitted'}])
|
||||
|
||||
self.assertRecordValues(expense_sheet, [{'state': 'draft'}])
|
||||
|
||||
# The expense employee shouldn't be able to bypass the submit state.
|
||||
def test_expense_user_cant_approve_own_expense(self):
|
||||
expense = self.env['hr.expense'].with_user(self.expense_user_employee).create({
|
||||
'name': "Superboy costume washing",
|
||||
'employee_id': self.expense_employee.id,
|
||||
'product_id': self.product_a.id,
|
||||
'quantity': 1,
|
||||
'price_unit': 1,
|
||||
})
|
||||
expense.with_user(self.expense_user_employee).action_submit()
|
||||
with self.assertRaises(UserError):
|
||||
expense_sheet.with_user(self.expense_user_employee).state = 'approve'
|
||||
expense.with_user(self.expense_user_employee).action_approve()
|
||||
|
||||
# The expense employee is able to submit the expense sheet.
|
||||
def test_expense_team_approver_cant_approve_expense_of_employee_he_does_not_manage(self):
|
||||
another_standard_user = new_test_user(self.env, login='another_standard_user', groups='base.group_user')
|
||||
another_standard_user_team_approver = new_test_user(self.env, login='another_standard_user_manager', groups='base.group_user,hr_expense.group_hr_expense_team_approver')
|
||||
|
||||
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'}])
|
||||
|
||||
# The expense employee shouldn't be able to modify an approved expense.
|
||||
with self.assertRaises(UserError):
|
||||
expense_sheet.expense_line_ids[0].with_user(self.expense_user_employee).total_amount = 1000.0
|
||||
|
||||
# An expense manager is not able to create the journal entry.
|
||||
another_employee = self.env['hr.employee'].sudo().create({
|
||||
'name': 'another_employee',
|
||||
'user_id': another_standard_user.id,
|
||||
'work_contact_id': another_standard_user.partner_id.id,
|
||||
'expense_manager_id': self.expense_user_manager.id,
|
||||
}).sudo(False)
|
||||
|
||||
expense = self.env['hr.expense'].with_user(another_standard_user).create({
|
||||
'name': "Superboy costume washing",
|
||||
'employee_id': another_employee.id,
|
||||
'product_id': self.product_a.id,
|
||||
'quantity': 1,
|
||||
'price_unit': 1,
|
||||
})
|
||||
expense.with_user(another_standard_user).action_submit()
|
||||
with self.assertRaises(AccessError):
|
||||
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.accountant_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'}])
|
||||
expense.with_user(another_standard_user_team_approver).action_approve()
|
||||
|
|
|
|||
|
|
@ -1,35 +1,46 @@
|
|||
# -*- 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('-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': [],
|
||||
}
|
||||
messages = (
|
||||
{
|
||||
'message_id': "the-world-is-a-ghetto",
|
||||
'subject': f'{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': [],
|
||||
}, {
|
||||
'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': [],
|
||||
}, {
|
||||
'message_id': "test",
|
||||
'subject': 'product_c my description 100',
|
||||
'email_from': self.expense_user_employee.email,
|
||||
'to': 'catchall@yourcompany.com',
|
||||
'body': "test",
|
||||
'attachments': [],
|
||||
}
|
||||
)
|
||||
expenses = self.env['hr.expense']
|
||||
for message in messages:
|
||||
expenses |= self.env['hr.expense'].message_new(message)
|
||||
|
||||
expense = self.env['hr.expense'].message_new(message_parsed)
|
||||
self.assertRecordValues(expense, [{
|
||||
'product_id': self.product_a.id,
|
||||
'total_amount': 800.0,
|
||||
'employee_id': self.expense_employee.id,
|
||||
}])
|
||||
self.assertRecordValues(expenses, [
|
||||
{'product_id': self.product_a.id, 'total_amount_currency': 800.0, 'employee_id': self.expense_employee.id},
|
||||
{'product_id': False, 'total_amount_currency': 800.0, 'employee_id': self.expense_employee.id},
|
||||
{'product_id': self.product_c.id, 'total_amount_currency': 100.0, 'employee_id': self.expense_employee.id},
|
||||
])
|
||||
|
||||
def test_import_expense_from_email_several_employees(self):
|
||||
"""When a user has several employees' profiles from different companies, the right record should be selected"""
|
||||
|
|
@ -38,12 +49,12 @@ class TestExpensesMailImport(TestExpenseCommon):
|
|||
user.company_id = company_2.id
|
||||
|
||||
# Create a second employee linked to the user for another company
|
||||
company_2_employee = self.env['hr.employee'].create({
|
||||
company_2_employee = self.env['hr.employee'].sudo().create({
|
||||
'name': 'expense_employee_2',
|
||||
'company_id': company_2.id,
|
||||
'user_id': user.id,
|
||||
'work_email': user.email,
|
||||
})
|
||||
}).sudo(False)
|
||||
|
||||
message_parsed = {
|
||||
'message_id': "the-world-is-a-ghetto",
|
||||
|
|
@ -59,9 +70,9 @@ class TestExpensesMailImport(TestExpenseCommon):
|
|||
}])
|
||||
|
||||
def test_import_expense_from_email_employee_without_user(self):
|
||||
"""When an employee is not linked to a user, he has to be able to create expenses from email"""
|
||||
""" When an employee is not linked to a user, he has to be able to create expenses from email """
|
||||
employee = self.expense_employee
|
||||
employee.user_id = False
|
||||
employee.sudo().user_id = False
|
||||
|
||||
message_parsed = {
|
||||
'message_id': "the-world-is-a-ghetto",
|
||||
|
|
@ -95,42 +106,16 @@ class TestExpensesMailImport(TestExpenseCommon):
|
|||
'employee_id': self.expense_employee.id,
|
||||
}])
|
||||
|
||||
def test_import_expense_from_email_product_no_cost(self):
|
||||
"""
|
||||
We have to compute a value for the total amount
|
||||
even if the product has no cost.
|
||||
"""
|
||||
product_no_cost = self.env['product.product'].create({
|
||||
'name': 'Product No Cost',
|
||||
'standard_price': 0.0,
|
||||
'can_be_expensed': True,
|
||||
'default_code': 'product_no_cost',
|
||||
})
|
||||
message_parsed = {
|
||||
'message_id': "test",
|
||||
'subject': 'product_no_cost my description 100',
|
||||
'email_from': self.expense_user_employee.email,
|
||||
'to': 'catchall@yourcompany.com',
|
||||
'body': "test",
|
||||
'attachments': [],
|
||||
}
|
||||
expense = self.env['hr.expense'].message_new(message_parsed)
|
||||
self.assertRecordValues(expense, [{
|
||||
'product_id': product_no_cost.id,
|
||||
'total_amount': 100.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)\
|
||||
def assertParsedValues(subject, currencies, exp_description, exp_amount, exp_product, exp_currency):
|
||||
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)
|
||||
self.assertEqual(currency_id, exp_currency)
|
||||
|
||||
# Without Multi currency access
|
||||
assertParsedValues(
|
||||
|
|
@ -139,86 +124,107 @@ class TestExpensesMailImport(TestExpenseCommon):
|
|||
"bar electro wizard",
|
||||
1205.91,
|
||||
self.product_a,
|
||||
self.company_data['currency'],
|
||||
)
|
||||
|
||||
# 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,
|
||||
f'foo bar {self.other_currency.symbol}1406.91 royal giant',
|
||||
self.company_data['currency'],
|
||||
"foo bar %s royal giant" % self.currency_data['currency'].symbol,
|
||||
f'foo bar {self.other_currency.symbol} royal giant',
|
||||
1406.91,
|
||||
self.env['product.product'],
|
||||
self.company_data['currency'],
|
||||
)
|
||||
|
||||
# With Multi currency access
|
||||
self.expense_user_employee.groups_id |= self.env.ref('base.group_multi_currency')
|
||||
|
||||
self.expense_user_employee.group_ids |= 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,
|
||||
self.company_data['currency'],
|
||||
)
|
||||
|
||||
# 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'],
|
||||
f'product_a {self.other_currency.symbol}2510.90 chhota bheem',
|
||||
self.company_data['currency'] + self.other_currency,
|
||||
"chhota bheem",
|
||||
2510.90,
|
||||
self.product_a,
|
||||
self.other_currency,
|
||||
)
|
||||
|
||||
# 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'],
|
||||
self.company_data['currency'] + self.other_currency,
|
||||
"foo bar spear goblins",
|
||||
109.96,
|
||||
self.env['product.product'],
|
||||
self.company_data['currency'],
|
||||
)
|
||||
|
||||
# subject with currency symbol at end
|
||||
assertParsedValues(
|
||||
"product_a foo bar 2910.94$ inferno dragon",
|
||||
self.company_data['currency'] + self.currency_data['currency'],
|
||||
self.company_data['currency'] + self.other_currency,
|
||||
"foo bar inferno dragon",
|
||||
2910.94,
|
||||
self.product_a,
|
||||
self.company_data['currency'],
|
||||
)
|
||||
|
||||
# subject with no amount and product
|
||||
assertParsedValues(
|
||||
"foo bar mega knight",
|
||||
self.company_data['currency'] + self.currency_data['currency'],
|
||||
self.company_data['currency'] + self.other_currency,
|
||||
"foo bar mega knight",
|
||||
0.0,
|
||||
self.env['product.product'],
|
||||
self.company_data['currency'],
|
||||
)
|
||||
|
||||
# price with a comma
|
||||
assertParsedValues(
|
||||
"foo bar 291,56$ mega knight",
|
||||
self.company_data['currency'] + self.currency_data['currency'],
|
||||
self.company_data['currency'] + self.other_currency,
|
||||
"foo bar mega knight",
|
||||
291.56,
|
||||
self.env['product.product'],
|
||||
self.company_data['currency'],
|
||||
)
|
||||
|
||||
# price without decimals
|
||||
# price different decimals than currency
|
||||
assertParsedValues(
|
||||
"foo bar 291$ mega knight",
|
||||
self.company_data['currency'] + self.currency_data['currency'],
|
||||
self.company_data['currency'] + self.other_currency,
|
||||
"foo bar mega knight",
|
||||
291.0,
|
||||
self.env['product.product'],
|
||||
self.company_data['currency'],
|
||||
)
|
||||
|
||||
assertParsedValues(
|
||||
"product_a foo bar 291.5$ mega knight",
|
||||
self.company_data['currency'] + self.currency_data['currency'],
|
||||
self.company_data['currency'] + self.other_currency,
|
||||
"foo bar mega knight",
|
||||
291.5,
|
||||
self.product_a,
|
||||
self.company_data['currency'],
|
||||
)
|
||||
|
||||
def test_import_expense_from_mail_action_submit_errors(self):
|
||||
""" Make sure we get the expected UserError when trying to validate an expense with no product """
|
||||
message = {
|
||||
'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)
|
||||
self.assertRaisesRegex(UserError, r"You can not submit an expense without a category\.", expense.action_submit)
|
||||
|
|
|
|||
|
|
@ -1,116 +0,0 @@
|
|||
# -*- 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('')
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.addons.hr_expense.tests.common import TestExpenseCommon
|
||||
from odoo.tests import tagged, Form
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestExpenseStandardPriceUpdateWarning(TestExpenseCommon):
|
||||
def test_expense_standard_price_update_warning(self):
|
||||
self.expense_cat_A = self.env['product.product'].create({
|
||||
'name': 'Category A',
|
||||
'default_code': 'CA',
|
||||
'standard_price': 0.0,
|
||||
})
|
||||
self.expense_cat_B = self.env['product.product'].create({
|
||||
'name': 'Category B',
|
||||
'default_code': 'CB',
|
||||
'standard_price': 0.0,
|
||||
})
|
||||
self.expense_cat_C = self.env['product.product'].create({
|
||||
'name': 'Category C',
|
||||
'default_code': 'CC',
|
||||
'standard_price': 0.0,
|
||||
})
|
||||
self.expense_1 = self.env['hr.expense'].create({
|
||||
'employee_id': self.expense_employee.id,
|
||||
'name': 'Expense 1',
|
||||
'product_id': self.expense_cat_A.id,
|
||||
'total_amount': 1,
|
||||
})
|
||||
self.expense_2 = self.env['hr.expense'].create({
|
||||
'employee_id': self.expense_employee.id,
|
||||
'name': 'Expense 2',
|
||||
'product_id': self.expense_cat_B.id,
|
||||
'total_amount': 5,
|
||||
})
|
||||
|
||||
# At first, there is no warning message on the categories because their prices are 0
|
||||
self.assertFalse(self.expense_cat_A.standard_price_update_warning)
|
||||
self.assertFalse(self.expense_cat_B.standard_price_update_warning)
|
||||
self.assertFalse(self.expense_cat_C.standard_price_update_warning)
|
||||
|
||||
# When modifying the price of the first category, a message should appear as a an expense will be modified.
|
||||
with Form(self.expense_cat_A, view="hr_expense.product_product_expense_form_view") as form:
|
||||
form.standard_price = 5
|
||||
self.assertTrue(form.standard_price_update_warning)
|
||||
|
||||
# When modifying the price of the second category, no message should appear as the price of the linked
|
||||
# expense is the price of the category that is going to be saved.
|
||||
with Form(self.expense_cat_B, view="hr_expense.product_product_expense_form_view") as form:
|
||||
form.standard_price = 5
|
||||
self.assertFalse(form.standard_price_update_warning)
|
||||
|
||||
# When modifying the price of the thirs category, no message should appear as no expense is linked to it.
|
||||
with Form(self.expense_cat_C, view="hr_expense.product_product_expense_form_view") as form:
|
||||
form.standard_price = 5
|
||||
self.assertFalse(form.standard_price_update_warning)
|
||||
|
||||
def test_compute_standard_price_update_warning_product_with_and_without_expense(self):
|
||||
self.product_expensed = self.env['product.product'].create({
|
||||
'name': 'Category A',
|
||||
'default_code': 'CA',
|
||||
'standard_price': 0.0,
|
||||
})
|
||||
self.product_not_expensed = self.env['product.product'].create({
|
||||
'name': 'Category B',
|
||||
'default_code': 'CB',
|
||||
'standard_price': 0.0,
|
||||
})
|
||||
self.expense_1 = self.env['hr.expense'].create({
|
||||
'employee_id': self.expense_employee.id,
|
||||
'name': 'Expense 1',
|
||||
'product_id': self.product_expensed.id,
|
||||
'total_amount': 1,
|
||||
})
|
||||
|
||||
# Having expensed and not expensed product in a recordset can cause a key error in the compute
|
||||
# as it looks for the product's origin id in the mapp dict, wich doesn't exists if the product
|
||||
# wasn't expensed.
|
||||
(self.product_expensed | self.product_not_expensed)._compute_standard_price_update_warning()
|
||||
|
|
@ -1,313 +1,214 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo import fields, Command
|
||||
from odoo.addons.mail.tests.common import MailCase
|
||||
from odoo import fields
|
||||
from odoo.addons.hr_expense.tests.common import TestExpenseCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestExpensesStates(TestExpenseCommon):
|
||||
class TestExpensesStates(TestExpenseCommon, MailCase):
|
||||
@classmethod
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
cls.expense_states_employee_sheet = cls.env['hr.expense.sheet'].create({
|
||||
def setUpClass(cls):
|
||||
super().setUpClass()
|
||||
cls.expenses_employee = cls.create_expenses({
|
||||
'name': 'Expense Employee 1',
|
||||
'employee_id': cls.expense_employee.id,
|
||||
'expense_line_ids': [Command.create({
|
||||
'name': 'Expense Employee 1',
|
||||
'employee_id': cls.expense_employee.id,
|
||||
'product_id': cls.product_c.id,
|
||||
'total_amount': 100.00,
|
||||
})],
|
||||
})
|
||||
|
||||
cls.expense_states_company_sheet = cls.env['hr.expense.sheet'].create({
|
||||
cls.expenses_company = cls.create_expenses({
|
||||
'name': 'Expense Company 1',
|
||||
'employee_id': cls.expense_employee.id,
|
||||
'expense_line_ids': [Command.create({
|
||||
'name': 'Expense Company 1',
|
||||
'payment_mode': 'company_account',
|
||||
'employee_id': cls.expense_employee.id,
|
||||
'product_id': cls.product_c.id,
|
||||
'total_amount': 100.00,
|
||||
})],
|
||||
'payment_mode': 'company_account',
|
||||
# To avoid duplicated expense wizard
|
||||
'total_amount_currency': 1000,
|
||||
'date': '2017-01-01',
|
||||
})
|
||||
cls.expense_states_sheets = cls.expense_states_employee_sheet + cls.expense_states_company_sheet
|
||||
cls.expenses_all = cls.expenses_employee + cls.expenses_company
|
||||
|
||||
cls.paid_or_in_payment_state = cls.env['account.move']._get_invoice_in_payment_state()
|
||||
|
||||
def test_expense_state_synchro_1_regular_flow(self):
|
||||
# STEP 1: Draft
|
||||
self.assertRecordValues(self.expense_states_sheets.expense_line_ids, [
|
||||
{'payment_mode': 'own_account', 'state': 'draft'},
|
||||
self.assertRecordValues(self.expenses_all, [
|
||||
{'payment_mode': 'own_account', 'state': 'draft'},
|
||||
{'payment_mode': 'company_account', 'state': 'draft'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_sheets, [
|
||||
{'payment_mode': 'own_account', 'state': 'draft', 'payment_state': 'not_paid'},
|
||||
{'payment_mode': 'company_account', 'state': 'draft', 'payment_state': 'not_paid'},
|
||||
])
|
||||
self.assertFalse(self.expense_states_sheets.account_move_id)
|
||||
self.assertFalse(self.expenses_all.account_move_id)
|
||||
|
||||
# STEP 2: Submit
|
||||
self.expense_states_sheets.action_submit_sheet()
|
||||
self.assertRecordValues(self.expense_states_sheets.expense_line_ids, [
|
||||
{'payment_mode': 'own_account', 'state': 'reported'},
|
||||
{'payment_mode': 'company_account', 'state': 'reported'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_sheets, [
|
||||
{'payment_mode': 'own_account', 'state': 'submit', 'payment_state': 'not_paid'},
|
||||
{'payment_mode': 'company_account', 'state': 'submit', 'payment_state': 'not_paid'},
|
||||
self.expenses_all.action_submit()
|
||||
self.assertRecordValues(self.expenses_all, [
|
||||
{'payment_mode': 'own_account', 'state': 'submitted'},
|
||||
{'payment_mode': 'company_account', 'state': 'submitted'},
|
||||
])
|
||||
self.assertFalse(self.expenses_all.account_move_id)
|
||||
|
||||
# STEP 3: Approve
|
||||
self.expense_states_sheets.approve_expense_sheets()
|
||||
self.assertRecordValues(self.expense_states_sheets.expense_line_ids, [
|
||||
{'payment_mode': 'own_account', 'state': 'approved'},
|
||||
self.expenses_all.action_approve()
|
||||
self.assertRecordValues(self.expenses_all, [
|
||||
{'payment_mode': 'own_account', 'state': 'approved'},
|
||||
{'payment_mode': 'company_account', 'state': 'approved'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_sheets, [
|
||||
{'payment_mode': 'own_account', 'state': 'approve', 'payment_state': 'not_paid'},
|
||||
{'payment_mode': 'company_account', 'state': 'approve', 'payment_state': 'not_paid'},
|
||||
])
|
||||
self.assertFalse(self.expense_states_sheets.account_move_id)
|
||||
self.assertFalse(self.expenses_all.account_move_id)
|
||||
|
||||
# STEP 4: Post
|
||||
self.expense_states_sheets.action_sheet_move_create()
|
||||
self.assertRecordValues(self.expense_states_sheets.expense_line_ids, [
|
||||
{'payment_mode': 'own_account', 'state': 'approved'},
|
||||
{'payment_mode': 'company_account', 'state': 'done'},
|
||||
# STEP 4: Post (create moves)
|
||||
self.post_expenses_with_wizard(self.expenses_all)
|
||||
self.assertRecordValues(self.expenses_all, [
|
||||
{'payment_mode': 'own_account', 'state': 'posted'},
|
||||
{'payment_mode': 'company_account', 'state': 'paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_sheets, [
|
||||
{'payment_mode': 'own_account', 'state': 'post', 'payment_state': 'not_paid'},
|
||||
{'payment_mode': 'company_account', 'state': 'done', 'payment_state': 'paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_sheets.account_move_id, [
|
||||
{'state': 'posted'},
|
||||
{'state': 'posted'},
|
||||
self.assertRecordValues(self.expenses_all.account_move_id, [
|
||||
{'state': 'posted', 'payment_state': 'not_paid'},
|
||||
{'state': 'posted', 'payment_state': 'not_paid'},
|
||||
])
|
||||
|
||||
def test_expense_state_synchro_2_employee_specific_flow(self):
|
||||
self.expense_states_sheets.action_submit_sheet()
|
||||
self.expense_states_sheets.approve_expense_sheets()
|
||||
self.expense_states_sheets.action_sheet_move_create()
|
||||
self.assertEqual('in_process', self.expenses_company.account_move_id.origin_payment_id.state)
|
||||
self.assertFalse(self.expenses_employee.account_move_id.origin_payment_id)
|
||||
|
||||
# STEP 1: ER posted -> Reset move to draft
|
||||
self.expense_states_employee_sheet.account_move_id.button_draft()
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.expense_line_ids, [
|
||||
{'state': 'approved'},
|
||||
def test_expense_state_synchro_1_cancel_move(self):
|
||||
""" Posted -> Cancel move (Back to approved) """
|
||||
self.expenses_all.action_submit()
|
||||
self.expenses_all.action_approve()
|
||||
self.post_expenses_with_wizard(self.expenses_all)
|
||||
|
||||
self.expenses_all.account_move_id.button_draft()
|
||||
self.expenses_all.account_move_id.button_cancel()
|
||||
self.assertRecordValues(self.expenses_all, [
|
||||
{'state': 'approved', 'account_move_id': False},
|
||||
{'state': 'approved', 'account_move_id': False},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet, [
|
||||
{'state': 'post', 'payment_state': 'not_paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.account_move_id, [
|
||||
{'state': 'draft'},
|
||||
self.assertFalse(self.expenses_all.account_move_id)
|
||||
|
||||
def test_expense_state_synchro_1_unlink_move(self):
|
||||
""" Posted -> Unlink move/payment (Back to approved) """
|
||||
self.expenses_all.action_submit()
|
||||
self.expenses_all.action_approve()
|
||||
self.post_expenses_with_wizard(self.expenses_all)
|
||||
|
||||
self.expenses_all.account_move_id.button_draft()
|
||||
self.expenses_all.account_move_id.origin_payment_id.unlink()
|
||||
self.expenses_all.account_move_id.unlink()
|
||||
self.assertRecordValues(self.expenses_all, [
|
||||
{'state': 'approved', 'account_move_id': False},
|
||||
{'state': 'approved', 'account_move_id': False},
|
||||
])
|
||||
|
||||
# STEP 2: ER posted with draft move -> Cancel move (nothing changes)
|
||||
self.expense_states_employee_sheet.account_move_id.button_cancel()
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.expense_line_ids, [
|
||||
{'state': 'approved'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet, [
|
||||
{'state': 'post', 'payment_state': 'not_paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.account_move_id, [
|
||||
{'state': 'cancel'},
|
||||
])
|
||||
def test_expense_state_synchro_1_reverse_move(self):
|
||||
""" Posted -> Reverse move (Back to approved) """
|
||||
self.expenses_all.action_submit()
|
||||
self.expenses_all.action_approve()
|
||||
self.post_expenses_with_wizard(self.expenses_all)
|
||||
|
||||
# Change move state to draft
|
||||
self.expense_states_employee_sheet.account_move_id.button_draft()
|
||||
|
||||
# STEP 3: ER posted with draft move -> unlink move (Reverts to approve state)
|
||||
self.expense_states_employee_sheet.account_move_id.unlink()
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.expense_line_ids, [
|
||||
{'state': 'approved'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet, [
|
||||
{'state': 'approve', 'payment_state': 'not_paid'},
|
||||
])
|
||||
self.assertFalse(self.expense_states_employee_sheet.account_move_id)
|
||||
|
||||
# Re-create posted move
|
||||
self.expense_states_employee_sheet.action_sheet_move_create()
|
||||
|
||||
# STEP 4: ER with draft move -> Reverse move (Reverts to approve state)
|
||||
self.expense_states_employee_sheet.account_move_id._reverse_moves(
|
||||
default_values_list=[{'invoice_date': fields.Date.context_today(self.expense_states_employee_sheet)}],
|
||||
self.expenses_all.account_move_id._reverse_moves(
|
||||
default_values_list=[{'invoice_date': fields.Date.context_today(self.expenses_all)}],
|
||||
cancel=True,
|
||||
)
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.expense_line_ids, [
|
||||
{'state': 'approved'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet, [
|
||||
{'state': 'approve', 'payment_state': 'not_paid'},
|
||||
])
|
||||
self.assertFalse(self.expense_states_employee_sheet.account_move_id)
|
||||
|
||||
# Change the report state to a partially paid one
|
||||
self.expense_states_employee_sheet.action_sheet_move_create()
|
||||
action_context = self.expense_states_employee_sheet.action_register_payment()['context']
|
||||
self.env['account.payment.register'].with_context(action_context).create({'amount': 1})._create_payments()
|
||||
|
||||
# STEP 5: ER Done (partially paid) -> Reset move to draft
|
||||
self.expense_states_employee_sheet.account_move_id.button_draft()
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.expense_line_ids, [
|
||||
{'state': 'approved'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet, [
|
||||
{'state': 'post', 'payment_state': 'not_paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.account_move_id, [
|
||||
{'state': 'draft'},
|
||||
self.assertRecordValues(self.expenses_all, [
|
||||
{'state': 'approved', 'account_move_id': False},
|
||||
{'state': 'approved', 'account_move_id': False},
|
||||
])
|
||||
|
||||
# Re-post the move & partially pay it
|
||||
self.expense_states_employee_sheet.account_move_id.action_post()
|
||||
action_context = self.expense_states_employee_sheet.action_register_payment()['context']
|
||||
self.env['account.payment.register'].with_context(action_context).create({'amount': 1})._create_payments()
|
||||
def test_expense_state_synchro_2_employee_specific_flow_1(self):
|
||||
""" Posted -> Reset move to draft (No change)"""
|
||||
self.expenses_employee.action_submit()
|
||||
self.expenses_employee.action_approve()
|
||||
self.post_expenses_with_wizard(self.expenses_employee)
|
||||
|
||||
# STEP 6: ER Done (partially paid) -> fully paid
|
||||
action_context = self.expense_states_employee_sheet.action_register_payment()['context']
|
||||
self.env['account.payment.register'].with_context(action_context).create(
|
||||
{'amount': self.expense_states_employee_sheet.amount_residual}
|
||||
)._create_payments()
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.expense_line_ids, [
|
||||
{'state': 'done'},
|
||||
self.expenses_employee.account_move_id.button_draft()
|
||||
self.assertEqual(self.expenses_employee.state, 'posted')
|
||||
self.assertRecordValues(self.expenses_employee.account_move_id, [
|
||||
{'state': 'draft', 'payment_state': 'not_paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet, [
|
||||
{'state': 'done', 'payment_state': self.paid_or_in_payment_state},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.account_move_id, [
|
||||
|
||||
def test_expense_state_synchro_2_employee_specific_flow_2(self):
|
||||
""" Posted -> Paid in one payment (Set to paid) """
|
||||
self.expenses_employee.action_submit()
|
||||
self.expenses_employee.action_approve()
|
||||
self.post_expenses_with_wizard(self.expenses_employee)
|
||||
|
||||
self.get_new_payment(self.expenses_employee, self.expenses_employee.total_amount)
|
||||
|
||||
self.assertEqual(self.expenses_employee.state, self.paid_or_in_payment_state)
|
||||
self.assertRecordValues(self.expenses_employee.account_move_id, [
|
||||
{'state': 'posted', 'payment_state': self.paid_or_in_payment_state},
|
||||
])
|
||||
|
||||
# STEP 7: ER Done (fully paid) -> Reset move to draft
|
||||
self.expense_states_employee_sheet.account_move_id.button_draft()
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.expense_line_ids, [
|
||||
{'state': 'approved'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet, [
|
||||
{'state': 'post', 'payment_state': 'not_paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.account_move_id, [
|
||||
{'state': 'draft'},
|
||||
def test_expense_state_synchro_2_employee_specific_flow_3(self):
|
||||
""" Posted -> Paid in several payment (Set to paid, even when partially)"""
|
||||
self.expenses_employee.action_submit()
|
||||
self.expenses_employee.action_approve()
|
||||
self.post_expenses_with_wizard(self.expenses_employee)
|
||||
|
||||
self.get_new_payment(self.expenses_employee, 1)
|
||||
|
||||
self.assertEqual(self.expenses_employee.state, self.paid_or_in_payment_state)
|
||||
self.assertRecordValues(self.expenses_employee.account_move_id, [
|
||||
{'state': 'posted', 'payment_state': 'partial'},
|
||||
])
|
||||
|
||||
# Change the report state to a paid one
|
||||
self.expense_states_employee_sheet.account_move_id.unlink()
|
||||
self.expense_states_employee_sheet.action_sheet_move_create()
|
||||
action_context = self.expense_states_employee_sheet.action_register_payment()['context']
|
||||
payment = self.env['account.payment.register'].with_context(action_context).create({})._create_payments()
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.expense_line_ids, [
|
||||
{'state': 'done'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet, [
|
||||
{'state': 'done', 'payment_state': self.paid_or_in_payment_state},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.account_move_id, [
|
||||
{'state': 'posted'},
|
||||
self.get_new_payment(self.expenses_employee, self.expenses_employee.total_amount - 1)
|
||||
|
||||
self.assertEqual(self.expenses_employee.state, self.paid_or_in_payment_state)
|
||||
self.assertRecordValues(self.expenses_employee.account_move_id, [
|
||||
{'state': 'posted', 'payment_state': self.paid_or_in_payment_state},
|
||||
])
|
||||
|
||||
# STEP 8: ER Done (fully paid) -> Reset to draft payment (Reverts to post state)
|
||||
payment.action_draft()
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.expense_line_ids, [
|
||||
{'state': 'approved'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet, [
|
||||
{'state': 'post', 'payment_state': 'not_paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_employee_sheet.account_move_id, [
|
||||
{'state': 'posted'},
|
||||
def test_expense_state_synchro_2_employee_specific_flow_4(self):
|
||||
""" (Partially/) Paid -> Reset move to draft (Back to posted) """
|
||||
self.expenses_employee.action_submit()
|
||||
self.expenses_employee.action_approve()
|
||||
self.post_expenses_with_wizard(self.expenses_employee)
|
||||
|
||||
self.get_new_payment(self.expenses_employee, self.expenses_employee.total_amount)
|
||||
|
||||
self.expenses_employee.account_move_id.button_draft()
|
||||
self.expenses_employee.account_move_id.line_ids.remove_move_reconcile()
|
||||
self.assertEqual(self.expenses_employee.state, 'posted')
|
||||
self.assertRecordValues(self.expenses_employee.account_move_id, [
|
||||
{'state': 'draft', 'payment_state': 'not_paid'},
|
||||
])
|
||||
|
||||
def test_expense_state_synchro_3_company_specific_flow(self):
|
||||
self.expense_states_company_sheet.action_submit_sheet()
|
||||
self.expense_states_company_sheet.approve_expense_sheets()
|
||||
self.expense_states_company_sheet.action_sheet_move_create()
|
||||
""" Paid -> Reset move or payment to draft (Stay paid) """
|
||||
self.expenses_company.action_submit()
|
||||
self.expenses_company.action_approve()
|
||||
self.expenses_company.action_post()
|
||||
|
||||
# STEP 1: ER Done & paid -> Reset move or payment to draft (nothing changes)
|
||||
self.expense_states_company_sheet.account_move_id.button_draft()
|
||||
self.assertRecordValues(self.expense_states_company_sheet.expense_line_ids, [
|
||||
{'state': 'done'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet, [
|
||||
{'state': 'done', 'payment_state': 'paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet.account_move_id, [
|
||||
{'state': 'draft'},
|
||||
self.expenses_company.account_move_id.button_draft()
|
||||
self.assertEqual(self.expenses_company.state, 'paid')
|
||||
self.assertRecordValues(self.expenses_company.account_move_id, [
|
||||
{'state': 'draft', 'payment_state': 'not_paid'},
|
||||
])
|
||||
|
||||
self.expense_states_company_sheet.account_move_id.action_post()
|
||||
self.expense_states_company_sheet.account_move_id.payment_id.action_draft()
|
||||
self.assertRecordValues(self.expense_states_company_sheet.expense_line_ids, [
|
||||
{'state': 'done'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet, [
|
||||
{'state': 'done', 'payment_state': 'paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet.account_move_id, [
|
||||
{'state': 'draft'},
|
||||
self.expenses_company.account_move_id.action_post()
|
||||
self.assertEqual(self.expenses_company.state, 'paid')
|
||||
|
||||
self.expenses_company.account_move_id.origin_payment_id.action_draft()
|
||||
self.assertEqual(self.expenses_company.state, 'paid')
|
||||
self.assertRecordValues(self.expenses_company.account_move_id, [
|
||||
{'state': 'draft', 'payment_state': 'not_paid'},
|
||||
])
|
||||
|
||||
# STEP 2: ER Done & paid (draft move) -> Cancel move or payment (nothing changes)
|
||||
self.expense_states_company_sheet.account_move_id.button_cancel()
|
||||
self.assertRecordValues(self.expense_states_company_sheet.expense_line_ids, [
|
||||
{'state': 'done'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet, [
|
||||
{'state': 'done', 'payment_state': 'paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet.account_move_id, [
|
||||
{'state': 'cancel'},
|
||||
])
|
||||
self.expense_states_company_sheet.account_move_id.button_draft()
|
||||
self.expense_states_company_sheet.account_move_id.payment_id.action_cancel()
|
||||
self.assertRecordValues(self.expense_states_company_sheet.expense_line_ids, [
|
||||
{'state': 'done'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet, [
|
||||
{'state': 'done', 'payment_state': 'paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet.account_move_id, [
|
||||
{'state': 'cancel'},
|
||||
])
|
||||
def test_expense_state_autovalidation(self):
|
||||
""" Test the auto-validation flow skips 'submitted' state when there is no manager"""
|
||||
self.expense_employee.sudo().expense_manager_id = False
|
||||
self.expenses_all.sudo().manager_id = False
|
||||
self.expenses_all.action_submit()
|
||||
self.assertSequenceEqual(['approved', 'approved'], self.expenses_all.mapped('state'))
|
||||
|
||||
# Change move state to draft
|
||||
self.expense_states_company_sheet.account_move_id.button_draft()
|
||||
|
||||
# STEP 3: ER draft & paid -> Delete move (Back to approve state)
|
||||
self.expense_states_company_sheet.account_move_id.unlink()
|
||||
self.assertRecordValues(self.expense_states_company_sheet.expense_line_ids, [
|
||||
{'state': 'approved'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet, [
|
||||
{'state': 'approve', 'payment_state': 'not_paid'},
|
||||
])
|
||||
self.assertFalse(self.expense_states_company_sheet.account_move_id)
|
||||
|
||||
# Re-create posted move
|
||||
self.expense_states_company_sheet.action_sheet_move_create()
|
||||
self.assertRecordValues(self.expense_states_company_sheet.expense_line_ids, [
|
||||
{'state': 'done'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet, [
|
||||
{'payment_mode': 'company_account', 'state': 'done', 'payment_state': 'paid'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet.account_move_id, [
|
||||
{'state': 'posted'},
|
||||
])
|
||||
|
||||
# STEP 4: ER Done & paid -> Reverse move (Change payment state to 'reversed')
|
||||
self.expense_states_company_sheet.account_move_id._reverse_moves(
|
||||
default_values_list=[{'invoice_date': fields.Date.context_today(self.expense_states_company_sheet)}],
|
||||
cancel=True,
|
||||
)
|
||||
self.assertRecordValues(self.expense_states_company_sheet.expense_line_ids, [
|
||||
{'state': 'done'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet, [
|
||||
{'state': 'done', 'payment_state': 'reversed'},
|
||||
])
|
||||
self.assertRecordValues(self.expense_states_company_sheet.account_move_id, [
|
||||
{'state': 'posted'},
|
||||
])
|
||||
def test_expense_next_activity(self):
|
||||
""" Test next activity is assigned to the right manager, no notification is sent, but validation email is sent"""
|
||||
self.expenses_employee.manager_id = self.expense_user_manager_2
|
||||
with self.mock_mail_gateway():
|
||||
self.expenses_employee.action_submit()
|
||||
self.env['hr.expense']._cron_send_submitted_expenses_mail()
|
||||
mail_activity = self.env['mail.activity'].search([('res_model', '=', 'hr.expense'), ('res_id', '=', self.expenses_employee.id)])
|
||||
self.assertEqual(mail_activity.user_id.id, self.expense_user_manager_2.id)
|
||||
# No notification should be sent
|
||||
notification_message = self.env['mail.message'].search([('partner_ids', 'in', self.expense_user_manager_2.partner_id.ids), ('display_name', '=', mail_activity.res_name)])
|
||||
self.assertFalse(notification_message)
|
||||
# Expenses submitted email is sent via cron
|
||||
expenses_submitted = self.env['mail.mail'].search(
|
||||
[('email_to', '=', self.expense_user_manager_2.email),
|
||||
('subject', '=', "New expenses waiting for your approval")]
|
||||
)
|
||||
self.assertEqual(len(expenses_submitted), 1)
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
from odoo.tests import tagged, HttpCase
|
||||
|
||||
|
||||
@tagged('post_install', '-at_install')
|
||||
class TestExpensesTour(HttpCase):
|
||||
def test_tour_expenses(self):
|
||||
self.start_tour("/web", "hr_expense_test_tour", login="admin")
|
||||
self.start_tour("/odoo", "hr_expense_test_tour", login="admin")
|
||||
|
|
|
|||
33
odoo-bringout-oca-ocb-hr_expense/hr_expense/tests/test_ui.py
Normal file
33
odoo-bringout-oca-ocb-hr_expense/hr_expense/tests/test_ui.py
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
from odoo.addons.hr_expense.tests.common import TestExpenseCommon
|
||||
from odoo.tests import tagged, HttpCase
|
||||
from odoo.tools import mute_logger
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestUi(TestExpenseCommon, HttpCase):
|
||||
browser_size = "1920,1080"
|
||||
|
||||
def test_expense_manager_can_always_set_employee(self):
|
||||
"""Test that users with access rights to `hr.expense` can set the employee on them
|
||||
by using the usual form view, even if they do not have access rights to `hr.employee`
|
||||
"""
|
||||
employee_1 = self.expense_employee
|
||||
employee_2 = self.env['hr.employee'].sudo().create({'name': 'employee2'})
|
||||
expense = self.env['hr.expense'].create({
|
||||
'name': 'expense_for_tour_0',
|
||||
'employee_id': employee_2.id,
|
||||
'product_id': self.product_c.id,
|
||||
'total_amount': 1,
|
||||
})
|
||||
self.start_tour('/odoo', 'create_expense_no_employee_access_tour', login=self.expense_user_manager.login)
|
||||
self.assertEqual(expense.employee_id.id, employee_1.id, "Employee should have been changed by tour")
|
||||
|
||||
def test_no_zero_amount_expense_in_expense(self):
|
||||
"""
|
||||
The test ensures that attempting to submit an expense with a zero amount fails as expected
|
||||
and that a valid amount can be set subsequently.
|
||||
"""
|
||||
expense = self.create_expenses({'name': 'expense_for_tour'})
|
||||
with mute_logger("odoo.http"):
|
||||
self.start_tour('/odoo', 'do_not_create_zero_amount_expense', login=self.expense_user_manager.login)
|
||||
self.assertEqual(expense.total_amount_currency, 10.0, "Expense amount should have been set by tour")
|
||||
Loading…
Add table
Add a link
Reference in a new issue