Skip to content

Commit

Permalink
FFT 142 Tests for attrition and uplift (#606)
Browse files Browse the repository at this point in the history
  • Loading branch information
CaitBarnard authored Jan 29, 2025
1 parent f6b6071 commit 8b05ed2
Show file tree
Hide file tree
Showing 3 changed files with 185 additions and 56 deletions.
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,
},
)

0 comments on commit 8b05ed2

Please sign in to comment.