Files
enterprise-addons/l10n_au_hr_payroll/tests/test_payroll.py
T

458 lines
21 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 freezegun import freeze_time
from contextlib import closing
from unittest import skip
from odoo import Command
from odoo.tests import tagged
from .common import TestPayrollCommon
@skip("This test is failing on runbot, it is not a priority to fix it now")
@tagged("post_install_l10n", "post_install", "-at_install", "l10n_au_hr_payroll")
class TestPayroll(TestPayrollCommon):
@classmethod
def setUpClass(cls):
super().setUpClass()
cls.startClassPatcher(freeze_time(date(2024, 1, 1)))
cls.tax_treatment_category = 'R'
def test_withholding_amounts(self):
test_scales = [
{'l10n_au_tax_free_threshold': False}, # Scale 1
{'l10n_au_tax_free_threshold': True}, # Scale 2
{'is_non_resident': True}, # Scale 3
{"l10n_au_medicare_exemption": "F"}, # Scale 5
{"l10n_au_medicare_exemption": "H"}, # Scale 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.default_payroll_structure.id,
})
payslip._compute_date_to()
self.employee_id.write({"l10n_au_tax_free_threshold": False})
for period, sample_data in self.schedule_1_withholding_sample_data.items():
for row in sample_data:
earnings = row[0]
self.version_ids[0].wage = earnings
for idx, test_data in enumerate(row[1:]):
with self.subTest(earnings=earnings, scale=test_scales[idx]):
with closing(self.env.cr.savepoint()):
self.employee_id.write(test_scales[idx])
coefficients = payslip._l10n_au_tax_schedule_parameters()
amount = payslip._l10n_au_compute_withholding_amount(earnings, period, coefficients)
self.assertEqual(amount, test_data, f"weekly earnings of {earnings} scale {idx}: 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.default_payroll_structure.id,
})
payslip._compute_date_to()
params = payslip._rule_parameter("l10n_au_withholding_schedule_1")['medicare']
if self.employee_id.l10n_au_tax_free_threshold:
key = 'tax-free'
elif self.employee_id.l10n_au_medicare_exemption == 'H':
key = 'half-exemption'
params = params[key]
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.version_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.default_payroll_structure.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_tax_free_threshold = False
coefs = loan_coefs["no-tax-free"]
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} expected {expected} got {-amount}")
self.employee_id.l10n_au_tax_free_threshold = True
coefs = loan_coefs["tax-free"]
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}: 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.version_ids[0].wage = 1103.45
self.version_ids[0].schedule_pay = "weekly"
self.employee_id.marital = "married"
self.employee_id.children = 5
self.employee_id.l10n_au_tax_free_threshold = True
payslip_id = self.env["hr.payslip"].create({
"name": "payslip",
"employee_id": self.employee_id.id,
"version_id": self.version_ids[0].id,
"struct_id": self.default_payroll_structure.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.version_ids[0].wage = 1110.30
self.version_ids[0].schedule_pay = "bi-weekly"
self.employee_id.marital = "single"
self.employee_id.children = 0
self.employee_id.l10n_au_medicare_exemption = "F"
self.employee_id.l10n_au_tax_free_threshold = True
self.employee_id.l10n_au_nat_3093_amount = 1645
payslip_id = self.env["hr.payslip"].create({
"name": "payslip",
"employee_id": self.employee_id.id,
"version_id": self.version_ids[0].id,
"struct_id": self.default_payroll_structure.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.version_ids[0].wage = 4500.33
self.version_ids[0].schedule_pay = "monthly"
self.employee_id.marital = "married"
self.employee_id.children = 1
self.employee_id.l10n_au_medicare_exemption = "X"
self.employee_id.l10n_au_tax_free_threshold = True
self.employee_id.l10n_au_nat_3093_amount = 1365
payslip_id = self.env["hr.payslip"].create({
"name": "payslip",
"employee_id": self.employee_id.id,
"version_id": self.version_ids[0].id,
"struct_id": self.default_payroll_structure.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 test_withholding_monthly_regular_employee(self):
self.tax_treatment_category = 'R'
employee_id, version_id = self.create_employee_and_contract(5000, {'schedule_pay': 'monthly', "l10n_au_training_loan": False, "l10n_au_tax_free_threshold": True})
payslip_id = self.env["hr.payslip"].create({
"name": "payslip",
"employee_id": employee_id.id,
"version_id": version_id.id,
"struct_id": self.default_payroll_structure.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
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")
# Scenario 3: Foreign resident
employee_id.is_non_resident = True
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
employee_id.l10n_au_tax_free_threshold = 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"
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertAlmostEqual(-lbc["WITHHOLD.TOTAL"]["total"], 2349, 0, msg="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
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": "999999661",
"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": "999999661",
# kill the wife
"marital": "single",
# kill the children
"children": 0,
})
# Scenario 14: Withholding variation
payslip_id.employee_id.write({
"l10n_au_withholding_variation": 'salaries',
"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.employee_id.write({
"l10n_au_withholding_variation": 'none',
})
def test_withholding_weekly_regular_employee(self):
employee_id, version_id = self.create_employee_and_contract(1000, {'schedule_pay': 'weekly', "l10n_au_training_loan": False, 'l10n_au_tax_free_threshold': True})
payslip_id = self.env["hr.payslip"].create({
"name": "payslip",
"employee_id": employee_id.id,
"version_id": version_id.id,
"struct_id": self.default_payroll_structure.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
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")
# Scenario 28: Foreign resident
employee_id.is_non_resident = True
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
employee_id.l10n_au_tax_free_threshold = True
# 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"
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertAlmostEqual(-lbc["WITHHOLD.TOTAL"]["total"], 470, 0, "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
payslip_id.version_id.schedule_pay = "weekly"
payslip_id.date_from = date(2023, 7, 3)
payslip_id.date_to = date(2023, 7, 7)
payslip_id.compute_sheet()
# Weekly wage is 1000 in this case
self.assertAlmostEqual(payslip_id.worked_days_line_ids.amount, 1000, 0)
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": "999999661",
"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": "999999661",
# kill the wife
"marital": "single",
# kill the children
"children": 0,
})
# Scenario 39: Withholding variation
payslip_id.employee_id.write({
"l10n_au_withholding_variation": 'salaries',
"l10n_au_withholding_variation_amount": 15,
})
payslip_id.compute_sheet()
lbc = self.lines_by_code(payslip_id.line_ids)
self.assertAlmostEqual(-lbc["WITHHOLD.TOTAL"]["total"], 150, 0, "test scenario 39: Withholding variation")
payslip_id.employee_id.write({
"l10n_au_withholding_variation": 'none',
})
def test_termination_payment_unused_leaves(self):
employee_id, version_id = self.create_employee_and_contract(5000, {'schedule_pay': 'monthly', 'scale': '2'})
# Allocate Holidays
self.env['hr.leave.allocation'].create([{
'name': 'Paid Time Off 2023-24',
'holiday_status_id': self.annual_leave_type.id,
'number_of_days': 15,
'employee_id': employee_id.id,
'state': 'confirm',
'date_from': date(2023, 7, 1),
'date_to': date(2024, 6, 30),
}]).action_approve()
# This would be done by the wizard.
version_id.date_end = date(2023, 8, 31)
payslip_term = self.env["hr.payslip"].create({
"name": "Termination Payment",
"employee_id": employee_id.id,
"version_id": version_id.id,
"struct_id": self.env.ref("l10n_au_hr_payroll.hr_payroll_structure_au_regular").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}),
]
})
# Scenario 1: Tax free threshold claimed
payslip_term.compute_sheet()
lbc = self.lines_by_code(payslip_term.line_ids)
self.assertEqual(lbc["ETP.LEAVE.GROSS"]["total"], 3461.54, "Incorrect Leave base")
self.assertEqual(-lbc["ETP.LEAVE.WITHHOLD"]["total"], 1108, "Withhold incorrect for genuine redundancy.")