# -*- coding: utf-8 -*- # Part of Odoo. See LICENSE file for full copyright and licensing details. import time import odoo.tests from unittest.mock import patch from datetime import date, datetime, timedelta from dateutil.relativedelta import relativedelta from odoo import fields, Command from odoo.tests import Form from odoo.addons.hr_payroll.tests.common import TestPayslipContractBase from odoo.exceptions import ValidationError from odoo.tools.float_utils import float_compare @odoo.tests.tagged('post_install', '-at_install') class TestHrPayrollAccountCommon(TestPayslipContractBase): @classmethod def setUpClass(cls): super().setUpClass() cls.env.user.group_ids |= cls.env.ref('hr_payroll.group_hr_payroll_manager') cls.env['account.chart.template'].try_loading('generic_coa', company=cls.company_us, install_demo=False) cls.work_contact = cls.env['res.partner'].create({'name': 'A work contact'}) cls.env['res.partner.bank'].create({ 'acc_number': 'IT22M8576110068R4A56E760901', 'partner_id': cls.work_contact.id, 'allow_out_payment': True, }) cls.work_address = cls.env['res.partner'].create({'name': 'A work address'}) partner_john = cls.env['res.partner'].create({ 'name': 'John', 'is_company': True, }) john_bank = cls.env['res.bank'].create({'name': 'ING'}) partner_bank_john = cls.env['res.partner.bank'].create({ 'acc_number': 'IT22M8576110068R4A56E760901', 'partner_id': partner_john.id, 'bank_id': john_bank.id, 'allow_out_payment': True, }) cls.hr_employee_john = cls.env['hr.employee'].create({ 'work_contact_id': partner_john.id, 'address_id': cls.work_address.id, 'birthday': '1984-05-01', 'children': 0.0, 'sex': 'male', 'country_id': cls.env.ref('base.in').id, 'marital': 'single', 'name': 'John', 'bank_account_ids': [partner_bank_john.id], }) salary_account = cls.env['account.account'].create({ 'name': "Salary Expense", 'code': "030303", 'account_type': "expense", }) salaries_journal = cls.env['account.journal'].create({ 'name': 'Test Salaries', 'type': 'general', 'code': 'SLR-TEST', 'default_account_id': salary_account.id, }) cls.hr_employee_mark = cls.env['hr.employee'].create({ 'work_contact_id': cls.work_contact.id, 'address_id': cls.work_address.id, 'birthday': '1984-05-01', 'children': 0.0, 'sex': 'male', 'country_id': cls.env.ref('base.in').id, 'marital': 'single', 'name': 'Mark', }) cls.account_journal = cls.env['account.journal'].create({ 'name' : 'MISC', 'code' : 'MSC', 'type' : 'general', }) cls.hr_structure_softwaredeveloper = cls.env['hr.payroll.structure'].create({ 'name': 'Salary Structure for Software Developer Two', 'rule_ids': [ (0, 0, { 'name': 'Basic Salary', 'amount_select': 'percentage', 'amount_percentage': 100, 'amount_percentage_base': 'version.wage', 'code': 'BASIC', 'category_id': cls.env.ref('hr_payroll.BASIC').id, 'sequence': 1, }), (0, 0, { 'name': 'Provident Fund', 'amount_select': 'percentage', 'sequence': 120, 'amount_percentage': -12.5, 'amount_percentage_base': 'version.wage', 'code': 'PF', 'category_id': cls.env.ref('hr_payroll.DED').id, }), (0, 0, { 'name': 'Meal Voucher', 'amount_select': 'fix', 'amount_fix': 10, 'quantity': "'WORK100' in worked_days and worked_days['WORK100'].number_of_days", 'code': 'MA', 'category_id': cls.env.ref('hr_payroll.ALW').id, 'sequence': 16, }), (0, 0, { 'name': 'Conveyance Allowance', 'amount_select': 'fix', 'amount_fix': 800, 'code': 'CA', 'category_id': cls.env.ref('hr_payroll.ALW').id, 'sequence': 10, }), (0, 0, { 'name': 'House Rent Allowance', 'amount_select': 'percentage', 'amount_percentage': 40, 'amount_percentage_base': 'version.wage', 'code': 'HRA', 'category_id': cls.env.ref('hr_payroll.ALW').id, 'sequence': 5, }) ], 'type_id': cls.env['hr.payroll.structure.type'].create({'name': 'Employee', 'country_id': False}).id, 'journal_id': salaries_journal.id, }) cls.professional_tax_rule = cls.env['hr.salary.rule'].create({ 'name': 'Professional Tax', 'amount_select': 'fix', 'sequence': 150, 'amount_fix': -200, 'code': 'PT', 'category_id': cls.env.ref('hr_payroll.DED').id, 'struct_id': cls.hr_structure_softwaredeveloper.id, }) cls.hr_structure_type = cls.env['hr.payroll.structure.type'].create({ 'name': 'Salary Structure Type', 'struct_ids': [(4, cls.hr_structure_softwaredeveloper.id)], 'default_struct_id': cls.hr_structure_softwaredeveloper.id, }) cls.hr_employee_john.version_id.write({ 'contract_date_end': fields.Date.to_string(datetime.now() + timedelta(days=365)), 'contract_date_start': date(2010, 1, 1), 'date_version': date(2010, 1, 1), 'wage': 5000.0, 'employee_id': cls.hr_employee_john.id, 'structure_type_id': cls.hr_structure_type.id, }) cls.hr_contract_john = cls.hr_employee_john.version_id cls.hr_payslip_john = cls.env['hr.payslip'].create({ 'employee_id': cls.hr_employee_john.id, 'struct_id' : cls.hr_structure_softwaredeveloper.id, 'version_id': cls.hr_contract_john.id, 'journal_id': cls.account_journal.id, 'name': 'Test Payslip John', }) cls.hr_employee_mark.version_id.write({ 'contract_date_end': fields.Date.to_string(datetime.now() + timedelta(days=365)), 'contract_date_start': date(2010, 1, 1), 'date_version': date(2010, 1, 1), 'wage': 5000.0, 'structure_type_id': cls.hr_structure_type.id, }) cls.hr_contract_mark = cls.hr_employee_mark.version_id cls.hr_payslip_john.date_from = time.strftime('%Y-%m-01') # YTI Clean that brol cls.hr_payslip_john.date_to = str(datetime.now() + relativedelta(months=+1, day=1, days=-1))[:10] cls.payslip_run = cls.env['hr.payslip.run'].create({ 'date_start': time.strftime('%Y-%m-01'), 'date_end': str(datetime.now() + relativedelta(months=+1, day=1, days=-1))[:10], 'name': 'Payslip for Employee' }) @odoo.tests.tagged('post_install', '-at_install') class TestHrPayrollAccount(TestHrPayrollAccountCommon): def test_00_hr_payslip_run(self): """ Checking the process of payslip run when you create payslip(s) in a payslip run and you validate the payslip run. """ # I verify the payslip run is in draft state. self.assertEqual(self.payslip_run.state, '01_ready', 'State not changed!') # I select employees and generate payslips by clicking on Select button wizard. self.payslip_run.generate_payslips(employee_ids=[self.hr_employee_john.id, self.hr_employee_mark.id]) # I verify if the payslip run has payslip(s). self.assertTrue(len(self.payslip_run.slip_ids) > 0, 'Payslip(s) not added!') # I verify the payslips lines have been computed self.assertTrue(all(slip.line_ids for slip in self.payslip_run.slip_ids), 'Payslip not computed') # I confirm the payslip run. self.payslip_run.action_validate() # I verify the payslips is in done state. for slip in self.payslip_run.slip_ids: self.assertEqual(slip.state, 'validated', 'State not changed!') # I verify the payslip run is in close state. self.assertEqual(self.payslip_run.state, '02_close', 'State not changed!') # I verify that the Accounting Entries are created. for slip in self.payslip_run.slip_ids: self.assertTrue(slip.move_id, 'Accounting Entries has not been created!') def test_01_hr_payslip_run(self): """ Checking the process of payslip run when you create payslip in a payslip run and you validate the payslip(s). """ # I select employees and generate payslips by clicking on Select button wizard. self.payslip_run.generate_payslips(employee_ids=[self.hr_employee_john.id, self.hr_employee_mark.id]) # I verify if the payslip run has payslip(s). self.assertTrue(len(self.payslip_run.slip_ids) > 0, 'Payslip(s) not added!') # I verify the payslips lines have been computed self.assertTrue(all(slip.line_ids for slip in self.payslip_run.slip_ids), 'Payslip not computed') # I confirm all payslip(s) in the payslip run. self.payslip_run.slip_ids.action_payslip_done() # I verify the payslip(s) is in done state. for slip in self.payslip_run.slip_ids: self.assertEqual(slip.state, 'validated', 'State not changed!') # I verify the payslip run is in close state. self.assertEqual(self.payslip_run.state, '02_close', 'State not changed!') # I verify that the Accounting Entries are created. for slip in self.payslip_run.slip_ids: self.assertTrue(slip.move_id, 'Accounting Entries has not been created!') def test_02_hr_payslip(self): """ Checking the process of payslip run when you create payslip in a payslip run and you cancel the payslip(s). """ # I select employees and generate payslips by clicking on Select button wizard. self.payslip_run.generate_payslips(employee_ids=[self.hr_employee_john.id, self.hr_employee_mark.id]) # I verify if the payslip run has payslip(s). self.assertTrue(len(self.payslip_run.slip_ids) > 0, 'Payslip(s) not added!') # I verify the payslips lines have been computed self.assertTrue(all(slip.line_ids for slip in self.payslip_run.slip_ids), 'Payslip not computed') # I confirm all payslip(s) in the payslip run. self.payslip_run.slip_ids.action_payslip_cancel() # I verify the payslip(s) is in cancel state. for slip in self.payslip_run.slip_ids: self.assertEqual(slip.state, 'cancel', 'State not changed!') # I verify the payslip run is in close state. self.assertEqual(self.payslip_run.state, '04_cancel', 'State not changed!') # I verify that the Accounting Entries are not created. for slip in self.payslip_run.slip_ids: self.assertFalse(slip.move_id, 'Accounting Entries has been created!') def test_03_hr_payslip(self): """ Checking the process of payslip run when you create payslip in a payslip run and you cancel a payslip and confirm another. """ # I select employees and generate payslips by clicking on Select button wizard. self.payslip_run.generate_payslips(employee_ids=[self.hr_employee_john.id, self.hr_employee_mark.id]) # Test only with payslip that were just generated. Remove the payslip from setup self.payslip_run.write({'slip_ids': [(3, self.hr_payslip_john.id)]}) # I verify if the payslip run has payslip(s). self.assertTrue(len(self.payslip_run.slip_ids) > 0, 'Payslip(s) not added!') # I verify the payslips lines have been computed self.assertTrue(all(slip.line_ids for slip in self.payslip_run.slip_ids), 'Payslip not computed') # I cancel one payslip and confirm another in the payslip run. payslip_1 = self.payslip_run.slip_ids[0] payslip_2 = self.payslip_run.slip_ids[1] payslip_1.action_payslip_cancel() payslip_2.action_payslip_done() # I verify the payslips' states. self.assertEqual(payslip_1.state, 'cancel', 'State not changed!') self.assertEqual(payslip_2.state, 'validated', 'State not changed!') # I verify the payslip run is in close state. self.assertEqual(self.payslip_run.state, '02_close', 'State not changed!') # I verify that the Accounting Entries are created or not. self.assertFalse(payslip_1.move_id, 'Accounting Entries has been created!') self.assertTrue(payslip_2.move_id, 'Accounting Entries has not been created!') def test_04_hr_payslip(self): """ Checking the process of payslip run when you create payslip in a payslip run and you cancel a payslip and after you confirm the payslip run. """ # I select employees and generate payslips by clicking on Select button wizard. self.payslip_run.generate_payslips(employee_ids=[self.hr_employee_john.id, self.hr_employee_mark.id]) # I verify if the payslip run has payslip(s). self.assertTrue(len(self.payslip_run.slip_ids) > 0, 'Payslip(s) not added!') # I verify the payslips lines have been computed self.assertTrue(all(slip.line_ids for slip in self.payslip_run.slip_ids), 'Payslip not computed') # Storing the references to slip_ids[0] and slip_ids[1] # for later use, because the order of the One2many is not guaranteed slip0 = self.payslip_run.slip_ids[0] slip1 = self.payslip_run.slip_ids[1] # I cancel one payslip and after i confirm the payslip run. slip0.action_payslip_cancel() self.payslip_run.action_validate() # I verify the payslips' states. self.assertEqual(slip0.state, 'cancel', 'State not changed!') self.assertEqual(slip1.state, 'validated', 'State not changed!') # I verify the payslip run is in close state. self.assertEqual(self.payslip_run.state, '02_close', 'State not changed!') # I verify that the Accounting Entries are created or not. self.assertFalse(slip0.move_id, 'Accounting Entries has been created!') self.assertTrue(slip1.move_id, 'Accounting Entries has not been created!') def test_05_hr_payslip(self): """ Checking the process of payslip run when you create payslip run from a payslip and you validate it. """ # I verify if the payslip has not already a payslip run. self.assertFalse(self.hr_payslip_john.payslip_run_id, 'There is already a payslip run!') # I create and i add the payslip run to the payslip. self.hr_payslip_john.payslip_run_id = self.env['hr.payslip.run'].create({ 'date_end': '2011-09-30', 'date_start': '2011-09-01', 'name': 'Payslip for Employee', }) # I validate the payslip. self.hr_payslip_john.action_payslip_done() # I verify the payslip is in done state. self.assertEqual(self.hr_payslip_john.state, 'validated', 'State not changed!') # I verify the payslip run is in close state. self.assertEqual(self.hr_payslip_john.payslip_run_id.state, '02_close', 'State not changed!') # I verify that the Accounting Entry is created. self.assertTrue(self.hr_payslip_john.move_id, 'Accounting entry has not been created!') def test_06_hr_payslip(self): """ Checking the process of payslip run when you create payslip run from a payslip and you validate the payslip run. """ # I verify if the payslip has not already a payslip run. self.assertFalse(self.hr_payslip_john.payslip_run_id, 'There is already a payslip run!') # I create and i add the payslip run to the payslip. self.hr_payslip_john.payslip_run_id = self.env['hr.payslip.run'].create({ 'date_end': '2011-09-30', 'date_start': '2011-09-01', 'name': 'Payslip for Employee', }) self.hr_payslip_john.compute_sheet() # I validate the payslip run. self.hr_payslip_john.payslip_run_id.action_validate() # I verify the payslip is in validated state. self.assertEqual(self.hr_payslip_john.state, 'validated', 'State not changed!') # I verify the payslip run is in close state. self.assertEqual(self.hr_payslip_john.payslip_run_id.state, '02_close', 'State not changed!') # I verify that the Accounting Entry is created. self.assertTrue(self.hr_payslip_john.move_id, 'Accounting entry has not been created!') def test_07_hr_payslip(self): """ Checking the process of payslip run when you create payslip run from a payslip and you cancel it. """ # I verify if the payslip has not already a payslip run. self.assertFalse(self.hr_payslip_john.payslip_run_id, 'There is already a payslip run!') # I create and i add the payslip run to the payslip. self.hr_payslip_john.payslip_run_id = self.env['hr.payslip.run'].create({ 'date_end': '2011-09-30', 'date_start': '2011-09-01', 'name': 'Payslip for Employee' }) # I cancel the payslip. self.hr_payslip_john.action_payslip_cancel() # I verify the payslip is in cancel state. self.assertEqual(self.hr_payslip_john.state, 'cancel', 'State not changed!') # I verify the payslip run is in close state. self.assertEqual(self.hr_payslip_john.payslip_run_id.state, '04_cancel', 'State not changed!') # I verify that the Accounting Entry is not created. self.assertFalse(self.hr_payslip_john.move_id, 'Accounting entry has been created!') def test_08_hr_payslip(self): """ Checking the process of a payslip when you validate it and it has not a payslip run. """ # I verify if the payslip has not already a payslip run. self.assertFalse(self.hr_payslip_john.payslip_run_id, 'There is already a payslip run!') # I validate the payslip. self.hr_payslip_john.action_payslip_done() # I verify the payslip is in validated state. self.assertEqual(self.hr_payslip_john.state, 'validated', 'State not changed!') # I verify that the Accounting Entry is created. self.assertTrue(self.hr_payslip_john.move_id, 'Accounting entry has not been created!') def test_09_hr_payslip(self): """Checking if taxes are added on a payslip accounting entry when there is a default tax on the journal""" # Create a default tax for the account on the salary rule. tax_account = self.env['account.account'].create({ 'name': 'Rental Tax', 'code': '777777', 'account_type': 'asset_current', }) hra_tax = self.env['account.tax'].create({ 'name': "hra_tax", 'amount_type': 'percent', 'amount': 10.0, 'type_tax_use': 'sale', 'invoice_repartition_line_ids': [ Command.create({'factor_percent': 100, 'repartition_type': 'base'}), Command.create({'factor_percent': 100, 'account_id': tax_account.id}), ], 'refund_repartition_line_ids': [ Command.create({'factor_percent': 100, 'repartition_type': 'base'}), Command.create({'factor_percent': 100, 'account_id': tax_account.id}), ], }) # Create a account for the HRA salary rule. hra_account = self.env['account.account'].create({ 'name': 'House Rental', 'code': '654321', 'account_type': 'income', 'tax_ids': [(4, hra_tax.id)] }) # Assign the account to the salary rule and the rule to the hr structure. self.hra_rule.account_credit = hra_account self.hra_rule.account_debit = hra_account self.hr_structure_softwaredeveloper.rule_ids = [(4, self.hra_rule.id)] self.hr_payslip_john.compute_sheet() # Validate the payslip. self.hr_payslip_john.action_payslip_done() # Verify that the taxes are applied on hra move lines. for line in self.hr_payslip_john.move_id.line_ids: if line.account_id.id == hra_account.id: self.assertEqual(line.tax_ids, hra_tax, 'The account default tax is not added to move lines!') def test_load_payroll_accounts_not_called_without_context_key(self): """_load_payroll_accounts must NOT be called when chart_template_load is absent.""" target = 'odoo.addons.hr_payroll_account.models.account_chart_template.AccountChartTemplate._load_payroll_accounts' with patch(target) as mock_load: ChartTemplate = self.env['account.chart.template'] ChartTemplate._post_load_data(None, self.env.company, {}) mock_load.assert_not_called() def test_payslip_refund(self): """ Checking if refunding a payslip creates the correct invoice lines """ # Create an account for the HRA salary rule test_account = self.env['account.account'].create({ 'name': 'House Rental', 'code': '654321', 'account_type': 'income', }) # Assign the account to the salary rule and the rule to the hr structure self.hra_rule.account_credit = test_account self.hra_rule.account_debit = test_account self.hr_structure_softwaredeveloper.rule_ids = [(4, self.hra_rule.id)] # Validate the payslip self.hr_payslip_john.compute_sheet() self.hr_payslip_john.action_payslip_done() self.assertEqual(self.hr_payslip_john.state, 'validated', 'State not changed!') self.assertTrue(self.hr_payslip_john.move_id, 'Accounting entry has not been created!') invoice_lines = self.hr_payslip_john.move_id.line_ids.sorted('amount_currency') # Verify that there are 2 invoice lines # 1. amount = -2000, credit = 2000, debit = 0 # 2. amount = 2000, credit = 0, debit = 2000 line_amount = self.hra_rule.amount_percentage / 100 * self.hr_payslip_john.version_id.wage self.assertEqual(len(invoice_lines), 2, 'There should be 2 invoice lines') self.assertEqual(invoice_lines[0].amount_currency, -line_amount) self.assertEqual(invoice_lines[0].credit, line_amount) self.assertEqual(invoice_lines[0].debit, 0) self.assertEqual(invoice_lines[1].amount_currency, line_amount) self.assertEqual(invoice_lines[1].credit, 0) self.assertEqual(invoice_lines[1].debit, line_amount) # Post the invoice self.hr_payslip_john.move_id.action_post() # Refund the payslip self.hr_payslip_john.refund_sheet() refund_slip = self.env['hr.payslip'].search([ ('employee_id', '=', self.hr_employee_john.id), ('credit_note', '=', True) ]) refund_slip.action_payslip_done() self.assertEqual(refund_slip.state, 'validated', 'State not changed!') self.assertTrue(refund_slip.move_id, 'Accounting entry has not been created!') invoice_lines = refund_slip.move_id.line_ids # Check that there are 2 invoice lines, and they are the inverse of the original payslip # 1. amount = -2000, credit = 2000, debit = 0 # 2. amount = 2000, credit = 0, debit = 2000 line_amount = self.hra_rule.amount_percentage / 100 * self.hr_payslip_john.version_id.wage self.assertEqual(len(invoice_lines), 2, 'There should be 2 invoice lines') self.assertEqual(invoice_lines[0].amount_currency, -line_amount) self.assertEqual(invoice_lines[0].credit, line_amount) self.assertEqual(invoice_lines[0].debit, 0) self.assertEqual(invoice_lines[1].amount_currency, line_amount) self.assertEqual(invoice_lines[1].credit, 0) self.assertEqual(invoice_lines[1].debit, line_amount) def _test_bank_account_partner_payment_payslip(self, add_isr_bank): """ Check that the payment generated for Professionnal Tax is made to the correct bank account (previously, the selected account of the payment was always the employee bank account, whatever the partner specified in the payment) """ tax_partner = self.env['res.partner'].create({ 'name': 'Regular Tax partner', 'is_company': True, }) tax_bank = self.env['res.bank'].create({'name': 'Belfius'}) if add_isr_bank: isr_partner_bank = self.env['res.partner.bank'].create({ 'acc_number': 'IT77H400725028682A0R202P050', 'partner_id': tax_partner.id, 'bank_id': tax_bank.id, 'allow_out_payment': True, }) tax_debit_account = self.env['account.account'].create({ 'name': "Professional tax", 'code': "020202", 'account_type': "liability_payable", 'reconcile': True, }) salary_payable = self.env['account.account'].create({ 'name': 'Salary Payable', 'code': '2300', 'reconcile': True, 'account_type': 'liability_payable', }) self.env['hr.salary.rule'].create({ 'name': 'Net Salary', 'amount_select': 'code', 'amount_python_compute': 'result = categories["BASIC"] + categories["ALW"] + categories["DED"]', 'code': 'NET', 'category_id': self.env.ref('hr_payroll.NET').id, 'sequence': 200, 'account_credit': salary_payable.id, 'struct_id': self.hr_structure_softwaredeveloper.id, 'employee_move_line': True, }) # Set necessary properties so that 'action_payslip_done' will create lines for the payslip self.professional_tax_rule.account_debit = tax_debit_account.id self.professional_tax_rule.partner_id = tax_partner.id self.hr_payslip_john.compute_sheet() self.hr_payslip_john.action_payslip_done() # 1. Debit for Salary Expense # 2. Credit for Salary Payable # 3. Credit for Professionnal Tax self.assertEqual(len(self.hr_payslip_john.move_id.line_ids), 3, "3 lines should've been created for John payslip !") self.hr_payslip_john.move_id.action_post() payment_register_wizard = Form.from_action(self.env, self.hr_payslip_john.action_register_payment()) saved_form = payment_register_wizard.save() # Simulate calling `action_create_payments` method from `account.payment.register` payments = saved_form._create_payments() self.assertEqual(len(payments), 2, "2 payments should've been made here (one for each line except for the salary expense associated " "with the salary rule created in this test).") for payment in payments: matching_lines = self.hr_payslip_john.move_id.line_ids.filtered( lambda line: float_compare(abs(line.amount_currency), payment.amount, precision_digits=3) == 0) self.assertEqual(len(matching_lines), 1, "1 line should match the payment here.") if payment.partner_id == self.hr_payslip_john.employee_id.work_contact_id: self.assertEqual(payment.partner_bank_id.id, self.hr_payslip_john.employee_id.bank_account_ids[0].id, "As the partner of the payment is the employee of the payslip, the partner_bank_id of the payment should be " f"the employee's bank account. Instead, we have the bank account of '{payment.partner_bank_id.partner_id.name}'.") else: if add_isr_bank: self.assertEqual(payment.partner_bank_id.id, isr_partner_bank.partner_id.bank_ids[0].id, "As the partner of the payment is " f"'{isr_partner_bank.partner_id.name}', the 'partner_bank_id' should've been set to the bank of '{isr_partner_bank.partner_id.name}' !") else: self.assertFalse(payment.partner_bank_id.id, f"As the partner of the payment is '{tax_partner.name}', " "the 'partner_bank_id' shouldn't be set because the partner has no bank account !") def test_bank_account_partner_payment_payslip_partner_with_bank_account(self): self._test_bank_account_partner_payment_payslip(True) def test_bank_account_partner_payment_payslip_partner_without_bank_account(self): self._test_bank_account_partner_payment_payslip(False) def test_payslip_cancel_1(self): """ Checking if canceling a payslip unlinks the draft associated entry """ # Create an account for the HRA salary rule test_account = self.env['account.account'].create({ 'name': 'House Rental', 'code': '654321', 'account_type': 'income', }) # Assign the account to the salary rule and the rule to the hr structure self.hra_rule.account_credit = test_account self.hra_rule.account_debit = test_account self.hr_structure_softwaredeveloper.rule_ids = [Command.link(self.hra_rule.id)] # Create accounting entry self.hr_payslip_john.compute_sheet() self.hr_payslip_john.action_payslip_done() self.assertTrue(self.hr_payslip_john.move_id, 'Accounting entry has not been created!') invoice = self.hr_payslip_john.move_id # Cancel the payslip self.hr_payslip_john.action_payslip_cancel() self.assertFalse(invoice.exists(), 'Invoice has not been deleted') self.assertFalse(self.hr_payslip_john.move_id, 'Accounting entry has not been deleted!') def test_payslip_cancel_2(self): """ Checking if canceling a payslip reverses the posted entry """ self.hr_payslip_john.journal_id.restrict_mode_hash_table = True # Create an account for the HRA salary rule test_account = self.env['account.account'].create({ 'name': 'House Rental', 'code': '654321', 'account_type': 'income', }) # Assign the account to the salary rule and the rule to the hr structure self.hra_rule.account_credit = test_account self.hra_rule.account_debit = test_account self.hr_structure_softwaredeveloper.rule_ids = [Command.link(self.hra_rule.id)] # Create accounting entry self.hr_payslip_john.compute_sheet() self.hr_payslip_john.action_payslip_done() self.assertTrue(self.hr_payslip_john.move_id, 'Accounting entry has not been created!') invoice = self.hr_payslip_john.move_id invoice.action_post() self.assertNotEqual(invoice.inalterable_hash, False) # Cancel the payslip: posted entry will be reversed self.hr_payslip_john.action_payslip_cancel() self.assertTrue(invoice.exists(), 'Invoice has been deleted') line_amount = self.hra_rule.amount_percentage / 100 * self.hr_payslip_john.version_id.wage reverse_invoice = self.env['account.move.line'].search([ ('amount_currency', '=', line_amount), ('move_id', '!=', invoice.id), ]) self.assertTrue(reverse_invoice, 'Reverse move not created') def test_payslip_paid_create_journal_entry(self): """ Check that you cannot create a journal entry for a paid payslip """ # I compute the payslip sheet. self.hr_payslip_john.compute_sheet() # I validate the payslip. self.hr_payslip_john.action_payslip_done() # I mark the payslip as paid. self.hr_payslip_john.action_payslip_paid() # I verify that an error is thrown if we try to recreate a journal entry on paid payslip. self.assertRaises(ValidationError, self.hr_payslip_john.action_payslip_done)