1
0
forked from Mapan/odoo17e
odoo17e-kedaikipas58/addons/l10n_au_hr_payroll/tests/test_payroll.py
2024-12-10 09:04:09 +07:00

509 lines
24 KiB
Python

# -*- coding:utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from datetime import date
from dateutil.relativedelta import relativedelta
from odoo import Command
from odoo.tests import tagged
from .common import TestPayrollCommon
@tagged("post_install_l10n", "post_install", "-at_install", "l10n_au_hr_payroll")
class TestPayroll(TestPayrollCommon):
@classmethod
def setUpClass(cls):
super(TestPayroll, cls).setUpClass()
cls.struct_regular = cls.env.ref("l10n_au_hr_payroll.hr_payroll_structure_au_regular")
def test_withholding_amounts(self):
test_scales = ["1", "2", "3", "5", "6"]
today = date.today()
payslip = self.env["hr.payslip"].create({
"name": "Test Payslip AU",
"employee_id": self.employee_id.id,
"date_from": today,
"date_to": today,
"struct_id": self.struct_regular.id,
})
payslip._compute_date_to()
coefficients = payslip._rule_parameter("l10n_au_withholding_coefficients")["regular"]
for period, sample_data in self.schedule_1_withholding_sample_data.items():
for row in sample_data:
earnings = row[0]
self.contract_ids[0].wage = earnings
for idx, test_data in enumerate(row[1:]):
self.employee_id.l10n_au_scale = test_scales[idx]
amount = payslip._l10n_au_compute_withholding_amount(earnings, period, coefficients)
self.assertEqual(amount, test_data, f"weekly earnings of {earnings} scale {self.employee_id.l10n_au_scale}: expected {test_data} got {-amount}")
def test_medicare_adjustment(self):
nbr_children = [0, 1, 2, 3, 4, 5]
today = date.today()
self.employee_id.marital = "married"
payslip = self.env["hr.payslip"].create({
"name": "Test Payslip AU",
"employee_id": self.employee_id.id,
"date_from": today,
"date_to": today,
"struct_id": self.struct_regular.id,
})
payslip._compute_date_to()
params = payslip._rule_parameter("l10n_au_medicare_params")[self.employee_id.l10n_au_scale]
for children in nbr_children:
self.employee_id.children = children
for period, sample_data in self.medicare_adjustment_sample_data.items():
for row in sample_data:
earnings = row[0]
self.contract_ids[0].wage = earnings
amount = payslip._l10n_au_compute_medicare_adjustment(earnings, period, params)
self.assertEqual(amount, row[children + 1], f"weekly earnings of {earnings} children {self.employee_id.children}: expected {row[children + 1]} got {-amount}")
def test_compute_loan_withhold(self):
today = date.today()
payslip = self.env["hr.payslip"].create({
"name": "Test Payslip AU",
"employee_id": self.employee_id.id,
"date_from": today,
"date_to": today,
"struct_id": self.struct_regular.id,
})
payslip._compute_date_to()
loan_coefs = payslip._rule_parameter("l10n_au_stsl")
for period, sample_data in self.loan_withhold_sample_data.items():
self.employee_id.l10n_au_scale = "1"
coefs = loan_coefs["1"]
for row_claimed in sample_data:
earnings = row_claimed[0]
expected = row_claimed[2]
amount = payslip._l10n_au_compute_loan_withhold(earnings, period, coefs)
self.assertAlmostEqual(amount, expected, delta=1, msg=f"weekly earnings of {earnings} scale {self.employee_id.l10n_au_scale}: expected {expected} got {-amount}")
self.employee_id.l10n_au_scale = "2"
coefs = loan_coefs["2"]
for row_not_claimed in sample_data:
earnings = row_not_claimed[0]
expected = row_not_claimed[1]
amount = payslip._l10n_au_compute_loan_withhold(earnings, period, coefs)
self.assertAlmostEqual(amount, expected, delta=1, msg=f"weekly earnings of {earnings} scale {self.employee_id.l10n_au_scale}: expected {expected} got {-amount}")
def test_general_cases(self):
# https://www.ato.gov.au/rates/schedule-1---statement-of-formulas-for-calculating-amounts-to-be-withheld/?page=6#General_examples
# EXAMPLE 1
self.contract_ids[0].wage = 1103.45
self.contract_ids[0].schedule_pay = "weekly"
self.contract_ids[0]._compute_wages()
self.employee_id.marital = "married"
self.employee_id.children = 5
self.employee_id.l10n_au_scale = "2"
payslip_id = self.env["hr.payslip"].create({
"name": "payslip",
"employee_id": self.employee_id.id,
"contract_id": self.contract_ids[0].id,
"struct_id": self.struct_regular.id,
"date_from": date(2023, 9, 18),
"date_to": date(2023, 9, 23),
})
payslip_id.compute_sheet()
payslip_lines = payslip_id.line_ids
withholding_amount = payslip_lines.filtered(lambda l: l.code == "WITHHOLD").amount
adjustment_amount = payslip_lines.filtered(lambda l: l.code == "MEDICARE").amount
self.assertEqual(-withholding_amount, 198)
self.assertEqual(adjustment_amount, 20)
# EXAMPLE 2
self.contract_ids[0].wage = 1110.30
self.contract_ids[0].schedule_pay = "bi-weekly"
self.contract_ids[0]._compute_wages()
self.employee_id.marital = "single"
self.employee_id.children = 0
self.employee_id.l10n_au_scale = "5"
self.employee_id.l10n_au_nat_3093_amount = 1645
payslip_id = self.env["hr.payslip"].create({
"name": "payslip",
"employee_id": self.employee_id.id,
"contract_id": self.contract_ids[0].id,
"struct_id": self.struct_regular.id,
"date_from": date(2023, 9, 18),
"date_to": date(2023, 9, 30),
})
payslip_id.compute_sheet()
payslip_lines = payslip_id.line_ids
withholding_amount = payslip_lines.filtered(lambda l: l.code == "WITHHOLD").amount
tax_offset = payslip_lines.filtered(lambda l: l.code == "TAX.OFFSET").amount
self.assertEqual(-withholding_amount, 74)
self.assertEqual(tax_offset, 63)
# EXAMPLE 3
self.contract_ids[0].wage = 4500.33
self.contract_ids[0].schedule_pay = "monthly"
self.contract_ids[0]._compute_wages()
self.employee_id.marital = "married"
self.employee_id.children = 1
self.employee_id.l10n_au_scale = "2"
self.employee_id.l10n_au_nat_3093_amount = 1365
payslip_id = self.env["hr.payslip"].create({
"name": "payslip",
"employee_id": self.employee_id.id,
"contract_id": self.contract_ids[0].id,
"struct_id": self.struct_regular.id,
"date_from": date(2023, 9, 1),
"date_to": date(2023, 9, 30),
})
payslip_id.compute_sheet()
payslip_lines = payslip_id.line_ids
withholding_amount = payslip_lines.filtered(lambda l: l.code == "WITHHOLD").amount
tax_offset = payslip_lines.filtered(lambda l: l.code == "TAX.OFFSET").amount
self.assertEqual(-withholding_amount, 758)
self.assertEqual(tax_offset, 113)
def lines_by_code(self, lines):
lines_by_code = defaultdict(lambda: {"lines": [], "amount": 0, "total": 0})
for line in lines:
lines_by_code[line.code]["lines"].append(line)
lines_by_code[line.code]["amount"] += line.amount
lines_by_code[line.code]["total"] += line.total
return lines_by_code
def create_employee_and_contract(self, wage, period):
employee_id = self.env["hr.employee"].create({
"name": "Mel",
"resource_calendar_id": self.resource_calendar.id,
"company_id": self.australian_company.id,
"private_street": "1 Test Street",
"private_city": "Sydney",
"private_country_id": self.env.ref("base.au").id,
"work_phone": "123456789",
"birthday": date.today() - relativedelta(years=22),
# fields modified in the tests
"marital": "single",
"l10n_au_tfn_declaration": "provided",
"l10n_au_tfn": "12345678",
"l10n_au_tax_free_threshold": True,
"is_non_resident": False,
"l10n_au_training_loan": False,
"l10n_au_nat_3093_amount": 0,
"l10n_au_child_support_garnishee_amount": 0,
"l10n_au_medicare_exemption": "X",
"l10n_au_medicare_surcharge": "X",
"l10n_au_medicare_reduction": "X",
"l10n_au_child_support_deduction": 0,
"l10n_au_scale": "2",
})
contract_id = self.env["hr.contract"].create({
"name": "Mel's contract",
"employee_id": employee_id.id,
"resource_calendar_id": self.resource_calendar.id,
"company_id": self.australian_company.id,
"date_start": date(2023, 1, 1),
"date_end": False,
"wage_type": "monthly",
"wage": wage,
"l10n_au_casual_loading": 0.0,
"structure_type_id": self.env.ref("l10n_au_hr_payroll.structure_type_schedule_1").id,
# fields modified in the tests
"schedule_pay": period,
"l10n_au_employment_basis_code": "F",
"l10n_au_income_stream_type": "SAW",
"l10n_au_withholding_variation": False,
"l10n_au_withholding_variation_amount": 0,
"l10n_au_workplace_giving": 0,
"l10n_au_tax_treatment_category": "R",
"l10n_au_tax_treatment_option": "T",
})
employee_id.contract_id = contract_id
return employee_id, contract_id
def test_withholding_monthly_regular_employee(self):
employee_id, contract_id = self.create_employee_and_contract(5000, "monthly")
no_tfn_structure_type = self.env.ref("l10n_au_hr_payroll.structure_type_no_tfn")
payslip_id = self.env["hr.payslip"].create({
"name": "payslip",
"employee_id": employee_id.id,
"contract_id": contract_id.id,
"struct_id": self.struct_regular.id,
"date_from": date(2023, 7, 1),
"date_to": date(2023, 7, 31),
})
# Scenario 1: Tax free threshold claimed
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 932, "test scenario 1: tax free threshold claimed")
# Scenario 2: Tax free threshold not claimed
employee_id.l10n_au_tax_free_threshold = False
self.assertEqual(employee_id.l10n_au_scale, "1")
self.assertEqual(employee_id.contract_id.l10n_au_tax_treatment_option, "N")
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 1456, "test scenario 2: tax free threshold not claimed")
employee_id.l10n_au_tax_free_threshold = True
# Scenario 3: Foreign resident
employee_id.is_non_resident = True
self.assertEqual(employee_id.l10n_au_scale, "3")
self.assertEqual(employee_id.contract_id.l10n_au_tax_treatment_option, "T")
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 1625, "test scenario 3: foreign resident")
employee_id.is_non_resident = False
# Scenario 4: HELP / STSL Loan
employee_id.l10n_au_training_loan = True
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 1032, "test scenario 4: HELP / STSL Loan")
employee_id.l10n_au_training_loan = False
# Scenario 5: Tax offset
employee_id.l10n_au_nat_3093_amount = 100
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 924, "test scenario 5: Tax offset of 100")
employee_id.l10n_au_nat_3093_amount = 0
# Scenario 6: Half medicare exemption
employee_id.l10n_au_medicare_exemption = "H"
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 880, "test scenario 6: Half medicare exemption")
# Scenario 7: Full medicare exemption
employee_id.l10n_au_medicare_exemption = "F"
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 832, "test scenario 7: Full medicare exemption")
employee_id.l10n_au_medicare_exemption = "X"
# Scenario 9: Medicare surcharge
employee_id.l10n_au_tfn_declaration = "000000000"
employee_id.contract_id.structure_type_id = self.env.ref("l10n_au_hr_payroll.structure_type_no_tfn")
payslip_id.struct_id = self.env.ref("l10n_au_hr_payroll.hr_payroll_structure_au_no_tfn")
self.assertEqual(employee_id.contract_id.structure_type_id, no_tfn_structure_type)
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 2350, "test scenario 9: TFN not provided")
# Scenario 10: TFN applied for but not provided, less than 28 days ago
employee_id.l10n_au_tfn_declaration = "111111111"
# structure_type was no tfn from previous test, make sure that changing declaration to a valid tfn does not change the structure
# because this has to be done manually by the payroll agent
self.assertEqual(employee_id.contract_id.structure_type_id, no_tfn_structure_type)
payslip_id.contract_id.structure_type_id = self.env.ref("l10n_au_hr_payroll.structure_type_schedule_1")
payslip_id.struct_id = self.struct_regular
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 932, "test scenario 10: TFN applied for but not provided, less than 28 days ago")
# Scenario 11: Employee under 18, and earns less than 350$ weekly
employee_id.l10n_au_tfn_declaration = "333333333"
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 932, "test scenario 11: Employee under 18, and earns less than 350$ weekly")
# Scenario 12: Exempt from TFN
employee_id.l10n_au_tfn_declaration = "444444444"
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 932, "test scenario 12: Exempt from TFN")
# Scenario 13: 4 children, medicare reduction
employee_id.write({
"l10n_au_tfn_declaration": "provided",
"l10n_au_tfn": "12345678",
"marital": "married",
"children": 4,
})
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 893, "test scenario 13: 4 children, medicare reduction")
employee_id.write({
"l10n_au_tfn_declaration": "provided",
"l10n_au_tfn": "12345678",
# kill the wife
"marital": "single",
# kill the children
"children": 0,
})
# Scenario 14: Withholding variation
payslip_id.contract_id.write({
"l10n_au_withholding_variation": True,
"l10n_au_withholding_variation_amount": 15,
})
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 750, "test scenario 14: Withholding variation")
payslip_id.contract_id.write({
"l10n_au_withholding_variation": False,
})
def test_withholding_weekly_regular_employee(self):
employee_id, contract_id = self.create_employee_and_contract(1000, "weekly")
no_tfn_structure_type = self.env.ref("l10n_au_hr_payroll.structure_type_no_tfn")
payslip_id = self.env["hr.payslip"].create({
"name": "payslip",
"employee_id": employee_id.id,
"contract_id": contract_id.id,
"struct_id": self.struct_regular.id,
"date_from": date(2023, 7, 1),
"date_to": date(2023, 7, 7),
})
# Scenario 26: Tax free threshold claimed
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 162, "test scenario 26: tax free threshold claimed")
# Scenario 27: Tax free threshold not claimed
employee_id.l10n_au_tax_free_threshold = False
self.assertEqual(employee_id.l10n_au_scale, "1")
self.assertEqual(employee_id.contract_id.l10n_au_tax_treatment_option, "N")
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 283, "test scenario 27: tax free threshold not claimed")
employee_id.l10n_au_tax_free_threshold = True
# Scenario 28: Foreign resident
employee_id.is_non_resident = True
self.assertEqual(employee_id.l10n_au_scale, "3")
self.assertEqual(employee_id.contract_id.l10n_au_tax_treatment_option, "T")
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 325, "test scenario 28: foreign resident")
employee_id.is_non_resident = False
# Scenario 29: HELP / STSL Loan
employee_id.l10n_au_training_loan = True
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 172, "test scenario 29: HELP / STSL Loan")
employee_id.l10n_au_training_loan = False
# Scenario 30: Tax offset
employee_id.l10n_au_nat_3093_amount = 100
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 160, "test scenario 30: Tax offset of 100")
employee_id.l10n_au_nat_3093_amount = 0
# Scenario 31: Half medicare exemption
employee_id.l10n_au_medicare_exemption = "H"
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 152, "test scenario 31: Half medicare exemption")
# Scenario 32: Full medicare exemption
employee_id.l10n_au_medicare_exemption = "F"
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 142, "test scenario 32: Full medicare exemption")
employee_id.l10n_au_medicare_exemption = "X"
# Scenario 34: Medicare surcharge
employee_id.l10n_au_tfn_declaration = "000000000"
employee_id.contract_id.structure_type_id = self.env.ref("l10n_au_hr_payroll.structure_type_no_tfn")
payslip_id.struct_id = self.env.ref("l10n_au_hr_payroll.hr_payroll_structure_au_no_tfn")
self.assertEqual(employee_id.contract_id.structure_type_id, no_tfn_structure_type)
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 470, "test scenario 34: TFN not provided")
# Scenario 35: TFN applied for but not provided, less than 28 days ago
employee_id.l10n_au_tfn_declaration = "111111111"
# structure_type was no tfn from previous test, make sure that changing declaration to a valid tfn does not change the structure
# because this has to be done manually by the payroll agent
self.assertEqual(employee_id.contract_id.structure_type_id, no_tfn_structure_type)
payslip_id.contract_id.structure_type_id = self.env.ref("l10n_au_hr_payroll.structure_type_schedule_1")
payslip_id.struct_id = self.struct_regular
self.assertEqual(payslip_id.worked_days_line_ids.amount, 1000)
payslip_id.contract_id.schedule_pay = "weekly"
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 162, "test scenario 35: TFN applied for but not provided, less than 28 days ago")
# Scenario 36: Employee under 18, and earns less than 350$ weekly
employee_id.l10n_au_tfn_declaration = "333333333"
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 162, "test scenario 36: Employee under 18, and earns less than 350$ weekly")
# Scenario 37: Exempt from TFN
employee_id.l10n_au_tfn_declaration = "444444444"
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 162, "test scenario 37: Exempt from TFN")
# Scenario 38: 4 children, medicare reduction
employee_id.write({
"l10n_au_tfn_declaration": "provided",
"l10n_au_tfn": "12345678",
"marital": "married",
"children": 4,
})
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 142, "test scenario 38: 4 children, medicare reduction")
employee_id.write({
"l10n_au_tfn_declaration": "provided",
"l10n_au_tfn": "12345678",
# kill the wife
"marital": "single",
# kill the children
"children": 0,
})
# Scenario 39: Withholding variation
payslip_id.contract_id.write({
"l10n_au_withholding_variation": True,
"l10n_au_withholding_variation_amount": 15,
})
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertEqual(-lbc["WITHHOLD.TOTAL"]["total"], 150, "test scenario 39: Withholding variation")
payslip_id.contract_id.write({
"l10n_au_withholding_variation": False,
})
def test_termination_payment_unused_leaves(self):
employee_id, contract_id = self.create_employee_and_contract(5000, "monthly")
# Allocate Holidays
self.env['hr.leave.allocation'].create([{
'name': 'Paid Time Off 2023',
'holiday_status_id': self.env.ref('l10n_au_hr_payroll.hr_leave_type_annual_au').id,
'number_of_days': 15,
'employee_id': employee_id.id,
'state': 'confirm',
'date_from': date(2023, 1, 1),
'date_to': date(2023, 12, 31),
}]).action_validate()
payslip_term = self.env["hr.payslip"].create({
"name": "Termination Payment",
"employee_id": employee_id.id,
"contract_id": contract_id.id,
"struct_id": self.env.ref("l10n_au_hr_payroll.hr_payroll_structure_au_termination").id,
"l10n_au_termination_type": "genuine",
"date_from": date(2023, 8, 1),
"date_to": date(2023, 8, 31),
"input_line_ids": [
Command.create({'input_type_id': self.env.ref('l10n_au_hr_payroll.input_severance_pay').id, 'amount': 15000}),
Command.create({'input_type_id': self.env.ref('l10n_au_hr_payroll.input_golden_handshake').id, 'amount': 20000}),
Command.create({'input_type_id': self.env.ref('l10n_au_hr_payroll.input_genuine_redundancy').id, 'amount': 5000}),
]
})
# print(payslip_term.input_line_ids.read(['input_type_id']))
# Scenario 1: Tax free threshold claimed
payslip_term.compute_sheet()
lbc = self.lines_by_code(payslip_term.line_ids)
self.assertEqual(lbc["LEAVE"]["total"], 3461.54, "Incorrect Leave base")
self.assertEqual(-lbc["LEAVE.WITHHOLD"]["total"], 1108, "Withhold incorrect for genuine redundancy.")