Files
enterprise-addons/hr_payroll_account_iso20022/tests/test_payroll_sepa.py
T

392 lines
16 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import base64
from lxml import etree
from datetime import date, datetime, timedelta
from odoo import Command, fields
from odoo.addons.hr_payroll_account.tests.test_hr_payroll_account import TestHrPayrollAccountCommon
from odoo.tests.common import test_xsd
from odoo.tests import tagged, Form
class TestPayrollSEPACreditTransferCommon(TestHrPayrollAccountCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.env.company.currency_id = cls.env.ref('base.USD')
cls.env.user.company_id.write({
'iso20022_orgid_id': "0468651441",
'country_id': cls.env.ref('base.us').id,
})
cls.bank = cls.env['res.bank'].create({
'name': 'BNP',
'bic': 'GEBABEBB',
})
cls.res_partner_bank = cls.env['res.partner.bank'].create({
'acc_number': 'BE32707171912447',
'partner_id': cls.work_contact.id,
'acc_type': 'bank',
'bank_id': cls.bank.id,
})
cls.bank_partner = cls.env['res.partner.bank'].create({
'acc_number': 'BE84567968814145',
'acc_type': 'iban',
'partner_id': cls.company_us.partner_id.id,
})
cls.hr_employee_john.bank_account_ids = [Command.link(cls.res_partner_bank.id)]
cls.bank_journal = cls.env['account.journal'].create({
'name': 'Bank',
'code': 'BNK',
'type': 'bank',
'bank_id': cls.bank.id,
'bank_account_id': cls.bank_partner.id,
})
cls.bank_journal.sepa_pain_version = 'pain.001.001.09'
# ============================= MIURA =============================
cls.miura_be_company = cls.env['res.company'].create({
'name': 'Miura BE company',
'country_id': cls.env.ref('base.be').id,
'currency_id': cls.env.ref('base.EUR').id,
'batch_payroll_move_lines': False,
})
miura_transfer_account = cls.env['account.account'].create({
'name': 'Miura Company Transfer Account',
'code': '994987',
'reconcile': True,
'account_type': 'liability_payable',
'company_ids': (cls.miura_be_company.id,),
})
cls.miura_be_company.transfer_account_id = miura_transfer_account.id
cls.bnp_bank = cls.env['res.bank'].create({
'name': 'Miura BNP',
'bic': 'GEBABEBB',
})
cls.miura_work_contact = cls.env['res.partner'].create({'name': 'Miura work contact'})
cls.miura_partner_bank = cls.env['res.partner.bank'].create({
'acc_number': 'BE91073397502076',
'partner_id': cls.miura_work_contact.id,
'acc_type': 'bank',
'bank_id': cls.bnp_bank.id,
'allow_out_payment': True,
})
cls.salary_work_contact = cls.env['res.partner'].create({'name': 'Salary work contact'})
cls.salary_partner_bank = cls.env['res.partner.bank'].create({
'acc_number': 'BE34438537755990',
'partner_id': cls.salary_work_contact.id,
'acc_type': 'bank',
'bank_id': cls.bnp_bank.id,
'allow_out_payment': True,
})
cls.miura_work_address = cls.env['res.partner'].create({'name': 'Miura work address'})
cls.hr_employee_miura = cls.env['hr.employee'].create({
'work_contact_id': cls.miura_work_contact.id,
'address_id': cls.miura_work_address.id,
'birthday': '1966-07-11',
'children': 0.0,
'sex': 'male',
'marital': 'single',
'name': 'Kentaro',
'bank_account_ids': [cls.miura_partner_bank.id],
'company_id': cls.miura_be_company.id,
})
salary_payable = cls.env['account.account'].create({
'name': 'Salary Payable',
'code': '2300',
'reconcile': True,
'account_type': 'liability_payable',
'company_ids': (cls.miura_be_company.id,),
})
salary_account = cls.env['account.account'].create({
'name': "Salary Expense",
'code': "092039",
'account_type': "expense",
'company_ids': (cls.miura_be_company.id,),
})
cls.miura_salaries_journal = cls.env['account.journal'].create({
'name': 'Sepa Test Salaries',
'type': 'credit',
'code': 'SEPA-SLR-TEST',
'default_account_id': salary_account.id,
'sepa_pain_version': 'pain.001.001.09',
'currency_id': cls.env.ref('base.EUR').id,
'company_id': cls.miura_be_company.id,
'bank_account_id': cls.salary_partner_bank.id,
})
cls.hr_structure_mangaka = cls.env['hr.payroll.structure'].create({
'name': 'Salary Structure for Mangaka',
'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,
}), (0, 0, {
'name': 'Net Salary',
'amount_select': 'code',
'amount_python_compute': 'result = categories["BASIC"] + categories["ALW"] + categories["DED"]',
'code': 'NET',
'category_id': cls.env.ref('hr_payroll.NET').id,
'sequence': 200,
'account_credit': salary_payable.id,
'employee_move_line': True,
})
],
'type_id': cls.env['hr.payroll.structure.type'].create({'name': 'Employee', 'country_id': False}).id,
'journal_id': cls.miura_salaries_journal.id,
})
hr_structure_type = cls.env['hr.payroll.structure.type'].create({
'name': 'Salary Structure Type',
'struct_ids': [(4, cls.hr_structure_mangaka.id)],
'default_struct_id': cls.hr_structure_mangaka.id,
})
cls.env['hr.version'].browse(cls.hr_employee_miura.version_id.id).write({
'name': 'Miura contract',
'contract_date_end': fields.Date.to_string(datetime.now() + timedelta(days=365)),
'contract_date_start': date(2010, 1, 1),
'wage': 5000.0,
'employee_id': cls.hr_employee_miura.id,
'structure_type_id': hr_structure_type.id,
})
cls.hr_payslip_miura = cls.env['hr.payslip'].create({
'employee_id': cls.hr_employee_miura.id,
'struct_id': cls.hr_structure_mangaka.id,
'journal_id': cls.miura_salaries_journal.id,
'name': 'Test Payslip Miura',
})
sepa_payment_method_line = cls.env['account.payment.method.line'].create({
'payment_method_id': cls.env.ref('account_iso20022.account_payment_method_sepa_ct').id,
'journal_id': cls.miura_salaries_journal.id,
})
cls.miura_salaries_journal.outbound_payment_method_line_ids |= sepa_payment_method_line
@tagged('post_install', '-at_install')
class TestPayrollSEPACreditTransfer(TestPayrollSEPACreditTransferCommon):
def test_00_hr_payroll_account_iso20022(self):
""" Checking the process of payslip when you create a SEPA payment. """
# 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 make the SEPA payment.
file = self.env['hr.payroll.payment.report.wizard'].create({
'payslip_ids': self.hr_payslip_john.ids,
'payslip_run_id': self.hr_payslip_john.payslip_run_id.id,
'export_format': 'sepa',
'journal_id': self.bank_journal.id,
})._create_sepa_binary()
# I verify if a file is created.
self.assertTrue(file, 'SEPA payment has not been created!')
# I verify the payslip is in validated state.
self.assertEqual(self.hr_payslip_john.state, 'validated', 'State should not change!')
def test_01_hr_payroll_account_iso20022(self):
""" Checking the process of payslip run when you create a SEPA payment. """
# I verify the payslip run is in draft state.
self.assertEqual(self.payslip_run.state, '01_ready', 'State not changed!')
# I create a payslip employee.
self.payslip_run.generate_payslips(employee_ids=[self.hr_employee_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 confirm the payslip run.
self.payslip_run.action_validate()
# I verify the payslip run is in close state.
self.assertEqual(self.payslip_run.state, '02_close', 'State not changed!')
# I make the SEPA payment.
file = self.env['hr.payroll.payment.report.wizard'].create({
'payslip_ids': self.payslip_run.slip_ids.ids,
'payslip_run_id': self.payslip_run.id,
'export_format': 'sepa',
'journal_id': self.bank_journal.id,
})._create_sepa_binary()
# I verify if a file is created for the payslip run.
self.assertTrue(file, 'SEPA payment has not been created!')
# I verify the payslip is in paid state.
self.assertEqual(self.payslip_run.state, '02_close', 'State should not change!')
def test_02_hr_payroll_account_iso20022_ch(self):
self.assertEqual(self.payslip_run.state, '01_ready')
# I create a payslip employee.
self.payslip_run.generate_payslips(employee_ids=[self.hr_employee_john.id])
self.assertTrue(len(self.payslip_run.slip_ids) > 0)
self.payslip_run.action_validate()
self.assertEqual(self.payslip_run.state, '02_close')
file = self.env['hr.payroll.payment.report.wizard'].create({
'payslip_ids': self.payslip_run.slip_ids.ids,
'payslip_run_id': self.payslip_run.id,
'export_format': 'iso20022_ch',
'journal_id': self.bank_journal.id,
})._create_sepa_binary()
self.assertTrue(file)
self.assertEqual(self.payslip_run.state, '02_close')
def test_sepa_payslip_partner_bank_id(self):
""" Check that the "partner_bank_id" is set after account_register_payment wizard has
been initialized and that the action_create_payments (action launched when the
user clicks on "Create Payments" button of the account_register_payment wizard)
doesn't raise any error.
This test is not following any real spec, its only purpose is to check that the payslip
sepa payment flow works (so feel free to modify it if you feel like something's wrong)
"""
self.hr_payslip_miura.compute_sheet()
self.hr_payslip_miura.action_payslip_done()
self.hr_payslip_miura.move_id.action_post()
sepa_payment_method_line = self.env['account.payment.method.line'].create({
'payment_method_id': self.env.ref('account_iso20022.account_payment_method_sepa_ct').id,
'journal_id': self.miura_salaries_journal.id,
})
action_register_payment = self.hr_payslip_miura.action_register_payment()
register_payment_form = Form.from_action(self.env, action_register_payment)
register_payment_form.payment_method_line_id = sepa_payment_method_line
saved_form = register_payment_form.save()
self.assertEqual(saved_form.partner_bank_id.id, self.miura_partner_bank.id)
saved_form.action_create_payments()
def test_sepa_export_multi_bank_account_instr_id_uniqueness(self):
""" Test that a payslip split across multiple bank accounts generates unique SEPA InstrId (e.g., ID-1, ID-2) """
self.miura_work_contact.country_id = self.env.ref('base.be')
other_account = self.env['res.partner.bank'].create({
'acc_number': 'BE32707171912490',
'partner_id': self.miura_work_contact.id,
'acc_type': 'bank',
'bank_id': self.bnp_bank.id,
})
self.hr_employee_miura.write({
'bank_account_ids': [Command.link(other_account.id)],
'salary_distribution': {
str(self.miura_partner_bank.id): {'sequence': 1, 'amount': 50.0, 'amount_is_percentage': True},
str(other_account.id): {'sequence': 2, 'amount': 50.0, 'amount_is_percentage': True},
}
})
self.hr_payslip_miura.compute_sheet()
self.hr_payslip_miura.action_payslip_done()
self.env['hr.payroll.payment.report.wizard'].create({
'payslip_ids': self.hr_payslip_miura.ids,
'export_format': 'sepa',
'journal_id': self.miura_salaries_journal.id,
}).generate_payment_report()
file = self.hr_payslip_miura.payment_report
self.assertTrue(file)
xml_content = base64.b64decode(file).decode('utf-8')
expected_id_1 = f'<InstrId>{self.hr_payslip_miura.id}-1</InstrId>'
expected_id_2 = f'<InstrId>{self.hr_payslip_miura.id}-2</InstrId>'
self.assertIn(expected_id_1, xml_content, f'Missing unique InstrId: {expected_id_1}')
self.assertIn(expected_id_2, xml_content, f'Missing unique InstrId: {expected_id_2}')
@tagged('external_l10n', 'post_install', '-at_install', '-standard')
class TestPayrollSEPACreditTransferXmlValidity(TestPayrollSEPACreditTransferCommon):
@test_xsd(path='account_sepa/schemas/pain.001.001.03.xsd')
def test_00_hr_payroll_account_iso20022(self):
""" Checking the process of payslip when you create a SEPA payment. """
self.hr_payslip_john.action_payslip_done()
file = self.env['hr.payroll.payment.report.wizard'].create({
'payslip_ids': self.hr_payslip_john.ids,
'payslip_run_id': self.hr_payslip_john.payslip_run_id.id,
'export_format': 'sepa',
'journal_id': self.bank_journal.id,
})._create_sepa_binary()
return etree.fromstring(base64.b64decode(file))
@test_xsd(path='account_sepa/schemas/pain.001.001.03.xsd')
def test_01_hr_payroll_account_iso20022(self):
""" Checking the process of payslip run when you create a SEPA payment. """
self.payslip_run.generate_payslips(employee_ids=[self.hr_employee_john.id])
self.payslip_run.action_validate()
file = self.env['hr.payroll.payment.report.wizard'].create({
'payslip_ids': self.payslip_run.slip_ids.ids,
'payslip_run_id': self.payslip_run.id,
'export_format': 'sepa',
'journal_id': self.bank_journal.id,
})._create_sepa_binary()
return etree.fromstring(base64.b64decode(file))