[IMP] hr_expense,sale_expense: Remove dependency to AccountTestCommon
l10n runbot builds are all failing when running at least one test depending of AccountTestCommon because it: - doesn't create a sandboxed testing environnement to manage the multi-currency, multi-company, the default company's currency, the exchange rates... - doesn't setup a testing user then all tests are done using the superuser. - doesn't provide a fully setup chart of accounts: exchange difference journal is not set, accounts have bad types, etc... - is run sometimes at-install. --task: 2301180
This commit is contained in:
@@ -3,5 +3,6 @@
|
||||
|
||||
from . import common
|
||||
from . import test_expenses
|
||||
from . import test_expenses_mail_subjects
|
||||
from . import test_multicompany
|
||||
from . import test_expenses_access_rights
|
||||
from . import test_expenses_mail_import
|
||||
from . import test_expenses_multi_company
|
||||
|
||||
@@ -1,225 +1,53 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.addons.account.tests.common import AccountTestNoChartCommon, AccountTestNoChartCommonMultiCompany
|
||||
from odoo.addons.account.tests.common import AccountTestInvoicingCommon
|
||||
|
||||
|
||||
class TestExpenseCommon(AccountTestNoChartCommon):
|
||||
class TestExpenseCommon(AccountTestInvoicingCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestExpenseCommon, cls).setUpClass()
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
|
||||
cls.setUpUsers()
|
||||
Users = cls.env['res.users'].with_context({'no_reset_password': True, 'mail_create_nosubscribe': True, 'mail_create_nolog': True})
|
||||
group_user = cls.env.ref('base.group_user')
|
||||
group_expense_manager = cls.env.ref('hr_expense.group_hr_expense_manager')
|
||||
|
||||
# The user manager is only expense manager
|
||||
user_group_manager = cls.env.ref('hr_expense.group_hr_expense_manager')
|
||||
cls.user_manager.write({
|
||||
'groups_id': [(6, 0, [user_group_manager.id, cls.env.ref('base.group_user').id])],
|
||||
cls.expense_user_employee = Users.create({
|
||||
'name': 'expense_user_employee',
|
||||
'login': 'expense_user_employee',
|
||||
'email': 'expense_user_employee@example.com',
|
||||
'notification_type': 'email',
|
||||
'groups_id': [(6, 0, group_user.ids)],
|
||||
'company_ids': [(6, 0, cls.env.companies.ids)],
|
||||
})
|
||||
cls.expense_user_manager = Users.create({
|
||||
'name': 'Expense manager',
|
||||
'login': 'expense_manager_1',
|
||||
'email': 'expense_manager_1@example.com',
|
||||
'notification_type': 'email',
|
||||
'groups_id': [(6, 0, (group_user + group_expense_manager).ids)],
|
||||
'company_ids': [(6, 0, cls.env.companies.ids)],
|
||||
})
|
||||
|
||||
# create employee
|
||||
cls.employee = cls.env['hr.employee'].create({
|
||||
'name': 'Johnny Employee',
|
||||
'user_id': cls.user_employee.id,
|
||||
'address_home_id': cls.user_employee.partner_id.id,
|
||||
'address_id': cls.user_employee.partner_id.id,
|
||||
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,
|
||||
})
|
||||
|
||||
# Create tax
|
||||
cls.tax = cls.env['account.tax'].create({
|
||||
'name': 'Expense 10%',
|
||||
'amount': 10,
|
||||
'amount_type': 'percent',
|
||||
'type_tax_use': 'purchase',
|
||||
'price_include': True,
|
||||
})
|
||||
# Allow the current accounting user to access the expenses.
|
||||
cls.env.user.groups_id |= group_expense_manager
|
||||
|
||||
# Create analytic account
|
||||
cls.analytic_account = cls.env['account.analytic.account'].create({
|
||||
'name': 'Test Analytic Account for Expenses',
|
||||
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',
|
||||
})
|
||||
|
||||
# Expense reports
|
||||
cls.journal = cls.env['account.journal'].create({
|
||||
'name': 'Purchase Journal - Test',
|
||||
'code': 'HRTPJ',
|
||||
'type': 'purchase',
|
||||
'company_id': cls.env.company.id,
|
||||
})
|
||||
cls.expense_sheet = cls.env['hr.expense.sheet'].create({
|
||||
'name': 'Expense for Johnny Employee',
|
||||
'employee_id': cls.employee.id,
|
||||
'journal_id': cls.journal.id,
|
||||
})
|
||||
cls.expense_sheet2 = cls.env['hr.expense.sheet'].create({
|
||||
'name': 'Second Expense for Johnny Employee',
|
||||
'employee_id': cls.employee.id,
|
||||
'journal_id': cls.journal.id,
|
||||
})
|
||||
|
||||
Users = cls.env['res.users'].with_context(no_reset_password=True)
|
||||
|
||||
# Find Employee group
|
||||
group_employee_id = cls.env.ref('base.group_user').id
|
||||
|
||||
cls.user_emp2 = Users.create({
|
||||
'name': 'Superboy Employee',
|
||||
'login': 'superboy',
|
||||
'email': 'superboy@example.com',
|
||||
'groups_id': [(6, 0, [group_employee_id])]
|
||||
})
|
||||
|
||||
cls.user_officer = Users.create({
|
||||
'name': 'Batman Officer',
|
||||
'login': 'batman',
|
||||
'email': 'batman.hero@example.com',
|
||||
'groups_id': [(6, 0, [group_employee_id, cls.env.ref('hr_expense.group_hr_expense_team_approver').id])]
|
||||
})
|
||||
|
||||
cls.emp_emp2 = cls.env['hr.employee'].create({
|
||||
'name': 'Superboy',
|
||||
'user_id': cls.user_emp2.id,
|
||||
})
|
||||
|
||||
cls.emp_officer = cls.env['hr.employee'].create({
|
||||
'name': 'Batman',
|
||||
'user_id': cls.user_officer.id,
|
||||
})
|
||||
|
||||
cls.emp_manager = cls.env['hr.employee'].create({
|
||||
'name': 'Superman',
|
||||
'user_id': cls.user_manager.id,
|
||||
})
|
||||
|
||||
cls.rd = cls.env['hr.department'].create({
|
||||
'name': 'R&D',
|
||||
'manager_id': cls.emp_officer.id,
|
||||
'member_ids': [(6, 0, [cls.employee.id])],
|
||||
})
|
||||
|
||||
cls.ps = cls.env['hr.department'].create({
|
||||
'name': 'PS',
|
||||
'manager_id': cls.emp_manager.id,
|
||||
'member_ids': [(6, 0, [cls.emp_emp2.id])],
|
||||
})
|
||||
|
||||
cls.uom_unit = cls.env.ref('uom.product_uom_unit').id
|
||||
cls.uom_dozen = cls.env.ref('uom.product_uom_dozen').id
|
||||
|
||||
cls.product_1 = cls.env['product.product'].create({
|
||||
'name': 'Batmobile repair',
|
||||
'type': 'service',
|
||||
'uom_id': cls.uom_unit,
|
||||
'uom_po_id': cls.uom_unit,
|
||||
})
|
||||
|
||||
cls.product_2 = cls.env['product.product'].create({
|
||||
'name': 'Superboy costume washing',
|
||||
'type': 'service',
|
||||
'uom_id': cls.uom_unit,
|
||||
'uom_po_id': cls.uom_unit,
|
||||
})
|
||||
|
||||
|
||||
class TestExpenseMultiCompanyCommon(AccountTestNoChartCommonMultiCompany):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestExpenseMultiCompanyCommon, cls).setUpClass()
|
||||
|
||||
cls.setUpAdditionalAccounts()
|
||||
cls.setUpUsers()
|
||||
|
||||
# The user manager is only expense manager
|
||||
user_group_manager = cls.env.ref('hr_expense.group_hr_expense_manager')
|
||||
cls.user_manager.write({
|
||||
'groups_id': [(6, 0, [user_group_manager.id, cls.env.ref('base.group_user').id])],
|
||||
})
|
||||
cls.user_manager_company_B.write({
|
||||
'groups_id': [(6, 0, [user_group_manager.id, cls.env.ref('base.group_user').id])],
|
||||
})
|
||||
|
||||
# create employee
|
||||
cls.employee = cls.env['hr.employee'].create({
|
||||
'name': 'Tyrion Lannister',
|
||||
'user_id': cls.user_employee.id,
|
||||
'address_home_id': cls.user_employee.partner_id.id,
|
||||
'address_id': cls.user_employee.partner_id.id,
|
||||
})
|
||||
|
||||
cls.employee_company_B = cls.env['hr.employee'].create({
|
||||
'name': 'Gregor Clegane',
|
||||
'user_id': cls.user_employee_company_B.id,
|
||||
'address_home_id': cls.user_employee_company_B.partner_id.id,
|
||||
'address_id': cls.user_employee_company_B.partner_id.id,
|
||||
})
|
||||
|
||||
# Create tax
|
||||
cls.tax = cls.env['account.tax'].create({
|
||||
'name': 'Expense 10%',
|
||||
'amount': 10,
|
||||
'amount_type': 'percent',
|
||||
'type_tax_use': 'purchase',
|
||||
'price_include': True,
|
||||
'company_id': cls.env.company.id
|
||||
})
|
||||
cls.tax_company_B = cls.env['account.tax'].create({
|
||||
'name': 'Expense 10%',
|
||||
'amount': 10,
|
||||
'amount_type': 'percent',
|
||||
'type_tax_use': 'purchase',
|
||||
'price_include': True,
|
||||
'company_id': cls.company_B.id
|
||||
})
|
||||
|
||||
# Create analytic account
|
||||
cls.analytic_account = cls.env['account.analytic.account'].create({
|
||||
'name': 'Test Analytic Account for Expenses',
|
||||
'company_id': cls.env.company.id,
|
||||
})
|
||||
cls.analytic_account_company_B = cls.env['account.analytic.account'].create({
|
||||
'name': 'Test Analytic Account for Expenses',
|
||||
'company_id': cls.company_B.id,
|
||||
})
|
||||
|
||||
# Expense reports
|
||||
cls.journal = cls.env['account.journal'].create({
|
||||
'name': 'Purchase Journal - Test',
|
||||
'code': 'HRTPJ',
|
||||
'type': 'purchase',
|
||||
'company_id': cls.env.company.id,
|
||||
})
|
||||
cls.journal_company_B = cls.env['account.journal'].create({
|
||||
'name': 'Purchase Journal Company B - Test',
|
||||
'code': 'HRTPJ',
|
||||
'type': 'purchase',
|
||||
'company_id': cls.company_B.id,
|
||||
})
|
||||
|
||||
cls.expense_sheet = cls.env['hr.expense.sheet'].create({
|
||||
'name': 'Expense for Tyrion',
|
||||
'employee_id': cls.employee.id,
|
||||
'journal_id': cls.journal.id,
|
||||
})
|
||||
cls.expense_sheet2 = cls.env['hr.expense.sheet'].create({
|
||||
'name': 'Second Expense for Tyrion',
|
||||
'employee_id': cls.employee.id,
|
||||
'journal_id': cls.journal.id,
|
||||
})
|
||||
|
||||
cls.product_1 = cls.env['product.product'].create({
|
||||
'name': 'Sword sharpening',
|
||||
'type': 'service',
|
||||
'uom_id': cls.env.ref('uom.product_uom_unit').id,
|
||||
'uom_po_id': cls.env.ref('uom.product_uom_unit').id,
|
||||
'property_account_expense_id': cls.account_expense.id,
|
||||
})
|
||||
|
||||
cls.product_2 = cls.env['product.product'].create({
|
||||
'name': 'Armor cleaning',
|
||||
'type': 'service',
|
||||
'uom_id': cls.env.ref('uom.product_uom_unit').id,
|
||||
'uom_po_id': cls.env.ref('uom.product_uom_unit').id,
|
||||
'property_account_expense_id': cls.account_expense.id,
|
||||
})
|
||||
# Ensure products can be expensed.
|
||||
(cls.product_a + cls.product_b).write({'can_be_expensed': True})
|
||||
|
||||
@@ -1,469 +1,140 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||
|
||||
from odoo.exceptions import UserError, AccessError
|
||||
|
||||
from odoo.addons.hr_expense.tests.common import TestExpenseCommon
|
||||
from odoo.tests import tagged
|
||||
from odoo import fields
|
||||
|
||||
|
||||
class TestAccountEntry(TestExpenseCommon):
|
||||
"""
|
||||
Check journal entries when the expense product is having tax which is tax included.
|
||||
"""
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestExpenses(TestExpenseCommon):
|
||||
|
||||
def setUp(self):
|
||||
super(TestAccountEntry, self).setUp()
|
||||
|
||||
self.setUpAdditionalAccounts()
|
||||
|
||||
self.product_expense = self.env['product.product'].create({
|
||||
'name': "Delivered at cost",
|
||||
'standard_price': 700,
|
||||
'list_price': 700,
|
||||
'type': 'consu',
|
||||
'supplier_taxes_id': [(6, 0, [self.tax.id])],
|
||||
'default_code': 'CONSU-DELI-COST',
|
||||
'taxes_id': False,
|
||||
'property_account_expense_id': self.account_expense.id,
|
||||
})
|
||||
|
||||
def test_account_entry(self):
|
||||
def test_expense_values(self):
|
||||
""" Checking accounting move entries and analytic entries when submitting expense """
|
||||
expense = self.env['hr.expense.sheet'].create({
|
||||
'name': 'Expense for John Smith',
|
||||
'employee_id': self.employee.id,
|
||||
})
|
||||
expense_line = self.env['hr.expense'].create({
|
||||
'name': 'Car Travel Expenses',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_expense.id,
|
||||
'unit_amount': 700.00,
|
||||
'tax_ids': [(6, 0, [self.tax.id])],
|
||||
'sheet_id': expense.id,
|
||||
'analytic_account_id': self.analytic_account.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')
|
||||
|
||||
# [(line.debit, line.credit, line.tax_line_id.id) for line in self.expense.expense_line_ids.account_move_id.line_ids]
|
||||
# should git this result [(0.0, 700.0, False), (63.64, 0.0, 179), (636.36, 0.0, False)]
|
||||
for line in expense.account_move_id.line_ids:
|
||||
if line.credit:
|
||||
self.assertAlmostEqual(line.credit, 700.00)
|
||||
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 == self.tax:
|
||||
self.assertAlmostEqual(line.debit, 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_expense, "Product of debit move line should be the one from the expense")
|
||||
else:
|
||||
self.assertAlmostEqual(line.debit, 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")
|
||||
# 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.
|
||||
|
||||
self.assertEqual(self.analytic_account.line_ids, expense.account_move_id.mapped('line_ids.analytic_line_ids'))
|
||||
self.assertEqual(len(self.analytic_account.line_ids), 1, "Analytic Account should have only one line")
|
||||
self.assertAlmostEqual(self.analytic_account.line_ids[0].amount, -636.36, "Amount on the only AAL is wrong")
|
||||
self.assertEqual(self.analytic_account.line_ids[0].product_id, self.product_expense, "Product of AAL should be the one from the expense")
|
||||
|
||||
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_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,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
expense = self.env['hr.expense.sheet'].create({
|
||||
'name': 'Expense for Dick Tracy',
|
||||
'employee_id': self.employee.id,
|
||||
})
|
||||
expense_line = self.env['hr.expense'].create({
|
||||
'name': 'Choucroute Saucisse',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_expense.id,
|
||||
'unit_amount': 700.00,
|
||||
'tax_ids': [(6, 0, [self.tax.id])],
|
||||
'sheet_id': expense.id,
|
||||
'analytic_account_id': self.analytic_account.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')
|
||||
# Check expense sheet values.
|
||||
self.assertRecordValues(expense_sheet, [{'state': 'draft', 'total_amount': 1725.0}])
|
||||
|
||||
# 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 == self.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_expense, "Product of debit move line should be the one from the expense")
|
||||
else:
|
||||
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")
|
||||
expense_sheet.action_submit_sheet()
|
||||
expense_sheet.approve_expense_sheets()
|
||||
expense_sheet.action_sheet_move_create()
|
||||
|
||||
self.assertEqual(self.analytic_account.line_ids, expense.account_move_id.mapped('line_ids.analytic_line_ids'))
|
||||
self.assertEqual(len(self.analytic_account.line_ids), 1, "Analytic Account should have only one line")
|
||||
self.assertAlmostEqual(self.analytic_account.line_ids[0].amount, -318.18, "Amount on the only AAL is wrong")
|
||||
self.assertAlmostEqual(self.analytic_account.line_ids[0].currency_id, self.env.company.currency_id, "Currency on the only AAL is wrong")
|
||||
self.assertEqual(self.analytic_account.line_ids[0].product_id, self.product_expense, "Product of AAL should be the one from the expense")
|
||||
# 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': 0.0,
|
||||
'account_id': self.company_data['default_account_payable'].id,
|
||||
'product_id': False,
|
||||
'currency_id': False,
|
||||
'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': 0.0,
|
||||
'account_id': self.company_data['default_account_tax_purchase'].id,
|
||||
'product_id': False,
|
||||
'currency_id': False,
|
||||
'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': 0.0,
|
||||
'account_id': self.company_data['default_account_expense'].id,
|
||||
'product_id': self.product_a.id,
|
||||
'currency_id': False,
|
||||
'tax_line_id': False,
|
||||
'analytic_account_id': self.analytic_account_1.id,
|
||||
},
|
||||
])
|
||||
|
||||
def test_expense_from_email(self):
|
||||
user_marc = self.env['res.users'].create({
|
||||
'name': 'Marc User',
|
||||
'login': 'Marc',
|
||||
'email': 'marc.user@example.com',
|
||||
})
|
||||
self.env['hr.employee'].create({
|
||||
'name': 'Marc Demo',
|
||||
'user_id': user_marc.id,
|
||||
})
|
||||
air_ticket = self.env['product.product'].create({
|
||||
'name': 'Air Flight',
|
||||
'type': 'service',
|
||||
'default_code': 'TESTREF',
|
||||
'can_be_expensed': True,
|
||||
})
|
||||
|
||||
self.tax.price_include = False
|
||||
|
||||
message_parsed = {
|
||||
'message_id': 'the-world-is-a-ghetto',
|
||||
'subject': 'TESTREF 9876',
|
||||
'email_from': 'marc.user@example.com',
|
||||
'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.assertEqual(expense.product_id, air_ticket)
|
||||
self.assertEqual(expense.tax_ids.ids, [])
|
||||
self.assertEqual(expense.total_amount, 9876.0)
|
||||
self.assertTrue(expense.employee_id in user_marc.employee_ids)
|
||||
|
||||
def test_expense_from_email_without_product(self):
|
||||
user_marc = self.env['res.users'].create({
|
||||
'name': 'Marc User',
|
||||
'login': 'Marc',
|
||||
'email': 'marc.user@example.com',
|
||||
})
|
||||
self.env['hr.employee'].create({
|
||||
'name': 'Marc Demo',
|
||||
'user_id': user_marc.id,
|
||||
})
|
||||
|
||||
self.tax.price_include = False
|
||||
|
||||
message_parsed = {
|
||||
'message_id': 'the-world-is-a-ghetto',
|
||||
'subject': 'no product code 9876',
|
||||
'email_from': 'marc.user@example.com',
|
||||
'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.assertFalse(expense.product_id, "No product should be linked")
|
||||
self.assertEqual(expense.tax_ids.ids, [])
|
||||
self.assertEqual(expense.total_amount, 9876.0)
|
||||
self.assertTrue(expense.employee_id in user_marc.employee_ids)
|
||||
|
||||
def test_partial_payment_multiexpense(self):
|
||||
bank_journal = self.env['account.journal'].create({
|
||||
'name': 'Payment Journal',
|
||||
'code': 'PAY',
|
||||
'type': 'bank',
|
||||
'company_id': self.env.company.id,
|
||||
})
|
||||
|
||||
outbound_pay_method = self.env['account.payment.method'].create({
|
||||
'name': 'outbound',
|
||||
'code': 'out',
|
||||
'payment_type': 'outbound',
|
||||
})
|
||||
|
||||
expense = self.env['hr.expense.sheet'].create({
|
||||
'name': 'Expense for John Smith',
|
||||
'employee_id': self.employee.id,
|
||||
})
|
||||
expense_line = self.env['hr.expense'].create({
|
||||
'name': 'Car Travel Expenses',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_expense.id,
|
||||
'unit_amount': 200.00,
|
||||
'tax_ids': [(6, 0, [self.tax.id])],
|
||||
'sheet_id': expense.id,
|
||||
'analytic_account_id': self.analytic_account.id,
|
||||
})
|
||||
expense_line.copy({
|
||||
'sheet_id': expense.id
|
||||
})
|
||||
expense.approve_expense_sheets()
|
||||
expense.action_sheet_move_create()
|
||||
|
||||
exp_move_lines = expense.account_move_id.line_ids
|
||||
payable_move_lines = exp_move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
|
||||
self.assertEqual(len(payable_move_lines), 2)
|
||||
|
||||
WizardRegister = self.env["hr.expense.sheet.register.payment.wizard"].with_context(
|
||||
active_model=expense._name, active_id=expense.id, active_ids=expense.ids
|
||||
)
|
||||
|
||||
register_pay1 = WizardRegister.create({
|
||||
'journal_id': bank_journal.id,
|
||||
'payment_method_id': outbound_pay_method.id,
|
||||
'amount': 300,
|
||||
})
|
||||
register_pay1.expense_post_payment()
|
||||
|
||||
exp_move_lines = expense.account_move_id.line_ids
|
||||
payable_move_lines = exp_move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
|
||||
self.assertEqual(len(payable_move_lines.filtered(lambda l: l.reconciled)), 1)
|
||||
|
||||
register_pay2 = WizardRegister.create({
|
||||
'journal_id': bank_journal.id,
|
||||
'payment_method_id': outbound_pay_method.id,
|
||||
'amount': 100,
|
||||
})
|
||||
register_pay2.expense_post_payment()
|
||||
exp_move_lines = expense.account_move_id.line_ids
|
||||
payable_move_lines = exp_move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
|
||||
self.assertEqual(len(payable_move_lines.filtered(lambda l: l.reconciled)), 2)
|
||||
|
||||
full_reconcile = payable_move_lines.mapped('full_reconcile_id')
|
||||
self.assertEqual(len(full_reconcile), 1)
|
||||
|
||||
|
||||
class TestExpenseRights(TestExpenseCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestExpenseRights, cls).setUpClass()
|
||||
|
||||
def test_expense_create(self):
|
||||
# Employee should be able to create an Expense
|
||||
self.env['hr.expense'].with_user(self.user_employee).create({
|
||||
'name': 'Batmobile repair',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_1.id,
|
||||
'unit_amount': 1,
|
||||
'quantity': 1,
|
||||
})
|
||||
|
||||
# Employee should not be able to create an Expense for someone else
|
||||
with self.assertRaises(AccessError):
|
||||
self.env['hr.expense'].with_user(self.user_employee).create({
|
||||
'name': 'Superboy costume washing',
|
||||
'employee_id': self.emp_emp2.id,
|
||||
'product_id': self.product_2.id,
|
||||
'unit_amount': 1,
|
||||
'quantity': 1,
|
||||
})
|
||||
|
||||
def test_expense_approve(self):
|
||||
sheet = self.env['hr.expense.sheet'].create({
|
||||
'name': 'Furnitures',
|
||||
'employee_id': self.emp_officer.id,
|
||||
})
|
||||
|
||||
sheet_2 = self.env['hr.expense.sheet'].create({
|
||||
'name': 'Services',
|
||||
'employee_id': self.employee.id,
|
||||
})
|
||||
|
||||
sheet_3 = self.env['hr.expense.sheet'].create({
|
||||
'name': 'Services 2',
|
||||
'employee_id': self.emp_emp2.id,
|
||||
})
|
||||
|
||||
# Employee should not be able to approve expense sheet
|
||||
with self.assertRaises(UserError):
|
||||
sheet.with_user(self.user_officer).approve_expense_sheets()
|
||||
# Officer should not be able to approve own expense sheet
|
||||
with self.assertRaises(UserError):
|
||||
sheet.with_user(self.user_officer).approve_expense_sheets()
|
||||
sheet.with_user(self.user_manager).approve_expense_sheets()
|
||||
|
||||
# Officer should be able to approve expense from his department
|
||||
sheet_2.with_user(self.user_officer).approve_expense_sheets()
|
||||
|
||||
# Officer should not be able to approve expense sheet from another department
|
||||
with self.assertRaises(AccessError):
|
||||
sheet_3.with_user(self.user_officer).approve_expense_sheets()
|
||||
sheet_3.with_user(self.user_manager).approve_expense_sheets()
|
||||
|
||||
def test_expense_refuse(self):
|
||||
sheet = self.env['hr.expense.sheet'].create({
|
||||
'name': 'Furnitures',
|
||||
'employee_id': self.emp_officer.id,
|
||||
})
|
||||
|
||||
sheet_2 = self.env['hr.expense.sheet'].create({
|
||||
'name': 'Services',
|
||||
'employee_id': self.employee.id,
|
||||
})
|
||||
|
||||
sheet_3 = self.env['hr.expense.sheet'].create({
|
||||
'name': 'Services 2',
|
||||
'employee_id': self.emp_emp2.id,
|
||||
})
|
||||
|
||||
sheet.with_user(self.user_manager).approve_expense_sheets()
|
||||
sheet_2.with_user(self.user_manager).approve_expense_sheets()
|
||||
sheet_3.with_user(self.user_manager).approve_expense_sheets()
|
||||
|
||||
# Employee should not be able to refuse expense sheet
|
||||
with self.assertRaises(UserError):
|
||||
sheet.with_user(self.user_employee).refuse_sheet('')
|
||||
# Officer should not be able to refuse own expense sheet
|
||||
with self.assertRaises(UserError):
|
||||
sheet.with_user(self.user_officer).refuse_sheet('')
|
||||
sheet.with_user(self.user_manager).refuse_sheet('')
|
||||
|
||||
# Officer should be able to refuse expense from his department
|
||||
sheet_2.with_user(self.user_officer).refuse_sheet('')
|
||||
|
||||
# Officer should not be able to refuse expense sheet from another department
|
||||
with self.assertRaises(AccessError):
|
||||
sheet_3.with_user(self.user_officer).refuse_sheet('')
|
||||
sheet_3.with_user(self.user_manager).refuse_sheet('')
|
||||
|
||||
|
||||
class TestExpenseLinesRights(TestExpenseCommon):
|
||||
|
||||
def setUp(self):
|
||||
super(TestExpenseLinesRights, self).setUp()
|
||||
|
||||
self.setUpAdditionalAccounts()
|
||||
|
||||
self.product_expense = self.env['product.product'].create({
|
||||
'name': "Delivered at cost",
|
||||
'standard_price': 700,
|
||||
'list_price': 700,
|
||||
'type': 'consu',
|
||||
'supplier_taxes_id': [(6, 0, [self.tax.id])],
|
||||
'default_code': 'CONSU-DELI-COST',
|
||||
'taxes_id': False,
|
||||
'property_account_expense_id': self.account_expense.id,
|
||||
})
|
||||
|
||||
self.user_manager.write({
|
||||
'groups_id': [(4, self.env.ref('account.group_account_user').id)],
|
||||
})
|
||||
|
||||
def test_expense_lines_rights(self):
|
||||
expense = self.env['hr.expense.sheet'].with_user(self.user_employee).create({
|
||||
'name': 'Expense for John Smith',
|
||||
'employee_id': self.employee.id,
|
||||
})
|
||||
expense_line = self.env['hr.expense'].with_user(self.user_employee).create({
|
||||
'name': 'Car Travel Expenses',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_expense.id,
|
||||
'unit_amount': 700.00,
|
||||
'tax_ids': [(6, 0, [self.tax.id])],
|
||||
'sheet_id': expense.id,
|
||||
'analytic_account_id': self.analytic_account.id,
|
||||
})
|
||||
expense.with_user(self.user_employee).action_submit_sheet()
|
||||
|
||||
# STATE APPROVE
|
||||
|
||||
expense.with_user(self.user_manager).approve_expense_sheets()
|
||||
# Test User without Accountant Rights
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'reference': 'Test Reference'})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'tax_ids': [(6, 0, [self.tax.id])]})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'account_id': self.account_expense.id})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'analytic_account_id': self.analytic_account.id})
|
||||
# Test User with Accountant Rights
|
||||
expense_line.with_user(self.user_manager).write({'reference': 'Test Reference'})
|
||||
expense_line.with_user(self.user_manager).write({'tax_ids': [(6, 0, [self.tax.id])]})
|
||||
expense_line.with_user(self.user_manager).write({'account_id': self.account_expense.id})
|
||||
expense_line.with_user(self.user_manager).write({'analytic_account_id': self.analytic_account.id})
|
||||
expense_line.invalidate_cache()
|
||||
|
||||
# STATE POST
|
||||
|
||||
expense.with_user(self.env.user).action_sheet_move_create()
|
||||
# Test User without Accountant Rights
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'reference': 'Test Reference'})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'tax_ids': [(6, 0, [self.tax.id])]})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'account_id': self.account_expense.id})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'analytic_account_id': self.analytic_account.id})
|
||||
# Test User with Accountant Rights
|
||||
expense_line.with_user(self.user_manager).write({'reference': 'Test Reference'})
|
||||
expense_line.invalidate_cache()
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_manager).write({'tax_ids': [(6, 0, [self.tax.id])]})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_manager).write({'account_id': self.account_expense.id})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_manager).write({'analytic_account_id': self.analytic_account.id})
|
||||
|
||||
# STATE DONE
|
||||
|
||||
expense.set_to_paid()
|
||||
# Test User without Accountant Rights
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'reference': 'Test Reference'})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'tax_ids': [(6, 0, [self.tax.id])]})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'account_id': self.account_expense.id})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_employee).write({'analytic_account_id': self.analytic_account.id})
|
||||
# Test User with Accountant Rights
|
||||
expense_line.with_user(self.user_manager).write({'reference': 'Test Reference'})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_manager).write({'tax_ids': [(6, 0, [self.tax.id])]})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_manager).write({'account_id': self.account_expense.id})
|
||||
with self.assertRaises(UserError):
|
||||
expense_line.with_user(self.user_manager).write({'analytic_account_id': self.analytic_account.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,
|
||||
},
|
||||
])
|
||||
|
||||
@@ -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'}])
|
||||
@@ -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,
|
||||
)
|
||||
@@ -1,103 +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
|
||||
|
||||
|
||||
class TestExpenseSubject(TestExpenseCommon):
|
||||
"""
|
||||
Check subject parsing while registering expense via mail.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestExpenseSubject, self).setUp()
|
||||
self.product_expense = self.env['product.product'].create({
|
||||
'name': "Phone bill",
|
||||
'can_be_expensed': True,
|
||||
'standard_price': 700,
|
||||
'list_price': 700,
|
||||
'type': 'consu',
|
||||
'default_code': 'EXP-PHONE'
|
||||
})
|
||||
|
||||
def test_expense_subjects(self):
|
||||
employee = self.employee
|
||||
employee.user_id = self.user_employee
|
||||
parse_subject = self.env['hr.expense']._parse_expense_subject
|
||||
company_currency = employee.company_id.currency_id
|
||||
alternate_currency = self.env['res.currency'].create({'name': 'AAB', 'symbol': '#'})
|
||||
|
||||
# Without Multi currency access
|
||||
subject = 'EXP-PHONE bar %s1205.91 electro wizard' % (company_currency.symbol,)
|
||||
|
||||
product, price, currency_id, expense_description = parse_subject(subject, company_currency)
|
||||
self.assertEqual(expense_description, 'bar electro wizard', "Should be remove price and product from subject")
|
||||
self.assertAlmostEqual(price, 1205.91, "Price is not fetched correctly")
|
||||
self.assertEqual(currency_id, company_currency, "Should fetch currency correctly")
|
||||
self.assertEqual(product, self.product_expense, "Should fetch product correctly")
|
||||
|
||||
# subject having other currency then company currency, it should ignore other currency then company currency
|
||||
subject = 'foo bar %s1406.91 royal giant' % (alternate_currency.symbol,)
|
||||
product, price, currency_id, expense_description = parse_subject(subject, company_currency)
|
||||
self.assertEqual(expense_description, 'foo bar %s royal giant' % (alternate_currency.symbol,), "Should be remove price and product from subject but not currency symbol")
|
||||
self.assertEqual(currency_id, company_currency, "Should fetch currency correctly")
|
||||
|
||||
# With Multi currency access
|
||||
group_multi_currency = self.env.ref('base.group_multi_currency')
|
||||
self.user_employee.write({
|
||||
'groups_id': [(4, group_multi_currency.id)],
|
||||
})
|
||||
|
||||
subject = 'EXP-PHONE foo bar %s2205.92 elite barbarians' % (company_currency.symbol,) # with product code at start
|
||||
product, price, currency_id, expense_description = parse_subject(subject, company_currency)
|
||||
self.assertEqual(expense_description, 'foo bar elite barbarians', "Should be remove price and product from subject")
|
||||
self.assertAlmostEqual(price, 2205.92, "Price is not fetched correctly")
|
||||
self.assertEqual(currency_id, company_currency, "Should fetch currency correctly")
|
||||
self.assertEqual(product, self.product_expense, "Should fetch product correctly")
|
||||
|
||||
# subject having other currency then company currency, it should accept other currency because multi currency is activated
|
||||
subject = 'EXP-PHONE %s2510.90 chhota bheem' % (alternate_currency.symbol,)
|
||||
product, price, currency_id, expense_description = parse_subject(subject, company_currency | alternate_currency)
|
||||
self.assertEqual(expense_description, 'chhota bheem', "Should be remove price and product from subject but not currency symbol")
|
||||
self.assertAlmostEqual(price, 2510.90, "Price is not fetched correctly")
|
||||
self.assertEqual(currency_id, alternate_currency, "Should fetch currency correctly")
|
||||
self.assertEqual(product, self.product_expense, "Should fetch product correctly")
|
||||
|
||||
# subject without product and currency, should take company currency and default product
|
||||
subject = 'foo bar 109.96 spear goblins'
|
||||
product, price, currency_id, expense_description = parse_subject(subject, company_currency | alternate_currency)
|
||||
self.assertEqual(expense_description, 'foo bar spear goblins', "Should remove price")
|
||||
self.assertAlmostEqual(price, 109.96, "Price is not fetched correctly")
|
||||
self.assertIn(currency_id, company_currency | alternate_currency, "Should fetch company currency")
|
||||
self.assertFalse(product, "Should not have parsed any product")
|
||||
|
||||
# subject with currency symbol at end
|
||||
subject = 'EXP-PHONE foo bar 2910.94%s inferno dragon' % (company_currency.symbol,)
|
||||
product, price, currency_id, expense_description = parse_subject(subject, company_currency | alternate_currency)
|
||||
self.assertEqual(expense_description, 'foo bar inferno dragon', "Should be remove price and product from subject")
|
||||
self.assertAlmostEqual(price, 2910.94, "Price is not fetched correctly")
|
||||
self.assertEqual(currency_id, company_currency, "Should fetch currency correctly")
|
||||
self.assertEqual(product, self.product_expense, "Should fetch product correctly")
|
||||
|
||||
# subject with no amount and product
|
||||
subject = 'foo bar mega knight'
|
||||
product, price, currency_id, expense_description = parse_subject(subject, company_currency | alternate_currency)
|
||||
self.assertEqual(expense_description, 'foo bar mega knight', "Should be same as subject")
|
||||
self.assertAlmostEqual(price, 0.0, "Price is not fetched correctly")
|
||||
self.assertIn(currency_id, company_currency | alternate_currency, "Should fetch currency correctly")
|
||||
self.assertFalse(product, "Should fetch product correctly")
|
||||
|
||||
# price with a comma
|
||||
subject = 'foo bar 291,56%s mega knight' % (company_currency.symbol,)
|
||||
product, price, currency_id, expense_description = parse_subject(subject, company_currency | alternate_currency)
|
||||
self.assertAlmostEqual(price, 291.56, "Price is not fetched correctly")
|
||||
self.assertEqual(currency_id, company_currency, "Should fetch currency correctly")
|
||||
|
||||
# price without decimals
|
||||
subject = 'foo bar 291%s mega knight' % (company_currency.symbol,)
|
||||
product, price, currency_id, expense_description = parse_subject(subject, company_currency | alternate_currency)
|
||||
self.assertAlmostEqual(price, 291.0, "Price is not fetched correctly")
|
||||
|
||||
subject = 'EXP-PHONE 2 foo bar 291.5%s mega knight' % (company_currency.symbol,)
|
||||
product, price, currency_id, expense_description = parse_subject(subject, company_currency | alternate_currency)
|
||||
self.assertAlmostEqual(price, 291.5, "Price is not fetched correctly")
|
||||
@@ -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('')
|
||||
@@ -1,75 +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 TestExpenseMultiCompanyCommon
|
||||
from odoo.tests import tagged
|
||||
|
||||
@tagged('post_install','-at_install')
|
||||
class TestExpenseMultiCompany(TestExpenseMultiCompanyCommon):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestExpenseMultiCompany, cls).setUpClass()
|
||||
|
||||
cls.bank_journal = cls.env['account.journal'].create({
|
||||
'name': 'Payment Journal',
|
||||
'code': 'PAY',
|
||||
'type': 'bank',
|
||||
'company_id': cls.env.company.id,
|
||||
})
|
||||
|
||||
cls.outbound_pay_method = cls.env['account.payment.method'].create({
|
||||
'name': 'outbound',
|
||||
'code': 'out',
|
||||
'payment_type': 'outbound',
|
||||
})
|
||||
|
||||
def test_expense_multicompany_company_propagation(self):
|
||||
# The company on the expense sheet should be the same as the one from the expense
|
||||
expense = self.env['hr.expense.sheet'].create({
|
||||
'name': 'Expense for employee a',
|
||||
'employee_id': self.employee.id,
|
||||
'journal_id': self.sale_journal0.id,
|
||||
})
|
||||
|
||||
expense_line = self.env['hr.expense'].create({
|
||||
'name': 'Sword Sharpening',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_1.id,
|
||||
'unit_amount': 1,
|
||||
'quantity': 1,
|
||||
'sheet_id': expense.id,
|
||||
'analytic_account_id': self.analytic_account.id,
|
||||
})
|
||||
|
||||
self.assertEqual(expense_line.company_id.id, self.env.company.id)
|
||||
self.assertEqual(expense.company_id.id, self.env.company.id)
|
||||
|
||||
expense.with_context(allowed_company_ids=[self.company_B.id, self.env.company.id], company_id=self.company_B.id).action_submit_sheet()
|
||||
self.assertEqual(expense.state, 'submit', 'Expense is not in Reported state')
|
||||
self.assertEqual(expense.company_id.id, self.env.company.id)
|
||||
|
||||
expense.with_context(allowed_company_ids=[self.company_B.id, self.env.company.id], company_id=self.company_B.id).approve_expense_sheets()
|
||||
self.assertEqual(expense.state, 'approve', 'Expense is not in Approved state')
|
||||
|
||||
expense.with_context(allowed_company_ids=[self.company_B.id, self.env.company.id], company_id=self.company_B.id).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')
|
||||
|
||||
exp_move_lines = expense.account_move_id.line_ids
|
||||
payable_move_lines = exp_move_lines.filtered(lambda l: l.account_id.internal_type == 'payable')
|
||||
self.assertEqual(len(payable_move_lines), 1)
|
||||
self.assertEqual(payable_move_lines[0].company_id.id, expense.company_id.id, 'The company of the move line should be the same as the one from the expense.')
|
||||
|
||||
#The company on the payment should be the same as the one on the expense, even if we are in a another company
|
||||
WizardRegister = self.env["hr.expense.sheet.register.payment.wizard"].with_context(
|
||||
active_model=expense._name, active_id=expense.id, active_ids=expense.ids, allowed_company_ids=[self.company_B.id, self.env.company.id], company_id=self.company_B.id
|
||||
)
|
||||
|
||||
register_payement = WizardRegister.create({
|
||||
'journal_id': self.bank_journal.id,
|
||||
'payment_method_id': self.outbound_pay_method.id,
|
||||
'amount': 300,
|
||||
})
|
||||
self.assertEqual(register_payement.company_id.id, expense.company_id.id, 'The company of the payement should be the same as the one from the expense.')
|
||||
register_payement.expense_post_payment()
|
||||
@@ -3,230 +3,131 @@
|
||||
|
||||
from odoo.addons.hr_expense.tests.common import TestExpenseCommon
|
||||
from odoo.addons.sale.tests.test_sale_common import TestCommonSaleNoChart
|
||||
from odoo.tests import tagged
|
||||
|
||||
|
||||
@tagged('-at_install', 'post_install')
|
||||
class TestReInvoice(TestExpenseCommon, TestCommonSaleNoChart):
|
||||
|
||||
@classmethod
|
||||
def setUpClass(cls):
|
||||
super(TestReInvoice, cls).setUpClass()
|
||||
def setUpClass(cls, chart_template_ref=None):
|
||||
super().setUpClass(chart_template_ref=chart_template_ref)
|
||||
super().setUpExpenseProducts()
|
||||
|
||||
cls.setUpExpenseProducts()
|
||||
|
||||
cls.env.ref('product.list0').currency_id = cls.env.user.company_id.currency_id
|
||||
|
||||
# partner and SO
|
||||
cls.partner_customer = cls.env['res.partner'].create({
|
||||
'name': 'Ze Client',
|
||||
'email': 'client@agrolait.com',
|
||||
'property_account_payable_id': cls.account_payable.id,
|
||||
cls.price_list = cls.env['product.pricelist'].create({
|
||||
'name': 'default_price_list_company_1',
|
||||
'sequence': 1,
|
||||
'company_id': cls.company_data['company'].id,
|
||||
'currency_id': cls.company_data['currency'].id,
|
||||
})
|
||||
|
||||
cls.sale_order = cls.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
'partner_id': cls.partner_customer_usd.id,
|
||||
'partner_invoice_id': cls.partner_customer_usd.id,
|
||||
'partner_shipping_id': cls.partner_customer_usd.id,
|
||||
})
|
||||
|
||||
def test_at_cost(self):
|
||||
""" Test invoicing expenses at cost for product based on delivered and ordered quantities. """
|
||||
def test_expenses_reinvoice(self):
|
||||
# create SO line and confirm SO (with only one line)
|
||||
sale_order_line = self.env['sale.order.line'].create({
|
||||
'name': self.product_ordered_cost.name,
|
||||
'product_id': self.product_ordered_cost.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': self.product_ordered_cost.uom_id.id,
|
||||
'price_unit': self.product_ordered_cost.list_price,
|
||||
'order_id': self.sale_order.id,
|
||||
sale_order = self.env['sale.order'].with_context(mail_notrack=True, mail_create_nolog=True).create({
|
||||
'partner_id': self.partner_a.id,
|
||||
'partner_invoice_id': self.partner_a.id,
|
||||
'partner_shipping_id': self.partner_a.id,
|
||||
'order_line': [(0, 0, {
|
||||
'name': self.product_ordered_cost.name,
|
||||
'product_id': self.product_ordered_cost.id,
|
||||
'product_uom_qty': 2.0,
|
||||
'price_unit': 1000.0,
|
||||
})],
|
||||
})
|
||||
sale_order_line.product_id_change()
|
||||
self.sale_order.onchange_partner_id()
|
||||
self.sale_order._compute_tax_id()
|
||||
self.sale_order.action_confirm()
|
||||
sale_order.action_confirm()
|
||||
|
||||
self.assertTrue(self.sale_order.analytic_account_id, "Confirming SO with an expense product should trigger the analytic account creation")
|
||||
|
||||
# create expense lines
|
||||
expense1 = self.env['hr.expense'].create({
|
||||
'name': 'Expense for ordered product',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_ordered_cost.id,
|
||||
'unit_amount': 12,
|
||||
'quantity': 2,
|
||||
'sheet_id': self.expense_sheet.id,
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': self.sale_order.analytic_account_id.id,
|
||||
})
|
||||
expense2 = self.env['hr.expense'].create({
|
||||
'name': 'Expense for delivered product',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_deliver_cost.id,
|
||||
'unit_amount': 15,
|
||||
'quantity': 1,
|
||||
'sheet_id': self.expense_sheet.id,
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': self.sale_order.analytic_account_id.id,
|
||||
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, {
|
||||
'name': 'expense_1',
|
||||
'date': '2016-01-01',
|
||||
'product_id': self.product_ordered_cost.id,
|
||||
'unit_amount': self.product_ordered_cost.list_price,
|
||||
'analytic_account_id': self.analytic_account_1.id,
|
||||
'employee_id': self.expense_employee.id,
|
||||
'sale_order_id': sale_order.id,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': 'expense_2',
|
||||
'date': '2016-01-01',
|
||||
'product_id': self.product_deliver_cost.id,
|
||||
'unit_amount': self.product_deliver_cost.list_price,
|
||||
'analytic_account_id': self.analytic_account_1.id,
|
||||
'employee_id': self.expense_employee.id,
|
||||
'sale_order_id': sale_order.id,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': 'expense_3',
|
||||
'date': '2016-01-01',
|
||||
'product_id': self.product_order_sales_price.id,
|
||||
'unit_amount': self.product_order_sales_price.list_price,
|
||||
'analytic_account_id': self.analytic_account_1.id,
|
||||
'employee_id': self.expense_employee.id,
|
||||
'sale_order_id': sale_order.id,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': 'expense_4',
|
||||
'date': '2016-01-01',
|
||||
'product_id': self.product_deliver_sales_price.id,
|
||||
'unit_amount': self.product_deliver_sales_price.list_price,
|
||||
'analytic_account_id': self.analytic_account_1.id,
|
||||
'employee_id': self.expense_employee.id,
|
||||
'sale_order_id': sale_order.id,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': 'expense_5',
|
||||
'date': '2016-01-01',
|
||||
'product_id': self.product_no_expense.id,
|
||||
'unit_amount': self.product_no_expense.list_price,
|
||||
'analytic_account_id': self.analytic_account_1.id,
|
||||
'employee_id': self.expense_employee.id,
|
||||
'sale_order_id': sale_order.id,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
# approve and generate entries
|
||||
self.expense_sheet.approve_expense_sheets()
|
||||
self.expense_sheet.action_sheet_move_create()
|
||||
expense_sheet.approve_expense_sheets()
|
||||
expense_sheet.action_sheet_move_create()
|
||||
|
||||
self.assertEqual(len(self.sale_order.order_line), 3, "There should be 3 lines on the SO")
|
||||
self.assertEqual(sale_order_line.qty_delivered, 0, "Exising SO line should not be impacted by reinvoicing product at cost")
|
||||
self.assertRecordValues(sale_order.order_line, [
|
||||
# Original SO line:
|
||||
{
|
||||
'qty_delivered': 0.0,
|
||||
'product_uom_qty': 2.0,
|
||||
'is_expense': False,
|
||||
},
|
||||
# Expense lines:
|
||||
{
|
||||
'qty_delivered': 1.0,
|
||||
'product_uom_qty': 0.0,
|
||||
'is_expense': True,
|
||||
},
|
||||
{
|
||||
'qty_delivered': 1.0,
|
||||
'product_uom_qty': 0.0,
|
||||
'is_expense': True,
|
||||
},
|
||||
{
|
||||
'qty_delivered': 1.0,
|
||||
'product_uom_qty': 0.0,
|
||||
'is_expense': True,
|
||||
},
|
||||
{
|
||||
'qty_delivered': 1.0,
|
||||
'product_uom_qty': 0.0,
|
||||
'is_expense': True,
|
||||
},
|
||||
])
|
||||
|
||||
sol_ordered = self.sale_order.order_line.filtered(lambda sol: sol.product_id == self.product_ordered_cost and sol != sale_order_line)
|
||||
self.assertTrue(sol_ordered.is_expense, "The new SOL should be flag as coming from an expense")
|
||||
self.assertEqual(sol_ordered.qty_delivered_method, 'analytic', 'Delivered quantity of expense SO line should be computed by analytic amount')
|
||||
self.assertTrue(sol_ordered, "A new line with ordered expense should have been created on expense report posting")
|
||||
self.assertEqual(sol_ordered.price_unit, expense1.unit_amount, "The unit price of new SO line should be the one from the expense (at cost)")
|
||||
self.assertEqual(sol_ordered.product_uom_qty, 0, "The ordered quantity of new SO line should be zero")
|
||||
self.assertEqual(sol_ordered.qty_delivered, expense1.quantity, "The delivered quantity of new SO line should be the one from the expense")
|
||||
|
||||
sol_deliver = self.sale_order.order_line.filtered(lambda sol: sol.product_id == self.product_deliver_cost and sol != sale_order_line)
|
||||
self.assertTrue(sol_deliver.is_expense, "The new SOL should be flag as coming from an expense")
|
||||
self.assertEqual(sol_deliver.qty_delivered_method, 'analytic', 'Delivered quantity of expense SO line should be computed by analytic amount')
|
||||
self.assertTrue(sol_deliver, "A new line with delivered expense should have been created on expense report posting")
|
||||
self.assertEqual(sol_deliver.price_unit, expense2.unit_amount, "The unit price of new SO line should be the one from the expense (at cost)")
|
||||
self.assertEqual(sol_deliver.product_uom_qty, 0, "The ordered quantity of new SO line should be zero")
|
||||
self.assertEqual(sol_deliver.qty_delivered, expense2.quantity, "The delivered quantity of new SO line should be the one from the expense")
|
||||
|
||||
def test_sales_price_ordered(self):
|
||||
""" Test invoicing expenses at sales price for product based on ordered quantities. """
|
||||
# confirm SO (with no line)
|
||||
self.sale_order._compute_tax_id()
|
||||
self.sale_order.action_confirm()
|
||||
self.assertFalse(self.sale_order.analytic_account_id, "Confirming SO with no expense product should not trigger the analytic account creation")
|
||||
|
||||
# create expense lines
|
||||
expense1 = self.env['hr.expense'].create({
|
||||
'name': 'Expense for ordered product at sales price',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_order_sales_price.id,
|
||||
'unit_amount': 15,
|
||||
'quantity': 2,
|
||||
'sheet_id': self.expense_sheet.id,
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': self.sale_order.analytic_account_id.id,
|
||||
})
|
||||
|
||||
# approve and generate entries
|
||||
self.expense_sheet.approve_expense_sheets()
|
||||
self.expense_sheet.action_sheet_move_create()
|
||||
|
||||
self.assertTrue(self.sale_order.analytic_account_id, "Posting expense with an expense product should trigger the analytic account creation on SO")
|
||||
self.assertEqual(self.sale_order.analytic_account_id, expense1.analytic_account_id, "SO analytic account should be the same for the expense")
|
||||
self.assertEqual(len(self.sale_order.order_line), 1, "A new So line should have been created on expense report posting")
|
||||
|
||||
sol_ordered = self.sale_order.order_line.filtered(lambda sol: sol.product_id == expense1.product_id)
|
||||
self.assertTrue(sol_ordered.is_expense, "The new SOL should be flag as coming from an expense")
|
||||
self.assertTrue(sol_ordered, "A new line with ordered expense should have been created on expense report posting")
|
||||
self.assertEqual(sol_ordered.price_unit, 10, "The unit price of new SO line should be the one from the expense (at sales price)")
|
||||
self.assertEqual(sol_ordered.product_uom_qty, 0, "The ordered quantity of new SO line should be zero")
|
||||
self.assertEqual(sol_ordered.qty_delivered, expense1.quantity, "The delivered quantity of new SO line should be the one from the expense")
|
||||
|
||||
def test_sales_price_delivered(self):
|
||||
""" Test invoicing expenses at sales price for product based on delivered quantities. Check the existing SO line is not incremented, but a new
|
||||
expense SOL will be created and incremented.
|
||||
"""
|
||||
# create SO line and confirm SO (with only one line)
|
||||
sale_order_line1 = self.env['sale.order.line'].create({
|
||||
'name': self.product_deliver_sales_price.name,
|
||||
'product_id': self.product_deliver_sales_price.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': self.product_deliver_sales_price.uom_id.id,
|
||||
'price_unit': self.product_deliver_sales_price.list_price,
|
||||
'order_id': self.sale_order.id,
|
||||
})
|
||||
sale_order_line1.product_id_change()
|
||||
self.sale_order._compute_tax_id()
|
||||
self.sale_order.action_confirm()
|
||||
|
||||
self.assertTrue(self.sale_order.analytic_account_id, "Confirming SO with an expense product should trigger the analytic account creation")
|
||||
|
||||
# create expense lines
|
||||
expense1 = self.env['hr.expense'].create({
|
||||
'name': 'Expense for delivered product at sales price',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_deliver_sales_price.id,
|
||||
'unit_amount': 15,
|
||||
'quantity': 3,
|
||||
'sheet_id': self.expense_sheet.id,
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': self.sale_order.analytic_account_id.id,
|
||||
})
|
||||
|
||||
# approve and generate entries
|
||||
self.expense_sheet.approve_expense_sheets()
|
||||
self.expense_sheet.action_sheet_move_create()
|
||||
|
||||
self.assertEqual(len(self.sale_order.order_line), 2, "A new expense SOL should have been created.")
|
||||
|
||||
sale_order_line2 = self.sale_order.order_line.filtered(lambda sol: sol != sale_order_line1)
|
||||
self.assertTrue(sale_order_line2.is_expense, "The new SOL should be flag as coming from an expense")
|
||||
self.assertEqual(sale_order_line2.price_unit, 10, "The unit price of SO line should be the same")
|
||||
self.assertEqual(sale_order_line2.product_uom_qty, 0, "The ordered quantity of new SO line should be zero")
|
||||
self.assertEqual(sale_order_line2.qty_delivered, expense1.quantity, "The delivered quantity of SO line should have be the same of the expense")
|
||||
|
||||
# create 2nd expense lines
|
||||
expense2 = self.env['hr.expense'].create({
|
||||
'name': 'Expense for delivered product at sales price',
|
||||
'employee_id': self.employee.id,
|
||||
'product_id': self.product_deliver_sales_price.id,
|
||||
'unit_amount': 15,
|
||||
'quantity': 3,
|
||||
'sheet_id': self.expense_sheet2.id,
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': self.sale_order.analytic_account_id.id,
|
||||
})
|
||||
|
||||
# approve and generate entries
|
||||
self.expense_sheet2.approve_expense_sheets()
|
||||
self.expense_sheet2.action_sheet_move_create()
|
||||
|
||||
self.assertEqual(len(self.sale_order.order_line), 2, "No new expense SOL should have been created.")
|
||||
|
||||
self.assertEqual(sale_order_line2.price_unit, 10, "The unit price of SO line should be the same")
|
||||
self.assertEqual(sale_order_line2.product_uom_qty, 0, "The ordered quantity of new SO line should remains zero")
|
||||
self.assertEqual(sale_order_line2.qty_delivered, expense1.quantity + expense2.quantity, "The delivered quantity of SO line should have been incremented")
|
||||
|
||||
def test_no_expense(self):
|
||||
""" Test invoicing expenses with no policy. Check nothing happen. """
|
||||
analytic_account = self.env['account.analytic.account'].create({
|
||||
'name': "AA to track expense",
|
||||
})
|
||||
# confirm SO
|
||||
sale_order_line = self.env['sale.order.line'].create({
|
||||
'name': self.product_no_expense.name,
|
||||
'product_id': self.product_no_expense.id,
|
||||
'product_uom_qty': 2,
|
||||
'product_uom': self.product_no_expense.uom_id.id,
|
||||
'price_unit': self.product_no_expense.list_price,
|
||||
'order_id': self.sale_order.id,
|
||||
})
|
||||
self.sale_order._compute_tax_id()
|
||||
self.sale_order.action_confirm()
|
||||
|
||||
self.assertFalse(self.sale_order.analytic_account_id, "Confirming SO with an no-expense product should not trigger the analytic account creation")
|
||||
|
||||
# create expense lines
|
||||
expense1 = self.env['hr.expense'].create({
|
||||
'name': 'Expense for no expense product',
|
||||
'employee_id': self.employee.id,
|
||||
'unit_amount': 15,
|
||||
'quantity': 3,
|
||||
'sheet_id': self.expense_sheet.id,
|
||||
'sale_order_id': self.sale_order.id,
|
||||
'analytic_account_id': analytic_account.id,
|
||||
})
|
||||
expense1.write({'product_id': self.product_no_expense.id})
|
||||
# approve and generate entries
|
||||
self.expense_sheet.approve_expense_sheets()
|
||||
self.expense_sheet.action_sheet_move_create()
|
||||
self.assertFalse(expense1.sale_order_id, "None reinvoicable expense can not be linked to SO")
|
||||
self.assertFalse(self.sale_order.analytic_account_id, "Posting expense with an non expense product should not trigger analytic Account creation from SO, since non reinvoicable expense can not be linked to SO")
|
||||
|
||||
self.assertEqual(len(self.sale_order.order_line), 1, "No SO line should have been created (or removed) on expense report posting")
|
||||
self.assertEqual(sale_order_line.price_unit, self.product_no_expense.list_price, "The unit price of SO line should be the same")
|
||||
self.assertEqual(sale_order_line.product_uom_qty, 2, "The ordered quantity of SO line should be two")
|
||||
self.assertEqual(sale_order_line.qty_delivered, 0, "The delivered quantity of SO line should have been incremented")
|
||||
# 'qty_delivered_method' is not checked on the first line because it could be multiple things depending if
|
||||
# 'sale_stock' is installed or not.
|
||||
self.assertRecordValues(sale_order.order_line[1:], [
|
||||
{'qty_delivered_method': 'analytic'},
|
||||
{'qty_delivered_method': 'analytic'},
|
||||
{'qty_delivered_method': 'analytic'},
|
||||
{'qty_delivered_method': 'analytic'},
|
||||
])
|
||||
|
||||
Reference in New Issue
Block a user