283 lines
11 KiB
Python
283 lines
11 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
import base64
|
|
|
|
from datetime import date
|
|
|
|
from odoo.exceptions import UserError
|
|
from odoo.tests import tagged, freeze_time
|
|
|
|
from .common import TestLuPayrollCommon
|
|
|
|
|
|
@freeze_time('2022-12-31')
|
|
@tagged('post_install_l10n', 'post_install', '-at_install')
|
|
class TestLuMonthlyDeclaration(TestLuPayrollCommon):
|
|
def setUp(self):
|
|
super().setUp()
|
|
|
|
self.contract_david.generate_work_entries(date(2022, 3, 1), date(2020, 3, 31))
|
|
self.payslip_run = self.env['hr.payslip.run'].create({
|
|
'name': 'March 2022',
|
|
'date_start': '2022-03-01',
|
|
'date_end': '2022-03-31',
|
|
'company_id': self.lux_company.id,
|
|
'state': '01_ready',
|
|
})
|
|
self.payslip_run.generate_payslips(employee_ids=[self.employee_david.id])
|
|
self.payslip_run.action_validate()
|
|
|
|
def test_01_generate_missing_identification(self):
|
|
wizard = self.env['l10n.lu.monthly.declaration.wizard'].create({
|
|
'month': '3',
|
|
'year': '2022',
|
|
})
|
|
|
|
self.employee_david.identification_id = False
|
|
with self.assertRaisesRegex(UserError, r'missing an identification number'):
|
|
wizard.action_generate_declaration()
|
|
|
|
self.employee_david.identification_id = 111111111
|
|
wizard.action_generate_declaration()
|
|
|
|
self.lux_company.l10n_lu_seculine = False
|
|
with self.assertRaisesRegex(UserError, r'Missing (.+) SECUline numbers'):
|
|
wizard.action_generate_declaration()
|
|
|
|
self.lux_company.l10n_lu_seculine = 999999999
|
|
|
|
self.lux_company.l10n_lu_official_social_security = False
|
|
with self.assertRaisesRegex(UserError, r'Missing (.+) social security'):
|
|
wizard.action_generate_declaration()
|
|
|
|
def test_02_company_identification(self):
|
|
wizard = self.env['l10n.lu.monthly.declaration.wizard'].create({
|
|
'month': '3',
|
|
'year': '2022',
|
|
})
|
|
wizard.action_generate_declaration()
|
|
|
|
declaration = base64.decodebytes(wizard.decsal_file).decode('utf8')
|
|
declaration_lines = declaration.split('\r\n')
|
|
|
|
company_ssn = self.lux_company.l10n_lu_official_social_security
|
|
company_seculine = self.lux_company.l10n_lu_seculine
|
|
self.assertEqual(declaration_lines[0], f"0;{company_ssn};{company_seculine}")
|
|
|
|
def test_03_multiple_contracts(self):
|
|
self.payslip_run.action_draft()
|
|
|
|
madison_employee = self.env['hr.employee'].create({
|
|
'name': 'Madison',
|
|
'company_id': self.lux_company.id,
|
|
'identification_id': 987654321,
|
|
'structure_type_id': self.env.ref('l10n_lu_hr_payroll.structure_type_employee_lux').id,
|
|
'date_version': '2022-01-01',
|
|
'contract_date_start': '2022-01-01',
|
|
'contract_date_end': '2022-03-11',
|
|
'wage': 4000.0,
|
|
})
|
|
madison_contract1 = madison_employee.version_id
|
|
|
|
madison_contract2 = madison_contract1.copy({
|
|
'date_version': '2022-03-21',
|
|
'contract_date_start': '2022-03-21',
|
|
'contract_date_end': False,
|
|
'wage': 4400.0,
|
|
})
|
|
laura_employee = self.env['hr.employee'].create({
|
|
'name': 'laura',
|
|
'company_id': self.lux_company.id,
|
|
'identification_id': 143111140,
|
|
'structure_type_id': self.env.ref('l10n_lu_hr_payroll.structure_type_employee_lux').id,
|
|
'date_version': '2022-03-04',
|
|
'contract_date_start': '2022-03-04',
|
|
'contract_date_end': '2022-03-15',
|
|
'wage': 4000.0,
|
|
})
|
|
laura_contract1 = laura_employee.version_id
|
|
contracts = madison_contract1 | madison_contract2 | laura_contract1 | self.contract_david
|
|
|
|
self.env['hr.leave'].create({
|
|
'name': 'such bad weather no work',
|
|
'employee_id': madison_employee.id,
|
|
'holiday_status_id': self.env.ref('hr_holidays.l10n_lu_leave_type_situational_unemployment').id,
|
|
'request_date_from': '2022-03-09',
|
|
'request_date_to': '2022-03-09',
|
|
})
|
|
contracts.generate_work_entries(date(2022, 3, 1), date(2022, 3, 31))
|
|
|
|
batch = self.env['hr.payslip.run'].create({
|
|
'name': 'March 2022',
|
|
'date_start': '2022-03-01',
|
|
'date_end': '2022-03-31',
|
|
'company_id': self.lux_company.id,
|
|
'state': '01_ready',
|
|
})
|
|
|
|
batch.generate_payslips(employee_ids=[self.employee_david.id, madison_employee.id, laura_employee.id])
|
|
batch.action_validate()
|
|
|
|
wizard = self.env['l10n.lu.monthly.declaration.wizard'].create({
|
|
'month': '3',
|
|
'year': '2022',
|
|
})
|
|
|
|
with self.assertRaisesRegex(UserError, r'^Missing amounts'):
|
|
wizard.action_generate_declaration()
|
|
|
|
self.assertEqual(len(wizard.situational_unemployment_ids), 1)
|
|
self.assertEqual(wizard.situational_unemployment_ids.employee_id, madison_employee)
|
|
self.assertEqual(wizard.situational_unemployment_ids.hours, 8)
|
|
|
|
wizard.situational_unemployment_ids.amount = 120
|
|
wizard.action_generate_declaration()
|
|
|
|
declaration = base64.decodebytes(wizard.decsal_file).decode('utf8')
|
|
declaration_lines = declaration.split('\r\n')
|
|
|
|
self.assertEqual(len(declaration_lines), 4, "Should have 4 lines, 1 for company identification + 1 for each employee")
|
|
|
|
employee_values = {
|
|
laura_employee.identification_id: {
|
|
1: self.lux_company.l10n_lu_official_social_security,
|
|
3: 202203, # period reference YYYYMM
|
|
4: 410000,
|
|
5: 64, # 8 days * 8h
|
|
13: '04', # period start date - start of contract
|
|
14: 15, # period end date - end of contract
|
|
},
|
|
madison_employee.identification_id: {
|
|
0: 1,
|
|
1: self.lux_company.l10n_lu_official_social_security,
|
|
4: 843174,
|
|
5: 136, # 17 days (9 days 1st contract + 9 days 2nd contract - 1 day unemployment) * 8h
|
|
10: 12000, # 120.00 encoded in the wizard
|
|
11: 8, # 1 day of situational unemployment
|
|
13: '01', # period start date - start of month
|
|
14: 31, # period end date - end of month
|
|
},
|
|
self.employee_david.identification_id: {
|
|
1: self.lux_company.l10n_lu_official_social_security,
|
|
2: self.employee_david.identification_id,
|
|
4: 630368,
|
|
5: 184, # 23 days * 8h
|
|
10: 0,
|
|
11: 0,
|
|
13: '01', # period start date - start of month
|
|
14: 31, # period end date - end of month
|
|
},
|
|
}
|
|
|
|
employees_done = []
|
|
for line in declaration_lines[1:]:
|
|
fields = line.split(';')
|
|
self.assertEqual(len(fields), 20)
|
|
|
|
employee_identification_id = fields[2]
|
|
if employee_identification_id in employees_done:
|
|
raise Exception('There should be only one line per employee')
|
|
employees_done.append(employee_identification_id)
|
|
|
|
for idx, val in employee_values[employee_identification_id].items():
|
|
self.assertEqual(str(val), fields[idx], f"Error: expected {val} on field #{idx} found {fields[idx]} instead: \n{line}")
|
|
|
|
def test_04_multiple_contracts_different_structures(self):
|
|
structure_type = self.env['hr.payroll.structure.type'].create({'name': 'Lux: Test'})
|
|
pay_structure = self.env['hr.payroll.structure'].create({
|
|
'name': 'Lux Structure Test',
|
|
'type_id': structure_type.id,
|
|
})
|
|
structure_type.default_struct_id = pay_structure
|
|
|
|
jade_employee = self.env['hr.employee'].create({
|
|
'name': 'Jade',
|
|
'company_id': self.lux_company.id,
|
|
'identification_id': 987654321,
|
|
'structure_type_id': structure_type.id,
|
|
'date_version': '2022-01-01',
|
|
'contract_date_start': '2022-01-01',
|
|
'contract_date_end': '2022-03-11',
|
|
'wage': 3000.0,
|
|
})
|
|
jade_contract1 = jade_employee.version_id
|
|
jade_contract2 = jade_contract1.copy({
|
|
'name': 'Jade Contract 2',
|
|
'date_version': '2022-03-21',
|
|
'contract_date_start': '2022-03-21',
|
|
'contract_date_end': False,
|
|
'wage': 4400.0,
|
|
'structure_type_id': self.env.ref('l10n_lu_hr_payroll.structure_type_employee_lux').id,
|
|
})
|
|
contracts = jade_contract1 | jade_contract2
|
|
contracts.generate_work_entries(date(2022, 3, 1), date(2022, 3, 31))
|
|
|
|
batch = self.env['hr.payslip.run'].create({
|
|
'name': 'March 2022',
|
|
'date_start': '2022-03-01',
|
|
'date_end': '2022-03-31',
|
|
'company_id': self.lux_company.id,
|
|
'state': '01_ready',
|
|
})
|
|
batch.generate_payslips(employee_ids=[self.employee_david.id, jade_employee.id])
|
|
batch.action_validate()
|
|
|
|
wizard = self.env['l10n.lu.monthly.declaration.wizard'].create({
|
|
'month': '3',
|
|
'year': '2022',
|
|
})
|
|
wizard.action_generate_declaration()
|
|
|
|
declaration = base64.decodebytes(wizard.decsal_file).decode('utf8')
|
|
declaration_lines = declaration.split('\r\n')
|
|
|
|
self.assertNotEqual(jade_contract1.structure_type_id, jade_contract2.structure_type_id)
|
|
self.assertEqual(len(declaration_lines), 4) # 1 for company identification + 1 for David + 2 for Jade
|
|
|
|
jade_entries = [
|
|
line.split(';')
|
|
for line in declaration_lines
|
|
if line.split(';')[2] == jade_employee.identification_id
|
|
]
|
|
self.assertEqual(len(jade_entries), 2)
|
|
|
|
self.assertEqual(jade_entries[0][13], "01")
|
|
self.assertEqual(jade_entries[0][14], "11")
|
|
|
|
self.assertEqual(jade_entries[1][13], "21")
|
|
self.assertEqual(jade_entries[1][14], "31")
|
|
|
|
def test_05_hourly_worker(self):
|
|
structure_type = self.env.ref('l10n_lu_hr_payroll.structure_type_employee_lux')
|
|
structure_type.wage_type = 'hourly'
|
|
hourly_employee = self.env['hr.employee'].create({
|
|
'name': 'Alice Smith',
|
|
'company_id': self.lux_company.id,
|
|
'identification_id': '1234567890123',
|
|
'structure_type_id': self.env.ref('l10n_lu_hr_payroll.structure_type_employee_lux').id,
|
|
'hourly_wage': 25.0,
|
|
'wage_type': 'hourly',
|
|
'wage': 4000.0,
|
|
'date_version': '2022-01-01',
|
|
'contract_date_start': '2022-01-01',
|
|
})
|
|
|
|
hourly_contract = hourly_employee.version_id
|
|
|
|
hourly_contract.generate_work_entries(date(2022, 1, 1), date(2022, 1, 31))
|
|
|
|
payslip = self.env['hr.payslip'].create({
|
|
'name': 'Test Hourly Payslip',
|
|
'employee_id': hourly_employee.id,
|
|
'version_id': hourly_contract.id,
|
|
'date_from': '2022-01-01',
|
|
'date_to': '2022-01-31',
|
|
'struct_id': self.env.ref('l10n_lu_hr_payroll.hr_payroll_structure_lux_employee_salary').id,
|
|
})
|
|
payslip.compute_sheet()
|
|
|
|
index_ratio = hourly_contract.l10n_lu_current_index / hourly_contract.l10n_lu_index_on_contract_signature
|
|
indexed_hourly = round(25 * index_ratio, 2)
|
|
expected_salary = indexed_hourly * 168
|
|
self.assertAlmostEqual(payslip.line_ids.filtered(lambda l: l.code == 'BASIC').total, expected_salary, 2)
|