Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FFT 142 Tests for attrition and uplift #606

Merged
merged 8 commits into from
Jan 29, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions payroll/services/payroll.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,16 +94,17 @@ def payroll_forecast_report(

pay_uplift = np.array(pay_uplift_obj.periods) if pay_uplift_obj else np.ones(12)
attrition = np.array(attrition_obj.periods) if attrition_obj else np.ones(12)
pay_uplift_accumulate = np.array(list(accumulate(pay_uplift, operator.mul)))
attrition_accumulate = np.array(list(accumulate(attrition, operator.mul)))

for employee in employee_qs.iterator():
periods = employee.pay_periods.first().periods
periods = np.array(periods)

periods = periods * pay_uplift * attrition_accumulate

prog_report = report[employee.programme_code_id]
prog_report[settings.PAYROLL.BASIC_PAY_NAC] += periods * employee.basic_pay
prog_report[settings.PAYROLL.BASIC_PAY_NAC] += (
periods * employee.basic_pay * pay_uplift_accumulate * attrition_accumulate
)
prog_report[settings.PAYROLL.PENSION_NAC] += periods * employee.pension
prog_report[settings.PAYROLL.ERNIC_NAC] += periods * employee.ernic

Expand Down
30 changes: 30 additions & 0 deletions payroll/tests/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from faker import Faker

from chartofaccountDIT.test.factories import NaturalCodeFactory, ProgrammeCodeFactory
from core.models import Attrition, PayUplift
from core.test.factories import FinancialYearFactory
from costcentre.test.factories import CostCentreFactory
from gifthospitality.test.factories import GradeFactory
from payroll.models import Employee, PayElementType, PayElementTypeGroup, Vacancy
Expand Down Expand Up @@ -63,3 +65,31 @@ class Meta:
appointee_name = factory.Faker("name")
hiring_manager = factory.Faker("name")
hr_ref = factory.Faker("name")


class PayModifierFactory(factory.django.DjangoModelFactory):
financial_year = factory.SubFactory(FinancialYearFactory)
apr = 1.0
may = 1.0
jun = 1.0
jul = 1.0
aug = 1.0
sep = 1.0
oct = 1.0
nov = 1.0
dec = 1.0
jan = 1.0
feb = 1.0
mar = 1.0


class PayUpliftFactory(PayModifierFactory):
class Meta:
model = PayUplift


class AttritionFactory(PayModifierFactory):
class Meta:
model = Attrition

cost_centre = factory.SubFactory(CostCentreFactory)
204 changes: 151 additions & 53 deletions payroll/tests/services/test_payroll.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import pytest

from core.constants import MONTHS
from core.models import FinancialYear
from costcentre.test.factories import CostCentreFactory
from payroll.services.payroll import (
Expand All @@ -10,31 +11,42 @@
vacancy_created,
)

from ..factories import EmployeeFactory, VacancyFactory
from ..factories import (
AttritionFactory,
EmployeeFactory,
PayUpliftFactory,
VacancyFactory,
)


def test_payroll_forecast(db):
# NOTE: These must match the PAYROLL.BASIC_PAY_NAC and PAYROLL.PENSION_NAC settings.
SALARY_NAC = "71111001"
PENSION_NAC = "71111002"
# NOTE: These must match the PAYROLL.BASIC_PAY_NAC, PAYROLL.PENSION_NAC and PAYROLL.ERNIC_NAC settings.
SALARY_NAC = "71111001"
PENSION_NAC = "71111002"
ERNIC_NAC = "71111003"
NACS = [SALARY_NAC, PENSION_NAC, ERNIC_NAC]

cost_centre = CostCentreFactory.create(cost_centre_code="123456")

# salary_1 = PayElementTypeFactory.create(
# name="Salary 1",
# group__name="Salary",
# group__natural_code__natural_account_code=SALARY_NAC,
# )
# salary_2 = PayElementTypeFactory.create(
# name="Salary 2",
# group__name="Salary",
# group__natural_code__natural_account_code=SALARY_NAC,
# )
# pension_1 = PayElementTypeFactory.create(
# name="Pension 1",
# group__name="Pension",
# group__natural_code__natural_account_code=PENSION_NAC,
# )
def assert_report_results_with_modifiers(
report, salary, pension, ernic, modifiers=None
):
if modifiers is None:
modifiers = {}

for nac in NACS:
for month in MONTHS:
modifier = modifiers.get(month, 1)
if nac == SALARY_NAC:
expected_result = salary * modifier
elif nac == PENSION_NAC:
expected_result = pension
else:
expected_result = ernic

assert float(report[nac][month]) == pytest.approx(expected_result)


def test_payroll_forecast(db):
cost_centre = CostCentreFactory.create(cost_centre_code="123456")

payroll_employee_1 = EmployeeFactory.create(
cost_centre=cost_centre,
Expand Down Expand Up @@ -64,38 +76,6 @@ def test_payroll_forecast(db):
employee_created(payroll_employee_1)
employee_created(payroll_employee_2)

# payroll_employees[0].pay_element.create(
# type=salary_1,
# debit_amount=2000,
# credit_amount=100,
# )
# payroll_employees[0].pay_element.create(
# type=salary_2,
# debit_amount=100,
# credit_amount=50,
# )
# payroll_employees[0].pay_element.create(
# type=pension_1,
# debit_amount=75.5,
# credit_amount=0,
# )

# payroll_employees[1].pay_element.create(
# type=salary_1,
# debit_amount=1500,
# credit_amount=55.6,
# )
# payroll_employees[1].pay_element.create(
# type=salary_2,
# debit_amount=80,
# credit_amount=0,
# )
# payroll_employees[1].pay_element.create(
# type=pension_1,
# debit_amount=130.25,
# credit_amount=15,
# )

vacancy = VacancyFactory.create(
cost_centre=cost_centre,
programme_code__programme_code="123456",
Expand Down Expand Up @@ -136,3 +116,121 @@ def test_payroll_forecast(db):
assert float(report_by_nac[PENSION_NAC]["may"]) == pytest.approx(e1p)
assert float(report_by_nac[SALARY_NAC]["jun"]) == pytest.approx(v1s)
assert float(report_by_nac[PENSION_NAC]["jun"]) == pytest.approx(0)


def test_one_employee_with_no_modifiers(db):
cost_centre = CostCentreFactory.create(cost_centre_code="123456")

payroll_employee_1 = EmployeeFactory.create(
cost_centre=cost_centre,
basic_pay=195000,
pension=7550,
ernic=2000,
)

employee_created(payroll_employee_1)

financial_year = FinancialYear.objects.current()

report = payroll_forecast_report(cost_centre, financial_year)

report_by_nac = {x["natural_account_code"]: x for x in report}

e1s = ((2000 - 100) + (100 - 50)) * 100
e1p = (75.5 - 0) * 100
e1e = payroll_employee_1.ernic

assert_report_results_with_modifiers(report_by_nac, e1s, e1p, e1e)


def test_one_employee_with_pay_uplift(db):
cost_centre = CostCentreFactory.create(cost_centre_code="123456")

payroll_employee_1 = EmployeeFactory.create(
cost_centre=cost_centre,
basic_pay=195000,
pension=7550,
ernic=2000,
)

employee_created(payroll_employee_1)

financial_year = FinancialYear.objects.current()

pay_uplift = PayUpliftFactory.create(
financial_year=financial_year,
aug=1.02,
)
modifier = pay_uplift.aug

report = payroll_forecast_report(cost_centre, financial_year)

report_by_nac = {x["natural_account_code"]: x for x in report}

e1s = ((2000 - 100) + (100 - 50)) * 100
e1p = (75.5 - 0) * 100
e1e = payroll_employee_1.ernic

assert_report_results_with_modifiers(
report_by_nac,
e1s,
e1p,
e1e,
modifiers={
"aug": modifier,
"sep": modifier,
"oct": modifier,
"nov": modifier,
"dec": modifier,
"jan": modifier,
"feb": modifier,
"mar": modifier,
},
)


def test_one_employee_with_attrition(db):
cost_centre = CostCentreFactory.create(cost_centre_code="123456")

payroll_employee_1 = EmployeeFactory.create(
cost_centre=cost_centre,
basic_pay=195000,
pension=7550,
ernic=2000,
)

employee_created(payroll_employee_1)

financial_year = FinancialYear.objects.current()

attrition = AttritionFactory.create(
cost_centre=cost_centre,
financial_year=financial_year,
aug=0.95,
)
modifier = attrition.aug

report = payroll_forecast_report(cost_centre, financial_year)

report_by_nac = {x["natural_account_code"]: x for x in report}

e1s = ((2000 - 100) + (100 - 50)) * 100
e1p = (75.5 - 0) * 100
e1e = payroll_employee_1.ernic

assert_report_results_with_modifiers(
report_by_nac,
e1s,
e1p,
e1e,
modifiers={
"aug": modifier,
"sep": modifier,
"oct": modifier,
"nov": modifier,
"dec": modifier,
"jan": modifier,
"feb": modifier,
"mar": modifier,
},
)