912 lines
48 KiB
Python
912 lines
48 KiB
Python
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
|
|
||
|
import base64
|
||
|
from datetime import date
|
||
|
from freezegun import freeze_time
|
||
|
|
||
|
from odoo import Command
|
||
|
from odoo.addons.hr_expense.tests.common import TestExpenseCommon
|
||
|
from odoo.exceptions import UserError
|
||
|
from odoo.tests import tagged, Form
|
||
|
from odoo.tools.misc import format_date
|
||
|
|
||
|
|
||
|
@tagged('-at_install', 'post_install')
|
||
|
class TestExpenses(TestExpenseCommon):
|
||
|
#############################################
|
||
|
# Test Expense flows
|
||
|
#############################################
|
||
|
def test_expense_main_flow(self):
|
||
|
"""
|
||
|
Test the main flows of expense
|
||
|
This includes:
|
||
|
- Approval flows for expense paid by company and employee up to reconciliation
|
||
|
- accounting_date computation and override
|
||
|
- price_unit, total_amount_currency and quantity computation
|
||
|
- Split payments into one payment per expense when paid by company
|
||
|
- Override account on expense
|
||
|
- Payment states and payment terms
|
||
|
- Unlinking payments reverts to approved state
|
||
|
- Cannot delete an analytic account if linked to an expense
|
||
|
"""
|
||
|
# pylint: disable=bad-whitespace
|
||
|
self.expense_employee.user_partner_id.property_supplier_payment_term_id = self.env.ref('account.account_payment_term_30days')
|
||
|
expense_sheet_by_employee = self.create_expense_report({
|
||
|
'name': 'Expense for John Smith',
|
||
|
'accounting_date': '2021-10-10', # This should be the date set as the accounting_date
|
||
|
'expense_line_ids': [Command.create({
|
||
|
'name': 'PA 2*800 + 15%', # Taxes are included
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'account_id': self.expense_account.id, # Test with a specific account override
|
||
|
'product_id': self.product_a.id,
|
||
|
'quantity': 2,
|
||
|
'payment_mode': 'own_account',
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'date': '2021-10-11',
|
||
|
'analytic_distribution': {self.analytic_account_1.id: 100},
|
||
|
}), Command.create({
|
||
|
'name': 'PB 160 + 2*15%', # Taxes are included
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_b.id,
|
||
|
'payment_mode': 'own_account',
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'date': '2021-10-13',
|
||
|
'analytic_distribution': {self.analytic_account_2.id: 100},
|
||
|
})],
|
||
|
})
|
||
|
expense_sheet_by_company = self.create_expense_report({
|
||
|
'name': 'Expense for Company',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'expense_line_ids': [Command.create({
|
||
|
'name': 'PC 1000 + 15%', # Taxes are included
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'total_amount_currency': 1000.00,
|
||
|
'date': '2021-10-11',
|
||
|
'payment_mode': 'company_account',
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'tax_ids': [Command.set(self.tax_purchase_a.ids)],
|
||
|
}), Command.create({
|
||
|
'name': 'PB 160 + 2*15% 2', # Taxes are included
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_b.id,
|
||
|
'payment_mode': 'company_account',
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'date': '2021-10-12', # This should be the date set as the accounting_date
|
||
|
})],
|
||
|
})
|
||
|
expense_sheets = expense_sheet_by_employee | expense_sheet_by_company
|
||
|
|
||
|
# Checking expense sheets values at creation
|
||
|
self.assertRecordValues(expense_sheets, [
|
||
|
{'total_amount': 1760.00, 'untaxed_amount': 1514.38, 'total_tax_amount': 245.62, 'state': 'draft', 'accounting_date': date(2021, 10, 10)},
|
||
|
{'total_amount': 1160.00, 'untaxed_amount': 992.65, 'total_tax_amount': 167.35, 'state': 'draft', 'accounting_date': False},
|
||
|
])
|
||
|
self.assertRecordValues(expense_sheets.expense_line_ids, [
|
||
|
{'total_amount_currency': 1600.00, 'untaxed_amount_currency': 1391.30, 'price_unit': 800.00, 'tax_amount': 208.70, 'state': 'reported'},
|
||
|
{'total_amount_currency': 160.00, 'untaxed_amount_currency': 123.08, 'price_unit': 160.00, 'tax_amount': 36.92, 'state': 'reported'},
|
||
|
{'total_amount_currency': 1000.00, 'untaxed_amount_currency': 869.57, 'price_unit': 1000.00, 'tax_amount': 130.43, 'state': 'reported'},
|
||
|
{'total_amount_currency': 160.00, 'untaxed_amount_currency': 123.08, 'price_unit': 160.00, 'tax_amount': 36.92, 'state': 'reported'},
|
||
|
])
|
||
|
|
||
|
# Submitting properly change states
|
||
|
expense_sheets.action_submit_sheet()
|
||
|
self.assertRecordValues(expense_sheets, [
|
||
|
{'state': 'submit'},
|
||
|
{'state': 'submit'},
|
||
|
])
|
||
|
self.assertRecordValues(expense_sheets.expense_line_ids, [
|
||
|
{'state': 'submitted'},
|
||
|
{'state': 'submitted'},
|
||
|
{'state': 'submitted'},
|
||
|
{'state': 'submitted'},
|
||
|
])
|
||
|
|
||
|
# Approving properly change states
|
||
|
expense_sheets.action_approve_expense_sheets()
|
||
|
self.assertRecordValues(expense_sheets, [
|
||
|
{'state': 'approve'},
|
||
|
{'state': 'approve'},
|
||
|
])
|
||
|
self.assertRecordValues(expense_sheets.expense_line_ids, [
|
||
|
{'state': 'approved'},
|
||
|
{'state': 'approved'},
|
||
|
{'state': 'approved'},
|
||
|
{'state': 'approved'},
|
||
|
])
|
||
|
|
||
|
# Generate a payment for 'company_account' (and its move(s)) and a vendor bill for 'own_account'
|
||
|
expense_sheets.action_sheet_move_create()
|
||
|
self.assertRecordValues(expense_sheets, [
|
||
|
{'state': 'post', 'payment_state': 'not_paid', 'accounting_date': date(2021, 10, 10)},
|
||
|
{'state': 'done', 'payment_state': 'paid', 'accounting_date': date(2021, 10, 12)}, # Set to paid as move is posted directly
|
||
|
])
|
||
|
self.assertRecordValues(expense_sheets.expense_line_ids, [
|
||
|
{'payment_mode': 'own_account', 'state': 'approved'}, # vv
|
||
|
{'payment_mode': 'own_account', 'state': 'approved'}, # As the payment is not done yet those are still in "approved"
|
||
|
{'payment_mode': 'company_account', 'state': 'done'},
|
||
|
{'payment_mode': 'company_account', 'state': 'done'},
|
||
|
])
|
||
|
# One payment for the whole sheet if 'own_account'
|
||
|
expected_partner_id = self.expense_user_employee.partner_id.id
|
||
|
self.assertRecordValues(expense_sheet_by_employee.account_move_ids, [{
|
||
|
'amount_total': 1760.00,
|
||
|
'ref': 'Expense for John Smith',
|
||
|
'date': date(2021, 10, 10),
|
||
|
'invoice_date_due': date(2021, 11, 9), # The due date is the one set for the partner
|
||
|
'partner_id': expected_partner_id
|
||
|
},
|
||
|
])
|
||
|
# One payment per expense if 'company_account'
|
||
|
self.assertRecordValues(expense_sheet_by_company.account_move_ids, [
|
||
|
{'amount_total': 1000.00, 'ref': 'PC 1000 + 15%', 'date': date(2021, 10, 12), 'partner_id': False},
|
||
|
{'amount_total': 160.00, 'ref': 'PB 160 + 2*15% 2', 'date': date(2021, 10, 12), 'partner_id': False},
|
||
|
])
|
||
|
tax_account_id = self.company_data['default_account_tax_purchase'].id
|
||
|
default_account_payable_id = self.company_data['default_account_payable'].id
|
||
|
product_b_account_id = self.product_b.property_account_expense_id.id
|
||
|
product_c_account_id = self.product_c.property_account_expense_id.id
|
||
|
company_payment_account_id = self.company_data['company'].account_journal_payment_credit_account_id.id
|
||
|
# One payment per expense
|
||
|
self.assertRecordValues(expense_sheets.account_move_ids.line_ids, [
|
||
|
# own_account expense sheet move
|
||
|
{'balance': 123.08, 'account_id': product_b_account_id, 'name': 'expense_employee: PB 160 + 2*15%', 'date': date(2021, 10, 10)},
|
||
|
{'balance': 1391.30, 'account_id': self.expense_account.id, 'name': 'expense_employee: PA 2*800 + 15%', 'date': date(2021, 10, 10)},
|
||
|
{'balance': 18.46, 'account_id': tax_account_id, 'name': '15%', 'date': date(2021, 10, 10)},
|
||
|
{'balance': 18.46, 'account_id': tax_account_id, 'name': '15% (Copy)', 'date': date(2021, 10, 10)},
|
||
|
{'balance': 208.70, 'account_id': tax_account_id, 'name': '15%', 'date': date(2021, 10, 10)},
|
||
|
{'balance': -1760.00, 'account_id': default_account_payable_id, 'name': False, 'date': date(2021, 10, 10)},
|
||
|
|
||
|
# company_account expense 1 move
|
||
|
{'balance': 869.57, 'account_id': product_c_account_id, 'name': 'expense_employee: PC 1000 + 15%', 'date': date(2021, 10, 12)},
|
||
|
{'balance': 130.43, 'account_id': tax_account_id, 'name': '15%', 'date': date(2021, 10, 12)},
|
||
|
{'balance': -1000.00, 'account_id': company_payment_account_id, 'name': 'expense_employee: PC 1000 + 15%', 'date': date(2021, 10, 12)},
|
||
|
|
||
|
# company_account expense 2 move
|
||
|
{'balance': 123.08, 'account_id': product_b_account_id, 'name': 'expense_employee: PB 160 + 2*15% 2', 'date': date(2021, 10, 12)},
|
||
|
{'balance': 18.46, 'account_id': tax_account_id, 'name': '15%', 'date': date(2021, 10, 12)},
|
||
|
{'balance': 18.46, 'account_id': tax_account_id, 'name': '15% (Copy)', 'date': date(2021, 10, 12)},
|
||
|
{'balance': -160.00, 'account_id': company_payment_account_id, 'name': 'expense_employee: PB 160 + 2*15% 2', 'date': date(2021, 10, 12)},
|
||
|
])
|
||
|
|
||
|
# Own_account partial payment
|
||
|
payment_1 = self.get_new_payment(expense_sheet_by_employee, 1700.0)
|
||
|
liquidity_lines1 = payment_1._seek_for_lines()[0]
|
||
|
self.assertRecordValues(expense_sheet_by_employee, [{'payment_state': 'partial', 'state': 'done'}])
|
||
|
|
||
|
# own_account remaining payment
|
||
|
payment_2 = self.get_new_payment(expense_sheet_by_employee, 60.0)
|
||
|
liquidity_lines2 = payment_2._seek_for_lines()[0]
|
||
|
in_payment_state = expense_sheet_by_employee.account_move_ids._get_invoice_in_payment_state()
|
||
|
self.assertRecordValues(expense_sheet_by_employee, [{'payment_state': in_payment_state, 'state': 'done'}])
|
||
|
self.assertRecordValues(expense_sheet_by_employee.expense_line_ids, [{'state': 'done'}] * 2)
|
||
|
|
||
|
# Reconciling own_account
|
||
|
statement_line = self.env['account.bank.statement.line'].create({
|
||
|
'journal_id': self.company_data['default_journal_bank'].id,
|
||
|
'payment_ref': 'pay_ref',
|
||
|
'amount': -1760.0,
|
||
|
'partner_id': self.expense_employee.work_contact_id.id,
|
||
|
})
|
||
|
|
||
|
# Reconcile without the bank reconciliation widget since the widget is in enterprise.
|
||
|
_trash, st_suspense_lines, _trash = statement_line.with_context(skip_account_move_synchronization=True)._seek_for_lines()
|
||
|
st_suspense_lines.account_id = liquidity_lines1.account_id
|
||
|
(st_suspense_lines + liquidity_lines1 + liquidity_lines2).reconcile()
|
||
|
self.assertRecordValues(expense_sheet_by_employee, [{'payment_state': 'paid', 'state': 'done'}])
|
||
|
|
||
|
# Trying to delete analytic accounts should be forbidden if linked to an expense
|
||
|
with self.assertRaises(UserError):
|
||
|
(self.analytic_account_1 | self.analytic_account_2).unlink()
|
||
|
|
||
|
# Unlinking moves
|
||
|
(payment_1 | payment_2).action_draft()
|
||
|
self.assertRecordValues(expense_sheet_by_employee, [{'payment_state': 'not_paid', 'state': 'post'}])
|
||
|
expense_sheet_by_employee.account_move_ids.button_draft()
|
||
|
expense_sheet_by_employee.account_move_ids.unlink()
|
||
|
|
||
|
with self.assertRaises(UserError, msg="For company-paid expenses report, deleting payments is an all-or-nothing situation"):
|
||
|
expense_sheet_by_company.account_move_ids[:-1].payment_id.unlink()
|
||
|
expense_sheet_by_company.account_move_ids.payment_id.unlink()
|
||
|
|
||
|
self.assertRecordValues(expense_sheets.sorted('payment_mode'), [
|
||
|
{'payment_mode': 'company_account', 'state': 'approve', 'payment_state': 'not_paid', 'account_move_ids': []},
|
||
|
{'payment_mode': 'own_account', 'state': 'approve', 'payment_state': 'not_paid', 'account_move_ids': []},
|
||
|
])
|
||
|
|
||
|
expense_sheet_by_employee.action_reset_expense_sheets()
|
||
|
self.assertRecordValues(expense_sheet_by_employee, [{'state': 'draft', 'payment_state': 'not_paid', 'account_move_ids': []}])
|
||
|
expense_sheet_by_employee.expense_line_ids.unlink()
|
||
|
# Only possible if no expense linked to the account
|
||
|
self.analytic_account_1.unlink()
|
||
|
|
||
|
def test_expense_split_flow(self):
|
||
|
""" Check Split Expense flow. """
|
||
|
expense = self.create_expense({'analytic_distribution': {self.analytic_account_1.id: 100}})
|
||
|
|
||
|
wizard = self.env['hr.expense.split.wizard'].browse(expense.action_split_wizard()['res_id'])
|
||
|
|
||
|
# Check default hr.expense.split values
|
||
|
self.assertRecordValues(wizard.expense_split_line_ids, [
|
||
|
{
|
||
|
'name': expense.name,
|
||
|
'wizard_id': wizard.id,
|
||
|
'expense_id': expense.id,
|
||
|
'product_id': expense.product_id.id,
|
||
|
'tax_ids': expense.tax_ids.ids,
|
||
|
'total_amount_currency': expense.total_amount_currency / 2,
|
||
|
'tax_amount_currency': 65.22,
|
||
|
'employee_id': expense.employee_id.id,
|
||
|
'company_id': expense.company_id.id,
|
||
|
'currency_id': expense.currency_id.id,
|
||
|
'analytic_distribution': expense.analytic_distribution,
|
||
|
}] * 2)
|
||
|
self.assertRecordValues(wizard, [{'split_possible': True, 'total_amount_currency': expense.total_amount_currency}])
|
||
|
|
||
|
# Grant Analytic Accounting rights, to be able to modify analytic_distribution from the wizard
|
||
|
self.env.user.groups_id += self.env.ref('analytic.group_analytic_accounting')
|
||
|
|
||
|
with Form(wizard) as form:
|
||
|
form.expense_split_line_ids.remove(index=0)
|
||
|
self.assertEqual(form.split_possible, False)
|
||
|
|
||
|
# Check removing tax_ids and analytic_distribution
|
||
|
with form.expense_split_line_ids.edit(0) as line:
|
||
|
line.total_amount_currency = 200.00
|
||
|
line.tax_ids.clear()
|
||
|
line.analytic_distribution = {}
|
||
|
self.assertEqual(line.total_amount_currency, 200.00)
|
||
|
self.assertEqual(line.tax_amount_currency, 0.00)
|
||
|
self.assertEqual(form.split_possible, False)
|
||
|
|
||
|
# This line should have the same tax_ids and analytic_distribution as original expense
|
||
|
with form.expense_split_line_ids.new() as line:
|
||
|
line.total_amount_currency = 300.00
|
||
|
self.assertEqual(line.total_amount_currency, 300.00)
|
||
|
self.assertEqual(line.tax_amount_currency, 39.13)
|
||
|
self.assertDictEqual(line.analytic_distribution, expense.analytic_distribution)
|
||
|
self.assertEqual(form.split_possible, False)
|
||
|
self.assertEqual(form.total_amount_currency, 500.00)
|
||
|
|
||
|
# Check adding tax_ids and setting analytic_distribution
|
||
|
with form.expense_split_line_ids.new() as line:
|
||
|
line.total_amount_currency = 500.00
|
||
|
line.tax_ids.add(self.tax_purchase_b)
|
||
|
line.analytic_distribution = {self.analytic_account_2.id: 100}
|
||
|
self.assertEqual(line.total_amount_currency, 500.00)
|
||
|
self.assertEqual(line.tax_amount_currency, 115.38)
|
||
|
|
||
|
# Check wizard values
|
||
|
self.assertRecordValues(wizard, [
|
||
|
{'total_amount_currency': 1000.00, 'total_amount_currency_original': 1000.00, 'tax_amount_currency': 154.51, 'split_possible': True}
|
||
|
])
|
||
|
|
||
|
wizard.action_split_expense()
|
||
|
# Check that split resulted into expenses with correct values
|
||
|
expenses_after_split = self.env['hr.expense'].search([('name', '=', expense.name)])
|
||
|
self.assertRecordValues(expenses_after_split.sorted('total_amount_currency'), [
|
||
|
{
|
||
|
'name': expense.name,
|
||
|
'employee_id': expense.employee_id.id,
|
||
|
'product_id': expense.product_id.id,
|
||
|
'total_amount_currency': 200.00,
|
||
|
'tax_ids': [],
|
||
|
'tax_amount_currency': 0.00,
|
||
|
'untaxed_amount_currency': 200.00,
|
||
|
'analytic_distribution': False,
|
||
|
}, {
|
||
|
'name': expense.name,
|
||
|
'employee_id': expense.employee_id.id,
|
||
|
'product_id': expense.product_id.id,
|
||
|
'total_amount_currency': 300.00,
|
||
|
'tax_ids': [self.tax_purchase_a.id],
|
||
|
'tax_amount_currency': 39.13,
|
||
|
'untaxed_amount_currency': 260.87,
|
||
|
'analytic_distribution': {str(self.analytic_account_1.id): 100},
|
||
|
}, {
|
||
|
'name': expense.name,
|
||
|
'employee_id': expense.employee_id.id,
|
||
|
'product_id': expense.product_id.id,
|
||
|
'total_amount_currency': 500.00,
|
||
|
'tax_ids': [self.tax_purchase_a.id, self.tax_purchase_b.id],
|
||
|
'tax_amount_currency': 115.38,
|
||
|
'untaxed_amount_currency': 384.62,
|
||
|
'analytic_distribution': {str(self.analytic_account_2.id): 100},
|
||
|
}
|
||
|
])
|
||
|
|
||
|
#############################################
|
||
|
# Test Multi-currency
|
||
|
#############################################
|
||
|
|
||
|
def test_expense_multi_currencies(self):
|
||
|
"""
|
||
|
Checks that the currency rate is recomputed properly when the total in company currency is set to a new value
|
||
|
and that extreme rounding cases do not end up with non-consistend data
|
||
|
"""
|
||
|
# pylint: disable=bad-whitespace
|
||
|
foreign_currency_1 = self.currency_data['currency']
|
||
|
foreign_currency_2, foreign_currency_3 = self.env['res.currency'].create([{
|
||
|
'name': 'Ex1',
|
||
|
'symbol': ' ',
|
||
|
'rounding': 0.01,
|
||
|
'position': 'after',
|
||
|
'currency_unit_label': 'Nothing',
|
||
|
'currency_subunit_label': 'Smaller Nothing',
|
||
|
}, {
|
||
|
'name': 'Ex2',
|
||
|
'symbol': ' ',
|
||
|
'rounding': 0.01,
|
||
|
'position': 'after',
|
||
|
'currency_unit_label': 'Nothing 2',
|
||
|
'currency_subunit_label': 'Smaller Nothing 2',
|
||
|
},
|
||
|
])
|
||
|
self.env['res.currency.rate'].create({
|
||
|
'name': '2016-01-01',
|
||
|
'rate': 1 / 1.52,
|
||
|
'currency_id': foreign_currency_2.id,
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
})
|
||
|
self.env['res.currency.rate'].create({
|
||
|
'name': '2016-01-01',
|
||
|
'rate': 1 / 0.148431,
|
||
|
'currency_id': foreign_currency_3.id,
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
})
|
||
|
foreign_sale_journal = self.company_data['default_journal_sale'].copy()
|
||
|
foreign_sale_journal.currency_id = foreign_currency_2.id
|
||
|
expense_sheet_currency_mix_1 = self.create_expense_report({
|
||
|
'journal_id': foreign_sale_journal.id,
|
||
|
'expense_line_ids': [Command.create({
|
||
|
'payment_mode': 'company_account',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'total_amount_currency': 1000.00,
|
||
|
'date': self.frozen_today,
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'currency_id': foreign_currency_1.id, # rate is 1:2
|
||
|
'tax_ids': [Command.set(self.tax_purchase_a.ids)],
|
||
|
})],
|
||
|
})
|
||
|
expense_sheet_currency_mix_2 = self.create_expense_report({
|
||
|
'journal_id': foreign_sale_journal.id,
|
||
|
'expense_line_ids': [Command.create({
|
||
|
'payment_mode': 'company_account',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'total_amount_currency': 1000.00,
|
||
|
'date': self.frozen_today,
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'currency_id': foreign_currency_2.id, # rate is 1:1.52
|
||
|
'tax_ids': [Command.set(self.tax_purchase_a.ids)],
|
||
|
}), Command.create({
|
||
|
'payment_mode': 'company_account',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'total_amount_currency': 1000.00,
|
||
|
'date': self.frozen_today,
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'currency_id': foreign_currency_2.id, # rate is 1:1.52
|
||
|
'tax_ids': [Command.set((self.tax_purchase_a.id, self.tax_purchase_b.id))],
|
||
|
})],
|
||
|
})
|
||
|
expense_sheet_currency_mix_3 = self.create_expense_report({
|
||
|
'journal_id': foreign_sale_journal.id,
|
||
|
'expense_line_ids': [Command.create({
|
||
|
'payment_mode': 'company_account',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'total_amount_currency': 1000.00,
|
||
|
'date': self.frozen_today,
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'currency_id': foreign_currency_2.id, # rate is 1:1.52
|
||
|
'tax_ids': [Command.set(self.tax_purchase_a.ids)],
|
||
|
}), Command.create({
|
||
|
'payment_mode': 'company_account',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'total_amount_currency': 1000.00,
|
||
|
'date': self.frozen_today,
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'currency_id': foreign_currency_1.id, # rate is 1:2
|
||
|
'tax_ids': [Command.set((self.tax_purchase_a.id, self.tax_purchase_b.id))],
|
||
|
})],
|
||
|
})
|
||
|
expense_sheet_currency_mix_4 = self.create_expense_report({ # This case handles a direct override in back-end of the rate
|
||
|
'journal_id': foreign_sale_journal.id,
|
||
|
'expense_line_ids': [Command.create({
|
||
|
'payment_mode': 'company_account',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'total_amount_currency': 1000.00,
|
||
|
'total_amount': 3000.00,
|
||
|
'date': self.frozen_today,
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'currency_id': foreign_currency_2.id, # default rate is 1:1.52, overriden to 3
|
||
|
'tax_ids': [Command.set(self.tax_purchase_a.ids)],
|
||
|
})],
|
||
|
})
|
||
|
expenses_sheet_currencies_mix = expense_sheet_currency_mix_1 | expense_sheet_currency_mix_2 \
|
||
|
| expense_sheet_currency_mix_3 | expense_sheet_currency_mix_4
|
||
|
self.assertRecordValues(expenses_sheet_currencies_mix.expense_line_ids, [
|
||
|
# Sheet 1, mono foreign currency
|
||
|
{'currency_rate': 0.50, 'total_amount_currency': 1000.00, 'total_amount': 500.00, 'currency_id': foreign_currency_1.id},
|
||
|
# Sheet 2, multiple identical foreign currencies
|
||
|
{'currency_rate': 1.52, 'total_amount_currency': 1000.00, 'total_amount': 1520.00, 'currency_id': foreign_currency_2.id},
|
||
|
{'currency_rate': 1.52, 'total_amount_currency': 1000.00, 'total_amount': 1520.00, 'currency_id': foreign_currency_2.id},
|
||
|
# Sheet 3, multiple different foreign currencies
|
||
|
{'currency_rate': 1.52, 'total_amount_currency': 1000.00, 'total_amount': 1520.00, 'currency_id': foreign_currency_2.id},
|
||
|
{'currency_rate': 0.50, 'total_amount_currency': 1000.00, 'total_amount': 500.00, 'currency_id': foreign_currency_1.id},
|
||
|
# Sheet 4, mono foreign currencies already overriden
|
||
|
{'currency_rate': 3.00, 'total_amount_currency': 1000.00, 'total_amount': 3000.00, 'currency_id': foreign_currency_2.id},
|
||
|
])
|
||
|
|
||
|
# Manually changing rate on the two first expenses after creation to check they recompute properly
|
||
|
# Back-end override
|
||
|
expense_sheet_currency_mix_1.expense_line_ids[0].write({'total_amount': 1000.00})
|
||
|
|
||
|
# Front-end override
|
||
|
expense = expense_sheet_currency_mix_2.expense_line_ids[0]
|
||
|
with Form(expense) as expense_form:
|
||
|
expense_form.total_amount = 2000.00
|
||
|
|
||
|
self.assertRecordValues(expenses_sheet_currencies_mix.expense_line_ids.sorted('id'), [
|
||
|
{'currency_rate': 1.00, 'total_amount_currency': 1000.00, 'total_amount': 1000.00}, # Rate should change
|
||
|
{'currency_rate': 2.00, 'total_amount_currency': 1000.00, 'total_amount': 2000.00}, # Rate should change
|
||
|
{'currency_rate': 1.52, 'total_amount_currency': 1000.00, 'total_amount': 1520.00}, # Rate should NOT change
|
||
|
{'currency_rate': 1.52, 'total_amount_currency': 1000.00, 'total_amount': 1520.00}, # Rate should NOT change
|
||
|
{'currency_rate': 0.50, 'total_amount_currency': 1000.00, 'total_amount': 500.00}, # Rate should NOT change
|
||
|
{'currency_rate': 3.00, 'total_amount_currency': 1000.00, 'total_amount': 3000.00}, # Rate should not revert to the default one (1.52)
|
||
|
])
|
||
|
|
||
|
# Sheet and move creation should not touch the rates anymore
|
||
|
expenses_sheet_currencies_mix.action_submit_sheet()
|
||
|
expenses_sheet_currencies_mix.action_approve_expense_sheets()
|
||
|
expenses_sheet_currencies_mix.action_sheet_move_create()
|
||
|
self.assertRecordValues(expenses_sheet_currencies_mix.account_move_ids, [
|
||
|
{'amount_total_in_currency_signed': 1000.00, 'amount_total_signed': 1000.00, 'currency_id': foreign_currency_1.id},
|
||
|
{'amount_total_in_currency_signed': 1000.00, 'amount_total_signed': 2000.00, 'currency_id': foreign_currency_2.id},
|
||
|
{'amount_total_in_currency_signed': 1000.00, 'amount_total_signed': 1520.00, 'currency_id': foreign_currency_2.id},
|
||
|
{'amount_total_in_currency_signed': 1000.00, 'amount_total_signed': 1520.00, 'currency_id': foreign_currency_2.id},
|
||
|
{'amount_total_in_currency_signed': 1000.00, 'amount_total_signed': 500.00, 'currency_id': foreign_currency_1.id},
|
||
|
{'amount_total_in_currency_signed': 1000.00, 'amount_total_signed': 3000.00, 'currency_id': foreign_currency_2.id},
|
||
|
])
|
||
|
self.assertRecordValues(expenses_sheet_currencies_mix.account_move_ids.payment_id, [
|
||
|
{'amount': 1000.00, 'payment_type': 'outbound', 'currency_id': foreign_currency_1.id},
|
||
|
{'amount': 1000.00, 'payment_type': 'outbound', 'currency_id': foreign_currency_2.id},
|
||
|
{'amount': 1000.00, 'payment_type': 'outbound', 'currency_id': foreign_currency_2.id},
|
||
|
{'amount': 1000.00, 'payment_type': 'outbound', 'currency_id': foreign_currency_2.id},
|
||
|
{'amount': 1000.00, 'payment_type': 'outbound', 'currency_id': foreign_currency_1.id},
|
||
|
{'amount': 1000.00, 'payment_type': 'outbound', 'currency_id': foreign_currency_2.id},
|
||
|
])
|
||
|
|
||
|
# Test that the roundings are consistent no matter by whom it is paid
|
||
|
expense_values = {
|
||
|
'payment_mode': 'company_account',
|
||
|
'total_amount_currency': 100.00,
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'currency_id': foreign_currency_3.id, # rate is 1:0.148431
|
||
|
'tax_ids': [Command.set((self.tax_purchase_a.id, self.tax_purchase_b.id))],
|
||
|
}
|
||
|
expense_sheet_company_rounding = self.create_expense_report({'expense_line_ids': [Command.create(expense_values)]})
|
||
|
del expense_values['payment_mode'] # Sets the default payment_mode (own_account)
|
||
|
expense_sheet_employee_rounding = self.create_expense_report({'expense_line_ids': [Command.create(expense_values)]})
|
||
|
expense_sheets_rounding = expense_sheet_company_rounding | expense_sheet_employee_rounding
|
||
|
self.assertRecordValues(expense_sheets_rounding.expense_line_ids, [
|
||
|
{'untaxed_amount_currency': 76.92, 'total_amount_currency': 100.00, 'total_amount': 14.84, 'tax_amount_currency': 23.08, 'tax_amount': 3.42},
|
||
|
{'untaxed_amount_currency': 76.92, 'total_amount_currency': 100.00, 'total_amount': 14.84, 'tax_amount_currency': 23.08, 'tax_amount': 3.42},
|
||
|
])
|
||
|
|
||
|
expense_sheets_rounding.action_submit_sheet()
|
||
|
expense_sheets_rounding.action_approve_expense_sheets()
|
||
|
expense_sheets_rounding.action_sheet_move_create()
|
||
|
|
||
|
self.assertRecordValues(expense_sheets_rounding.account_move_ids.line_ids, [
|
||
|
{'balance': 11.42, 'amount_currency': 76.92},
|
||
|
{'balance': 1.71, 'amount_currency': 11.54}, # == 3.42 tax_amount & 23.08 tax_amount
|
||
|
{'balance': 1.71, 'amount_currency': 11.54},
|
||
|
{'balance': -14.84, 'amount_currency': -100.00},
|
||
|
|
||
|
{'balance': 11.42, 'amount_currency': 11.42}, # Paid by employee so converted into company_currency
|
||
|
{'balance': 1.71, 'amount_currency': 1.71}, # == 3.42 tax_amount
|
||
|
{'balance': 1.71, 'amount_currency': 1.71},
|
||
|
{'balance': -14.84, 'amount_currency': -14.84},
|
||
|
])
|
||
|
|
||
|
#############################################
|
||
|
# Test Corner Cases
|
||
|
#############################################
|
||
|
def test_expense_corner_case_changing_employee(self):
|
||
|
"""
|
||
|
Test changing an employee on the expense that is linked with the sheet.
|
||
|
- In case sheet has only one expense linked with it, than changing an employee on expense should trigger changing an employee
|
||
|
on the sheet itself.
|
||
|
- In case sheet has more than one expense linked with it, than changing an employee on one of the expenses,
|
||
|
should cause unlinking the expense from the sheet.
|
||
|
"""
|
||
|
|
||
|
employee = self.env['hr.employee'].create({'name': 'Gabriel Iglesias'})
|
||
|
expense_sheet_employee_1 = self.create_expense_report() # default employee is self.expense_employee
|
||
|
expense_employee_2 = self.create_expense({'employee_id': employee.id})
|
||
|
|
||
|
expense_sheet_employee_1.expense_line_ids.employee_id = employee
|
||
|
self.assertEqual(expense_sheet_employee_1.employee_id, employee, 'Employee should have changed on the sheet')
|
||
|
|
||
|
expense_sheet_employee_1.expense_line_ids |= expense_employee_2
|
||
|
expense_employee_2.employee_id = self.expense_employee.id
|
||
|
self.assertEqual(expense_employee_2.sheet_id.id, False, 'Sheet should be unlinked from the expense')
|
||
|
|
||
|
def test_expenses_corner_case_with_tax_and_lock_date(self):
|
||
|
""" Test that when creating an expense move in a locked period still works but its accounting date is the current day """
|
||
|
self.env.company.tax_lock_date = '2022-01-01'
|
||
|
|
||
|
expense_sheet = self.create_expense_report({
|
||
|
'name': 'Expense for John Smith',
|
||
|
'accounting_date': '2020-01-01',
|
||
|
'expense_line_ids': [Command.create({
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_a.id,
|
||
|
'price_unit': 1000.00,
|
||
|
'date': '2020-01-02',
|
||
|
})],
|
||
|
})
|
||
|
|
||
|
expense_sheet.action_submit_sheet()
|
||
|
with freeze_time(self.frozen_today):
|
||
|
expense_sheet.action_approve_expense_sheets()
|
||
|
expense_sheet.action_sheet_move_create()
|
||
|
self.assertEqual(expense_sheet.accounting_date, self.frozen_today)
|
||
|
|
||
|
def test_corner_case_defaults_values_from_product(self):
|
||
|
""" As soon as you set a product, the expense name, uom, taxes and account are set according to the product. """
|
||
|
# Disable multi-uom
|
||
|
self.env.ref('base.group_user').implied_ids -= self.env.ref('uom.group_uom')
|
||
|
self.expense_user_employee.groups_id -= self.env.ref('uom.group_uom')
|
||
|
|
||
|
# Use the expense employee
|
||
|
Expense = self.env['hr.expense'].with_user(self.expense_user_employee)
|
||
|
|
||
|
# Make sure the multi-uom is correctly disabled for the user creating the expense
|
||
|
self.assertFalse(Expense.env.user.has_group('uom.group_uom'))
|
||
|
|
||
|
# Use a product not using the default uom "Unit(s)"
|
||
|
product = Expense.env.ref('hr_expense.expense_product_mileage')
|
||
|
|
||
|
expense_form = Form(Expense)
|
||
|
expense_form.product_id = product
|
||
|
expense = expense_form.save()
|
||
|
self.assertEqual(expense.name, product.display_name)
|
||
|
self.assertEqual(expense.product_uom_id, product.uom_id)
|
||
|
self.assertEqual(expense.tax_ids, product.supplier_taxes_id)
|
||
|
self.assertEqual(expense.account_id, product._get_product_accounts()['expense'])
|
||
|
|
||
|
def test_attachments_in_move_from_own_expense(self):
|
||
|
""" Checks that journal entries created form expense reports paid by employee have a copy of the attachments in the expense. """
|
||
|
expense = self.env['hr.expense'].create({
|
||
|
'name': 'Employee expense',
|
||
|
'date': '2022-11-16',
|
||
|
'payment_mode': 'own_account',
|
||
|
'total_amount': 1000.00,
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
})
|
||
|
expense_2 = self.env['hr.expense'].create({
|
||
|
'name': 'Employee expense 2',
|
||
|
'date': '2022-11-16',
|
||
|
'payment_mode': 'own_account',
|
||
|
'total_amount': 1000.00,
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
})
|
||
|
attachment = self.env['ir.attachment'].create({
|
||
|
'raw': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=",
|
||
|
'name': 'file1.png',
|
||
|
'res_model': 'hr.expense',
|
||
|
'res_id': expense.id,
|
||
|
})
|
||
|
attachment_2 = self.env['ir.attachment'].create({
|
||
|
'raw': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=",
|
||
|
'name': 'file2.png',
|
||
|
'res_model': 'hr.expense',
|
||
|
'res_id': expense_2.id,
|
||
|
})
|
||
|
|
||
|
expense.message_main_attachment_id = attachment
|
||
|
expense_2.message_main_attachment_id = attachment_2
|
||
|
expenses = expense | expense_2
|
||
|
|
||
|
expense_sheet = self.env['hr.expense.sheet'].create({
|
||
|
'name': 'Expenses paid by employee',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'expense_line_ids': expenses,
|
||
|
})
|
||
|
expense_sheet.action_submit_sheet()
|
||
|
expense_sheet.action_approve_expense_sheets()
|
||
|
expense_sheet.action_sheet_move_create()
|
||
|
|
||
|
self.assertRecordValues(expense_sheet.account_move_ids.attachment_ids, [
|
||
|
{
|
||
|
'raw': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=",
|
||
|
'name': 'file1.png',
|
||
|
'res_model': 'account.move',
|
||
|
'res_id': expense_sheet.account_move_ids.id
|
||
|
},
|
||
|
{
|
||
|
'raw': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=",
|
||
|
'name': 'file2.png',
|
||
|
'res_model': 'account.move',
|
||
|
'res_id': expense_sheet.account_move_ids.id
|
||
|
}
|
||
|
])
|
||
|
|
||
|
def test_attachments_in_move_from_company_expense(self):
|
||
|
""" Checks that journal entries created form expense reports paid by company have a copy of the attachments in the expense. """
|
||
|
expense = self.env['hr.expense'].create({
|
||
|
'name': 'Company expense',
|
||
|
'date': '2022-11-16',
|
||
|
'payment_mode': 'company_account',
|
||
|
'total_amount_currency': 1000.00,
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
})
|
||
|
expense_2 = self.env['hr.expense'].create({
|
||
|
'name': 'Company expense 2',
|
||
|
'date': '2022-11-16',
|
||
|
'payment_mode': 'company_account',
|
||
|
'total_amount_currency': 1000.00,
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
})
|
||
|
attachment = self.env['ir.attachment'].create({
|
||
|
'raw': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=",
|
||
|
'name': 'file1.png',
|
||
|
'res_model': 'hr.expense',
|
||
|
'res_id': expense.id,
|
||
|
})
|
||
|
attachment_2 = self.env['ir.attachment'].create({
|
||
|
'raw': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=",
|
||
|
'name': 'file2.png',
|
||
|
'res_model': 'hr.expense',
|
||
|
'res_id': expense_2.id,
|
||
|
})
|
||
|
|
||
|
expense.message_main_attachment_id = attachment
|
||
|
expense_2.message_main_attachment_id = attachment_2
|
||
|
expenses = expense | expense_2
|
||
|
|
||
|
expense_sheet = self.env['hr.expense.sheet'].create({
|
||
|
'name': 'Expenses paid by company',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'expense_line_ids': expenses,
|
||
|
})
|
||
|
expense_sheet.action_submit_sheet()
|
||
|
expense_sheet.action_approve_expense_sheets()
|
||
|
expense_sheet.action_sheet_move_create()
|
||
|
|
||
|
self.assertRecordValues(expense_sheet.account_move_ids[0].attachment_ids, [{
|
||
|
'raw': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=",
|
||
|
'name': 'file1.png',
|
||
|
'res_model': 'account.move',
|
||
|
'res_id': expense_sheet.account_move_ids[0].id
|
||
|
}])
|
||
|
|
||
|
self.assertRecordValues(expense_sheet.account_move_ids[1].attachment_ids, [{
|
||
|
'raw': b"R0lGODdhAQABAIAAAP///////ywAAAAAAQABAAACAkQBADs=",
|
||
|
'name': 'file2.png',
|
||
|
'res_model': 'account.move',
|
||
|
'res_id': expense_sheet.account_move_ids[1].id
|
||
|
}])
|
||
|
|
||
|
def test_expense_payment_method(self):
|
||
|
default_payment_method_line = self.company_data['default_journal_bank'].outbound_payment_method_line_ids[0]
|
||
|
check_method = self.env['account.payment.method'].sudo().create({
|
||
|
'name': 'Print checks',
|
||
|
'code': 'check_printing_expense_test',
|
||
|
'payment_type': 'outbound',
|
||
|
})
|
||
|
new_payment_method_line = self.env['account.payment.method.line'].create({
|
||
|
'name': 'Check',
|
||
|
'payment_method_id': check_method.id,
|
||
|
'journal_id': self.company_data['default_journal_bank'].id,
|
||
|
})
|
||
|
|
||
|
expense_sheet = self.env['hr.expense.sheet'].create({
|
||
|
'name': 'Sheet test',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'payment_method_line_id': default_payment_method_line.id,
|
||
|
'expense_line_ids': [Command.create({
|
||
|
'name': 'test payment_mode',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'payment_mode': 'company_account',
|
||
|
'total_amount': 60,
|
||
|
'tax_ids': [self.tax_purchase_a.id, self.tax_purchase_b.id],
|
||
|
})],
|
||
|
})
|
||
|
|
||
|
self.assertRecordValues(expense_sheet, [{'payment_method_line_id': default_payment_method_line.id}])
|
||
|
expense_sheet.payment_method_line_id = new_payment_method_line
|
||
|
|
||
|
expense_sheet.action_submit_sheet()
|
||
|
expense_sheet.action_approve_expense_sheets()
|
||
|
expense_sheet.action_sheet_move_create()
|
||
|
self.assertRecordValues(expense_sheet.account_move_ids.payment_id, [{'payment_method_line_id': new_payment_method_line.id}])
|
||
|
|
||
|
def test_payment_edit_fields(self):
|
||
|
""" Test payment fields cannot be modified once linked with an expense
|
||
|
"""
|
||
|
sheet = self.env['hr.expense.sheet'].create({
|
||
|
'company_id': self.env.company.id,
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'name': 'test sheet 2',
|
||
|
'expense_line_ids': [
|
||
|
Command.create({
|
||
|
'name': 'expense_1',
|
||
|
'date': '2016-01-01',
|
||
|
'product_id': self.product_c.id,
|
||
|
'total_amount': 10.0,
|
||
|
'payment_mode': 'company_account',
|
||
|
'employee_id': self.expense_employee.id
|
||
|
}),
|
||
|
],
|
||
|
})
|
||
|
sheet.action_submit_sheet()
|
||
|
sheet.action_approve_expense_sheets()
|
||
|
sheet.action_sheet_move_create()
|
||
|
payment = sheet.account_move_ids.payment_id
|
||
|
|
||
|
with self.assertRaises(UserError, msg="Cannot edit payment amount after linking to an expense"):
|
||
|
payment.write({'amount': 500})
|
||
|
|
||
|
payment.write({'is_move_sent': True})
|
||
|
|
||
|
def test_expense_sheet_attachments_sync(self):
|
||
|
"""
|
||
|
Test that the hr.expense.sheet attachments stay in sync with the attachments associated with the expense lines
|
||
|
Syncing should happen when:
|
||
|
- When adding/removing expense_line_ids on a hr.expense.sheet <-> changing sheet_id on an expense
|
||
|
- When deleting an expense that is associated with an hr.expense.sheet
|
||
|
- When adding/removing an attachment of an expense that is associated with an hr.expense.sheet
|
||
|
"""
|
||
|
def assert_attachments_are_synced(sheet, attachments_on_sheet, sheet_has_attachment):
|
||
|
if sheet_has_attachment:
|
||
|
self.assertTrue(bool(attachments_on_sheet), "Attachment that belongs to the hr.expense.sheet only was removed unexpectedly")
|
||
|
self.assertSetEqual(
|
||
|
set(sheet.expense_line_ids.attachment_ids.mapped('checksum')),
|
||
|
set((sheet.attachment_ids - attachments_on_sheet).mapped('checksum')),
|
||
|
"Attachments between expenses and their sheet is not in sync.",
|
||
|
)
|
||
|
|
||
|
for sheet_has_attachment in (False, True):
|
||
|
expense_1, expense_2, expense_3 = self.env['hr.expense'].create([{
|
||
|
'name': 'expense_1',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'total_amount': 1000,
|
||
|
}, {
|
||
|
'name': 'expense_2',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'total_amount': 999,
|
||
|
}, {
|
||
|
'name': 'expense_3',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_c.id,
|
||
|
'total_amount': 998,
|
||
|
}])
|
||
|
self.env['ir.attachment'].create([{
|
||
|
'name': "test_file_1.txt",
|
||
|
'datas': base64.b64encode(b'content'),
|
||
|
'res_id': expense_1.id,
|
||
|
'res_model': 'hr.expense',
|
||
|
}, {
|
||
|
'name': "test_file_2.txt",
|
||
|
'datas': base64.b64encode(b'other content'),
|
||
|
'res_id': expense_2.id,
|
||
|
'res_model': 'hr.expense',
|
||
|
}, {
|
||
|
'name': "test_file_3.txt",
|
||
|
'datas': base64.b64encode(b'different content'),
|
||
|
'res_id': expense_3.id,
|
||
|
'res_model': 'hr.expense',
|
||
|
}])
|
||
|
|
||
|
sheet = self.env['hr.expense.sheet'].create({
|
||
|
'company_id': self.env.company.id,
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'name': 'test sheet',
|
||
|
'expense_line_ids': [Command.set([expense_1.id, expense_2.id, expense_3.id])],
|
||
|
})
|
||
|
|
||
|
sheet_attachment = self.env['ir.attachment'].create({
|
||
|
'name': "test_file_4.txt",
|
||
|
'datas': base64.b64encode(b'yet another different content'),
|
||
|
'res_id': sheet.id,
|
||
|
'res_model': 'hr.expense.sheet',
|
||
|
}) if sheet_has_attachment else self.env['ir.attachment']
|
||
|
|
||
|
assert_attachments_are_synced(sheet, sheet_attachment, sheet_has_attachment)
|
||
|
expense_1.attachment_ids.unlink()
|
||
|
assert_attachments_are_synced(sheet, sheet_attachment, sheet_has_attachment)
|
||
|
self.env['ir.attachment'].create({
|
||
|
'name': "test_file_1.txt",
|
||
|
'datas': base64.b64encode(b'content'),
|
||
|
'res_id': expense_1.id,
|
||
|
'res_model': 'hr.expense',
|
||
|
})
|
||
|
assert_attachments_are_synced(sheet, sheet_attachment, sheet_has_attachment)
|
||
|
expense_2.sheet_id = False
|
||
|
assert_attachments_are_synced(sheet, sheet_attachment, sheet_has_attachment)
|
||
|
expense_2.sheet_id = sheet
|
||
|
assert_attachments_are_synced(sheet, sheet_attachment, sheet_has_attachment)
|
||
|
sheet.expense_line_ids = [Command.set([expense_1.id, expense_3.id])]
|
||
|
assert_attachments_are_synced(sheet, sheet_attachment, sheet_has_attachment)
|
||
|
expense_3.unlink()
|
||
|
assert_attachments_are_synced(sheet, sheet_attachment, sheet_has_attachment)
|
||
|
sheet.attachment_ids.filtered(
|
||
|
lambda att: att.checksum in sheet.expense_line_ids.attachment_ids.mapped('checksum')
|
||
|
).unlink()
|
||
|
assert_attachments_are_synced(sheet, sheet_attachment, sheet_has_attachment)
|
||
|
|
||
|
def test_create_report_name(self):
|
||
|
"""
|
||
|
When an expense sheet is created from one or more expense, the report name is generated through the expense name or date.
|
||
|
As the expense sheet is created directly from the hr.expense._get_default_expense_sheet_values method,
|
||
|
we only need to test the method.
|
||
|
"""
|
||
|
expense_with_date_1, expense_with_date_2, expense_without_date = self.env['hr.expense'].create([{
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'name': f'test expense {i}',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_a.id,
|
||
|
'date': '2021-01-01',
|
||
|
'quantity': i + 1,
|
||
|
} for i in range(3)])
|
||
|
expense_without_date.date = False
|
||
|
|
||
|
# CASE 1: only one expense with or without date -> expense name
|
||
|
sheet_name = expense_with_date_1._get_default_expense_sheet_values()[0]['name']
|
||
|
self.assertEqual(expense_with_date_1.name, sheet_name, "The report name should be the same as the expense name")
|
||
|
sheet_name = expense_without_date._get_default_expense_sheet_values()[0]['name']
|
||
|
self.assertEqual(expense_without_date.name, sheet_name, "The report name should be the same as the expense name")
|
||
|
|
||
|
# CASE 2: two expenses with the same date -> expense date
|
||
|
expenses = expense_with_date_1 | expense_with_date_2
|
||
|
sheet_name = expenses._get_default_expense_sheet_values()[0]['name']
|
||
|
self.assertEqual(format_date(self.env, expense_with_date_1.date), sheet_name, "The report name should be the same as the expense date")
|
||
|
|
||
|
# CASE 3: two expenses with different dates -> date range
|
||
|
expense_with_date_2.date = '2021-01-02'
|
||
|
sheet_name = expenses._get_default_expense_sheet_values()[0]['name']
|
||
|
self.assertEqual(
|
||
|
f"{format_date(self.env, expense_with_date_1.date)} - {format_date(self.env, expense_with_date_2.date)}",
|
||
|
sheet_name,
|
||
|
"The report name should be the date range of the expenses",
|
||
|
)
|
||
|
|
||
|
# CASE 4: One or more expense doesn't have a date (single sheet) -> No fallback name
|
||
|
expenses |= expense_without_date
|
||
|
sheet_name = expenses._get_default_expense_sheet_values()[0]['name']
|
||
|
self.assertFalse(
|
||
|
sheet_name,
|
||
|
"The report (with the empty expense date) name should be empty as a fallback when several reports are created",
|
||
|
)
|
||
|
expenses.date = False
|
||
|
sheet_name = expenses._get_default_expense_sheet_values()[0]['name']
|
||
|
self.assertFalse(sheet_name, "The report name should be empty as a fallback")
|
||
|
|
||
|
# CASE 5: One or more expense doesn't have a date (multiple sheets) -> Fallback name
|
||
|
expenses |= self.env['hr.expense'].create([{
|
||
|
'company_id': self.company_data['company'].id,
|
||
|
'name': f'test expense by company {i}',
|
||
|
'employee_id': self.expense_employee.id,
|
||
|
'product_id': self.product_a.id,
|
||
|
'payment_mode': 'company_account',
|
||
|
'date': '2021-01-01',
|
||
|
'quantity': i + 1,
|
||
|
} for i in range(3)])
|
||
|
sheet_names = [sheet['name'] for sheet in expenses._get_default_expense_sheet_values()]
|
||
|
self.assertSequenceEqual(
|
||
|
("New Expense Report, paid by employee", format_date(self.env, expenses[-1].date)),
|
||
|
sheet_names,
|
||
|
"The report name should be 'New Expense Report, paid by (employee|company)' as a fallback",
|
||
|
)
|