457 lines
20 KiB
Python
457 lines
20 KiB
Python
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
|
|
|
from datetime import date
|
|
|
|
from odoo.fields import Command
|
|
from odoo.tests import tagged
|
|
from odoo.addons.hr_payroll_account.tests.common import TestPayslipValidationCommon
|
|
|
|
|
|
@tagged('post_install', 'post_install_l10n', '-at_install', 'payslips_validation')
|
|
class TestPayslipValidation(TestPayslipValidationCommon):
|
|
|
|
@classmethod
|
|
@TestPayslipValidationCommon.setup_country('ae')
|
|
def setUpClass(cls):
|
|
super().setUpClass()
|
|
cls.env.user.group_ids += cls.env.ref('hr_payroll.group_hr_payroll_user')
|
|
cls._setup_common(
|
|
country=cls.env.ref('base.ae'),
|
|
structure=cls.env.ref('l10n_ae_hr_payroll.uae_employee_payroll_structure'),
|
|
structure_type=cls.env.ref('l10n_ae_hr_payroll.uae_employee_payroll_structure_type'),
|
|
contract_fields={
|
|
'wage': 40000.0,
|
|
'l10n_ae_housing_allowance': 400.0,
|
|
'l10n_ae_transportation_allowance': 220.0,
|
|
'l10n_ae_other_allowances': 100.0,
|
|
'l10n_ae_is_dews_applied': True,
|
|
}
|
|
)
|
|
|
|
cls.work_entry_types = {
|
|
entry_type.code: entry_type
|
|
for entry_type in cls.env['hr.work.entry.type'].search([])
|
|
}
|
|
|
|
def _get_input_line_amount(self, payslip, code):
|
|
input_lines = payslip.input_line_ids.filtered(lambda line: line.code == code)
|
|
amounts = input_lines.mapped('amount')
|
|
return len(amounts), sum(amounts)
|
|
|
|
@classmethod
|
|
def _create_worked_days(cls, name=False, code=False, number_of_days=0, number_of_hours=0):
|
|
return Command.create({
|
|
'name': name,
|
|
'work_entry_type_id': cls.work_entry_types[code].id,
|
|
'code': code,
|
|
'number_of_days': number_of_days,
|
|
'number_of_hours': number_of_hours,
|
|
})
|
|
|
|
def _test_eos_calculation(self, start_date, departure_date, payslip_start, payslip_end,
|
|
expected_eos, has_unpaid_leave=False, wage=15000.0):
|
|
"""Helper method to test end of service salary rule calculations."""
|
|
employee = self.env['hr.employee'].create({
|
|
'name': 'Test Employee',
|
|
'structure_type_id': self.env.ref('l10n_ae_hr_payroll.uae_employee_payroll_structure_type').id,
|
|
'country_id': self.env.ref('base.ae').id,
|
|
'wage': wage,
|
|
'date_version': start_date,
|
|
'contract_date_start': start_date,
|
|
})
|
|
|
|
if has_unpaid_leave:
|
|
unpaid_leave = self.env['hr.leave'].create({
|
|
'name': 'Unpaid Leave',
|
|
'employee_id': employee.id,
|
|
'holiday_status_id': self.env.ref('hr_holidays.leave_type_unpaid').id,
|
|
'request_date_from': date(2025, 8, 4),
|
|
'request_date_to': date(2025, 8, 8),
|
|
})
|
|
unpaid_leave.action_approve()
|
|
employee.version_id.generate_work_entries(payslip_start, payslip_end)
|
|
|
|
departure_notice = self.env['hr.departure.wizard'].create({
|
|
'employee_ids': [Command.link(employee.id)],
|
|
'departure_date': departure_date,
|
|
'departure_description': 'foo',
|
|
})
|
|
departure_notice.with_context(employee_termination=True).action_register_departure()
|
|
|
|
payslip = self._generate_payslip(payslip_start, payslip_end, employee_id=employee.id, version_id=employee.version_id.id)
|
|
payslip.compute_sheet()
|
|
|
|
self.assertEqual(
|
|
payslip._get_line_values(['EOS'])['EOS'][payslip.id]['total'],
|
|
expected_eos,
|
|
"End of Service calculation is incorrect"
|
|
)
|
|
|
|
def test_payslip_1(self):
|
|
payslip = self._generate_payslip(date(2024, 1, 1), date(2024, 1, 31))
|
|
payslip_results = {'BASIC': 40000.0, 'HOUALLOW': 400.0, 'TRAALLOW': 220.0, 'OTALLOW': 100.0, 'EOSP': 3333.33, 'ALP': 3393.33, 'GROSS': 40720.0, 'SICC': 5090.0, 'SIEC': -2036.0, 'DEWS': -3332.0, 'NET': 35352.0, 'NETCOST': 45810.0}
|
|
self._validate_payslip(payslip, payslip_results)
|
|
|
|
def test_payslip_2(self):
|
|
payslip = self._generate_payslip(date(2024, 1, 1), date(2024, 1, 31))
|
|
other_inputs_to_add = [
|
|
(self.env.ref('l10n_ae_hr_payroll.input_salary_arrears'), 1000),
|
|
(self.env.ref('l10n_ae_hr_payroll.input_other_earnings'), 2000),
|
|
(self.env.ref('l10n_ae_hr_payroll.input_salary_deduction'), 500),
|
|
(self.env.ref('l10n_ae_hr_payroll.input_other_deduction'), 200),
|
|
(self.env.ref('l10n_ae_hr_payroll.l10n_ae_input_overtime_allowance'), 300),
|
|
(self.env.ref('l10n_ae_hr_payroll.input_bonus_earnings'), 400),
|
|
(self.env.ref('l10n_ae_hr_payroll.l10n_ae_input_other_allowance'), 600),
|
|
(self.env.ref('l10n_ae_hr_payroll.input_airfare_allowance_earnings'), 700),
|
|
]
|
|
for other_input, amount in other_inputs_to_add:
|
|
self._add_other_input(payslip, other_input, amount)
|
|
payslip.compute_sheet()
|
|
|
|
payslip_results = {'BASIC': 40000.0, 'HOUALLOW': 400.0, 'TRAALLOW': 220.0, 'OTALLOW': 100.0, 'SALARY_ARREARS': 1000.0, 'OTHER_EARNINGS': 2000.0, 'SALARY_DEDUCTIONS': -500.0, 'OTHER_DEDUCTIONS': -200.0, 'OVERTIMEALLOWINP': 300.0, 'BONUS': 400.0, 'OTALLOWINP': 600.0, 'AIRFARE_ALLOWANCE': 700.0, 'EOSP': 3333.33, 'ALP': 3393.33, 'GROSS': 45720.0, 'SICC': 5090.0, 'SIEC': -2036.0, 'DEWS': -3332.0, 'NET': 39652.0, 'NETCOST': 50110.0}
|
|
self._validate_payslip(payslip, payslip_results)
|
|
|
|
def test_instant_pay_payslip_generation(self):
|
|
instant_pay_structure = self.env.ref('l10n_ae_hr_payroll.l10n_ae_uae_instant_pay')
|
|
payslip = self._generate_payslip(date(2023, 3, 1), date(2023, 3, 31), struct_id=instant_pay_structure.id)
|
|
other_inputs_to_add = [
|
|
(self.env.ref('l10n_ae_hr_payroll.l10n_ae_input_allowance'), 1000),
|
|
(self.env.ref('l10n_ae_hr_payroll.l10n_ae_input_commission'), 800),
|
|
(self.env.ref('l10n_ae_hr_payroll.l10n_ae_input_salary_advance'), 1500),
|
|
(self.env.ref('l10n_ae_hr_payroll.l10n_ae_input_loan_advance'), 1200),
|
|
(self.env.ref('l10n_ae_hr_payroll.l10n_ae_input_deduction'), 700),
|
|
]
|
|
for other_input, amount in other_inputs_to_add:
|
|
self._add_other_input(payslip, other_input, amount)
|
|
payslip.compute_sheet()
|
|
|
|
payslip_results = {'ALLOW': 1000.00, 'COMM': 800.00, 'ADV': 1500.00, 'LOAN': 1200.00, 'DED': -700.00, 'NET': 3800.00}
|
|
self._validate_payslip(payslip, payslip_results)
|
|
|
|
def test_salary_advance(self):
|
|
instant_pay_structure = self.env.ref('l10n_ae_hr_payroll.l10n_ae_uae_instant_pay')
|
|
uae_employee_structure = self.env.ref('l10n_ae_hr_payroll.uae_employee_payroll_structure')
|
|
salary_advance_other_input = self.env.ref('l10n_ae_hr_payroll.l10n_ae_input_salary_advance')
|
|
|
|
# First salary advance payslip of 500 on 01/09/2024 and setting the advance amount to 500 and validate the payslip
|
|
test_saladv_payslip1 = self._generate_payslip(
|
|
date(2024, 9, 1), date(2024, 9, 30), struct_id=instant_pay_structure.id
|
|
)
|
|
self._add_other_input(test_saladv_payslip1, salary_advance_other_input, 500)
|
|
|
|
test_saladv_payslip1.compute_sheet()
|
|
test_saladv_payslip1.action_payslip_done()
|
|
|
|
# Second salary advance payslip of 200 on 15/09/2024
|
|
test_saladv_payslip2 = self._generate_payslip(
|
|
date(2024, 9, 15), date(2024, 9, 30), struct_id=instant_pay_structure.id
|
|
)
|
|
self._add_other_input(test_saladv_payslip2, salary_advance_other_input, 200)
|
|
|
|
test_saladv_payslip2.compute_sheet()
|
|
test_saladv_payslip2.action_payslip_done()
|
|
|
|
# September monthly payslip
|
|
test_payslip_sept = self._generate_payslip(
|
|
date(2024, 9, 1), date(2024, 9, 30), struct_id=uae_employee_structure.id
|
|
)
|
|
test_payslip_sept._compute_input_line_ids()
|
|
# September monthly pay should have salary advance recovery = 700 by default
|
|
nbr_rec, amount_rec = self._get_input_line_amount(test_payslip_sept, "ADVREC")
|
|
self.assertEqual(nbr_rec, 1)
|
|
self.assertEqual(amount_rec, 700)
|
|
# Changing the recovery amount to 500 and validate the payslip
|
|
test_payslip_sept.input_line_ids.filtered(lambda line: line.code == "ADVREC").write({
|
|
"amount": 500
|
|
})
|
|
test_payslip_sept.compute_sheet()
|
|
test_payslip_sept.action_payslip_done()
|
|
|
|
nbr_rec, amount_rec = self._get_input_line_amount(test_payslip_sept, "ADVREC")
|
|
self.assertEqual(nbr_rec, 1)
|
|
self.assertEqual(amount_rec, 500)
|
|
|
|
# Third salary advance payslip of 300 on 1/10/2024
|
|
test_saladv_payslip3 = self._generate_payslip(
|
|
date(2024, 10, 1), date(2024, 10, 31), struct_id=instant_pay_structure.id
|
|
)
|
|
self._add_other_input(test_saladv_payslip3, salary_advance_other_input, 300)
|
|
test_saladv_payslip3.compute_sheet()
|
|
test_saladv_payslip3.action_payslip_done()
|
|
|
|
# October monthly pay should have salary advance recovery = 500 (200+300) by default
|
|
test_payslip_oct = self._generate_payslip(
|
|
date(2024, 10, 1), date(2024, 10, 31), struct_id=uae_employee_structure.id
|
|
)
|
|
test_payslip_oct._compute_input_line_ids()
|
|
test_payslip_oct.compute_sheet()
|
|
test_payslip_oct.action_payslip_done()
|
|
|
|
nbr_rec, amount_rec = self._get_input_line_amount(test_payslip_oct, "ADVREC")
|
|
self.assertEqual(nbr_rec, 1)
|
|
self.assertEqual(amount_rec, 500)
|
|
|
|
# November monthly pay should have salary advance recovery = 0
|
|
test_payslip_nov = self._generate_payslip(
|
|
date(2024, 11, 1), date(2024, 11, 30), struct_id=uae_employee_structure.id
|
|
)
|
|
test_payslip_nov._compute_input_line_ids()
|
|
test_payslip_nov.compute_sheet()
|
|
test_payslip_nov.action_payslip_done()
|
|
nbr_rec, amount_rec = self._get_input_line_amount(test_payslip_nov, "ADVREC")
|
|
self.assertEqual(nbr_rec, 0)
|
|
self.assertEqual(amount_rec, 0)
|
|
|
|
def test_end_of_service_salary_rule_1(self):
|
|
"""Case: Employee worked 2 years, 8 months, and 15 days
|
|
Expected: Calculate EOS for 2 years, 8 months and 15 days
|
|
(2 years * 12 month / year) + 8 months = 32 months
|
|
because the total is less than 6 years, ratio = 21 / 30
|
|
(15_000 $/year) * ratio * (1/12 year/month) = 875 $/month
|
|
(15_000 $/year) * ratio * (1/365 year/day) ~ 28.77 $/day
|
|
|
|
total = 32 months * 875 $/month = 28_000 $
|
|
15 days * 28.77 $/day ~ 432 $ +
|
|
= 28_432 $
|
|
"""
|
|
self._test_eos_calculation(
|
|
start_date=date(2014, 6, 4),
|
|
departure_date=date(2017, 2, 19),
|
|
payslip_start=date(2017, 2, 1),
|
|
payslip_end=date(2017, 2, 28),
|
|
expected_eos=28_432.0
|
|
)
|
|
|
|
def test_end_of_service_salary_rule_2(self):
|
|
"""Case: Employee worked 5 years, 5 months, and 17 days
|
|
Expected: Calculate EOS for 5 years, 5 months and 17 days
|
|
(5 years * 12 month / year) + 5 months = 65 months
|
|
because the total is less than 6 years, ratio = 21 / 30
|
|
(15_000 $/year) * ratio * (1/12 year/month) = 875 $/month
|
|
(15_000 $/year) * ratio * (1/365 year/day) ~ 28.77 $/day
|
|
|
|
total = 65 months * 875 $/month = 56_875 $
|
|
17 days * 28.77 $/day ~ 490 $ +
|
|
= 57_365 $
|
|
"""
|
|
self._test_eos_calculation(
|
|
start_date=date(2019, 7, 22),
|
|
departure_date=date(2025, 1, 8),
|
|
payslip_start=date(2025, 1, 1),
|
|
payslip_end=date(2025, 1, 31),
|
|
expected_eos=57_365.0
|
|
)
|
|
|
|
def test_end_of_service_salary_rule_3(self):
|
|
"""Case: Employee worked 6 years, 5 months, and 17 days
|
|
Expected: Calculate EOS for 6 years, 5 months and 17 days
|
|
(6 years * 12 month / year) + 5 months = 77 months
|
|
because the total is greater than 6 years, ratio = 1
|
|
(15_000 $/year) * ratio * (1/12 year/month) = 1250 $/month
|
|
(15_000 $/year) * ratio * (1/365 year/day) ~ 41.1 $/day
|
|
|
|
total = 60 months * 1250 * (21/30) $/month = 52_500 $
|
|
17 months * 1250 $/month = 21_250 $
|
|
17 days * 41.1 $/day ~ 699 $ +
|
|
= 74_449 $
|
|
"""
|
|
self._test_eos_calculation(
|
|
start_date=date(2018, 7, 22),
|
|
departure_date=date(2025, 1, 8),
|
|
payslip_start=date(2025, 1, 1),
|
|
payslip_end=date(2025, 1, 31),
|
|
expected_eos=74_449.0
|
|
)
|
|
|
|
def test_end_of_service_salary_rule_4(self):
|
|
"""Case: Employee worked 2 years, 8 months, and 15 days with 5 unpaid days
|
|
Expected: Calculate EOS for 2 years, 8 months and 10 days
|
|
(2 years * 12 month / year) + 8 months = 32 months
|
|
because the total is less than 6 years, ratio = 21 / 30
|
|
(15_000 $/year) * ratio * (1/12 year/month) = 875 $/month
|
|
(15_000 $/year) * ratio * (1/365 year/day) ~ 28.77 $/day
|
|
|
|
total = 32 months * 875 $/month = 28000 $
|
|
10 days * 28.77 $/day ~ 288 $ +
|
|
= 28_288 $
|
|
"""
|
|
self._test_eos_calculation(
|
|
start_date=date(2022, 12, 16),
|
|
departure_date=date(2025, 8, 31),
|
|
payslip_start=date(2025, 8, 1),
|
|
payslip_end=date(2025, 8, 31),
|
|
expected_eos=28_288.0,
|
|
has_unpaid_leave=True
|
|
)
|
|
|
|
def test_end_of_service_salary_rule_5(self):
|
|
"""Case: Employee worked 6 years and 3 days with 5 unpaid days
|
|
Expected: Calculate EOS for 5 years, 11 months and 29 days
|
|
(5 years * 12 month / year) + 11 months = 71 months
|
|
because the total is less than 6 years, ratio = 21 / 30
|
|
(15_000 $/year) * ratio * (1/12 year/month) = 875 $/month
|
|
(15_000 $/year) * ratio * (1/365 year/day) ~ 28.77 $/day
|
|
|
|
total = 71 months * 875 $/month = 62_125 $
|
|
29 days * 28.77 $/day ~ 835 $ +
|
|
= 62_960 $
|
|
"""
|
|
self._test_eos_calculation(
|
|
start_date=date(2019, 8, 28),
|
|
departure_date=date(2025, 8, 31),
|
|
payslip_start=date(2025, 8, 1),
|
|
payslip_end=date(2025, 8, 31),
|
|
expected_eos=62_960.0,
|
|
has_unpaid_leave=True
|
|
)
|
|
|
|
def test_end_of_service_salary_rule_6(self):
|
|
"""Case: Employee worked 7 years and 3 days with 5 unpaid days
|
|
Expected: Calculate EOS for 6 years, 11 months and 29 days
|
|
(6 years * 12 month / year) + 11 months = 83 months
|
|
because the total is greater than 6 years, ratio = 1
|
|
(15_000 $/year) * ratio * (1/12 year/month) = 1250 $/month
|
|
(15_000 $/year) * ratio * (1/365 year/day) ~ 41.1 $/day
|
|
|
|
total = 60 months * 1250 * (21/30) $/month = 52_500 $
|
|
23 months * 1250 $/month = 28_750 $
|
|
29 days * 41.1 $/day ~ 1192 $ +
|
|
= 82_442 $
|
|
"""
|
|
self._test_eos_calculation(
|
|
start_date=date(2018, 8, 28),
|
|
departure_date=date(2025, 8, 31),
|
|
payslip_start=date(2025, 8, 1),
|
|
payslip_end=date(2025, 8, 31),
|
|
expected_eos=82_442.0,
|
|
has_unpaid_leave=True
|
|
)
|
|
|
|
def test_payslip_attendance_1(self):
|
|
if self.env['ir.module.module']._get('hr_payroll_attendance').state != 'installed':
|
|
self.skipTest("Skipping test because hr_payroll_attendance is not installed.")
|
|
|
|
self.employee.country_id = False
|
|
self.contract.write({
|
|
'contract_date_start': '2025-01-01',
|
|
'work_entry_source': 'attendance',
|
|
'wage': 5000,
|
|
'wage_type': 'monthly',
|
|
'l10n_ae_housing_allowance': 2000,
|
|
'l10n_ae_transportation_allowance': 1000,
|
|
'l10n_ae_other_allowances': 100,
|
|
'l10n_ae_is_dews_applied': False,
|
|
})
|
|
|
|
worked_days_vals = [
|
|
{'name': 'Unpaid', 'code': 'LEAVE90', 'number_of_hours': 16, 'number_of_days': 2},
|
|
{'name': 'Paid Time Off', 'code': 'LEAVE120', 'number_of_hours': 24, 'number_of_days': 3},
|
|
{'name': 'Sick Leave 50', 'code': 'AESICKLEAVE50', 'number_of_hours': 24, 'number_of_days': 3},
|
|
{'name': 'Out of Contract', 'code': 'OUT', 'number_of_hours': 32, 'number_of_days': 4},
|
|
{'name': 'Attendance', 'code': 'WORK100', 'number_of_hours': 88, 'number_of_days': 11},
|
|
]
|
|
|
|
payslip = self._generate_payslip('2025-07-01', '2025-07-31')
|
|
payslip.write({
|
|
"worked_days_line_ids": [self._create_worked_days(**vals) for vals in worked_days_vals],
|
|
})
|
|
payslip.compute_sheet()
|
|
payslip_results = {
|
|
'BASIC': 2391.30,
|
|
'HOUALLOW': 956.52,
|
|
'TRAALLOW': 478.26,
|
|
'OTALLOW': 47.83,
|
|
'EOSP': 188.73,
|
|
'ALP': 436.76,
|
|
'AEPAID': 1056.48,
|
|
'AESPAID50': 528.24,
|
|
'GROSS': 5458.63,
|
|
'NET': 5458.63,
|
|
'NETCOST': 5458.63,
|
|
}
|
|
self._validate_payslip(payslip, payslip_results)
|
|
|
|
def test_payslip_attendance_2(self):
|
|
if self.env['ir.module.module']._get('hr_payroll_attendance').state != 'installed':
|
|
self.skipTest("Skipping test because hr_payroll_attendance is not installed.")
|
|
|
|
self.employee.country_id = False
|
|
self.contract.write({
|
|
'contract_date_start': '2025-01-01',
|
|
'work_entry_source': 'attendance',
|
|
'wage': 5000,
|
|
'hourly_wage': 44.02,
|
|
'wage_type': 'hourly',
|
|
'l10n_ae_housing_allowance': 2000,
|
|
'l10n_ae_transportation_allowance': 1000,
|
|
'l10n_ae_other_allowances': 100,
|
|
'l10n_ae_is_dews_applied': False,
|
|
})
|
|
|
|
worked_days_vals = [
|
|
{'name': 'Unpaid', 'code': 'LEAVE90', 'number_of_hours': 16, 'number_of_days': 2},
|
|
{'name': 'Paid Time Off', 'code': 'LEAVE120', 'number_of_hours': 24, 'number_of_days': 3},
|
|
{'name': 'Sick Leave 50', 'code': 'AESICKLEAVE50', 'number_of_hours': 24, 'number_of_days': 3},
|
|
{'name': 'Out of Contract', 'code': 'OUT', 'number_of_hours': 32, 'number_of_days': 4},
|
|
{'name': 'Attendance', 'code': 'WORK100', 'number_of_hours': 72, 'number_of_days': 9},
|
|
]
|
|
|
|
payslip = self._generate_payslip('2025-06-01', '2025-06-30')
|
|
payslip.write({
|
|
"worked_days_line_ids": [self._create_worked_days(**vals) for vals in worked_days_vals]
|
|
})
|
|
payslip.compute_sheet()
|
|
payslip_results = {
|
|
'BASIC': 2142.86,
|
|
'EOSP': 175.0,
|
|
'ALP': 405.0,
|
|
'AEPAID': 1056.48,
|
|
'AESPAID50': 528.24,
|
|
'GROSS': 3727.58,
|
|
'NET': 3727.58,
|
|
'NETCOST': 3727.58,
|
|
}
|
|
self._validate_payslip(payslip, payslip_results)
|
|
|
|
def test_payslip_attendance_3(self):
|
|
if self.env['ir.module.module']._get('hr_payroll_attendance').state != 'installed':
|
|
self.skipTest("Skipping test because hr_payroll_attendance is not installed.")
|
|
|
|
self.employee.country_id = False
|
|
self.contract.write({
|
|
'contract_date_start': '2025-01-01',
|
|
'work_entry_source': 'attendance',
|
|
'wage': 10000,
|
|
'wage_type': 'monthly',
|
|
'l10n_ae_housing_allowance': 2000,
|
|
'l10n_ae_transportation_allowance': 1000,
|
|
'l10n_ae_other_allowances': 100,
|
|
'l10n_ae_is_dews_applied': False,
|
|
})
|
|
|
|
worked_days_vals = [
|
|
{'name': 'Attendance', 'code': 'WORK100', 'number_of_hours': 80, 'number_of_days': 20},
|
|
]
|
|
|
|
payslip = self._generate_payslip('2025-11-01', '2025-11-30')
|
|
payslip.write({
|
|
"worked_days_line_ids": [self._create_worked_days(**vals) for vals in worked_days_vals],
|
|
})
|
|
payslip.compute_sheet()
|
|
|
|
payslip_results = {
|
|
'BASIC': 5000.0,
|
|
'HOUALLOW': 1000.0,
|
|
'TRAALLOW': 500.0,
|
|
'OTALLOW': 50.0,
|
|
'EOSP': 583.33,
|
|
'ALP': 1091.67,
|
|
'AEPAID': 0.0,
|
|
'AESPAID50': 0.0,
|
|
'GROSS': 6550.0,
|
|
'NET': 6550.0,
|
|
'NETCOST': 6550.0,
|
|
}
|
|
|
|
self._validate_payslip(payslip, payslip_results)
|