From 402f14e620fa6ace19f8cd7346ddd92f016a4f7a Mon Sep 17 00:00:00 2001 From: Caitlin Barnard Date: Fri, 6 Dec 2024 16:21:14 +0000 Subject: [PATCH 1/2] Add PayUplift model and multiply pay periods by pay uplift --- core/admin.py | 21 +++++++++++++- core/migrations/0014_payuplift.py | 47 +++++++++++++++++++++++++++++++ core/models.py | 19 +++++++++++++ payroll/services/payroll.py | 29 ++++++++++++++++++- 4 files changed, 114 insertions(+), 2 deletions(-) create mode 100644 core/migrations/0014_payuplift.py diff --git a/core/admin.py b/core/admin.py index 006f84c45..7f36214df 100644 --- a/core/admin.py +++ b/core/admin.py @@ -12,7 +12,7 @@ from django.views.decorators.csrf import csrf_exempt from core.export_data import export_logentry_iterator -from core.models import CommandLog, FinancialYear +from core.models import CommandLog, FinancialYear, PayUplift from core.utils.export_helpers import ( export_csv_from_import, export_to_csv, @@ -368,6 +368,25 @@ def has_delete_permission(self, request, obj=None): return False +class PayUpliftAdmin(admin.ModelAdmin): + list_display = ( + "financial_year", + "apr", + "may", + "jun", + "jul", + "aug", + "sep", + "oct", + "nov", + "dec", + "jan", + "feb", + "mar", + ) + + admin.site.register(LogEntry, LogEntryAdmin) admin.site.register(FinancialYear, FinancialYearAdmin) admin.site.register(CommandLog, CustomLogModelAdmin) +admin.site.register(PayUplift, PayUpliftAdmin) diff --git a/core/migrations/0014_payuplift.py b/core/migrations/0014_payuplift.py new file mode 100644 index 000000000..ffb9f470b --- /dev/null +++ b/core/migrations/0014_payuplift.py @@ -0,0 +1,47 @@ +# Generated by Django 5.1.3 on 2024-12-06 15:33 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("core", "0013_alter_historicalgroup_options_and_more"), + ] + + operations = [ + migrations.CreateModel( + name="PayUplift", + fields=[ + ( + "id", + models.AutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("apr", models.FloatField(default=1.0)), + ("may", models.FloatField(default=1.0)), + ("jun", models.FloatField(default=1.0)), + ("jul", models.FloatField(default=1.0)), + ("aug", models.FloatField(default=1.0)), + ("sep", models.FloatField(default=1.0)), + ("oct", models.FloatField(default=1.0)), + ("nov", models.FloatField(default=1.0)), + ("dec", models.FloatField(default=1.0)), + ("jan", models.FloatField(default=1.0)), + ("feb", models.FloatField(default=1.0)), + ("mar", models.FloatField(default=1.0)), + ( + "financial_year", + models.ForeignKey( + on_delete=django.db.models.deletion.PROTECT, + to="core.financialyear", + ), + ), + ], + ), + ] diff --git a/core/models.py b/core/models.py index 0c69ae69f..ceeca4435 100644 --- a/core/models.py +++ b/core/models.py @@ -87,6 +87,25 @@ def __str__(self): return str(self.financial_year_display) +class PayUplift(models.Model): + financial_year = models.ForeignKey( + FinancialYear, + on_delete=models.PROTECT, + ) + apr = models.FloatField(default=1.0) + may = models.FloatField(default=1.0) + jun = models.FloatField(default=1.0) + jul = models.FloatField(default=1.0) + aug = models.FloatField(default=1.0) + sep = models.FloatField(default=1.0) + oct = models.FloatField(default=1.0) + nov = models.FloatField(default=1.0) + dec = models.FloatField(default=1.0) + jan = models.FloatField(default=1.0) + feb = models.FloatField(default=1.0) + mar = models.FloatField(default=1.0) + + # Track changes to permissions register(Permission, app=__package__, inherit=True) register(get_user_model(), app=__package__, inherit=True) diff --git a/payroll/services/payroll.py b/payroll/services/payroll.py index b0418f15d..28a3fa5c9 100644 --- a/payroll/services/payroll.py +++ b/payroll/services/payroll.py @@ -9,7 +9,7 @@ from django.db.models import Avg, Count, Q from core.constants import MONTHS -from core.models import FinancialYear +from core.models import FinancialYear, PayUplift from core.types import MonthsDict from costcentre.models import CostCentre from gifthospitality.models import Grade @@ -75,10 +75,15 @@ def payroll_forecast_report( cost_centre=cost_centre, pay_periods__year=financial_year, ) + pay_uplift_obj = PayUplift.objects.filter(financial_year=financial_year).first() + pay_uplift = pay_uplift_to_numpy_array(pay_uplift_obj) + for employee in employee_qs.iterator(): periods = employee.pay_periods.first().periods periods = np.array(periods) + periods = periods * pay_uplift + prog_report = report[employee.programme_code_id] prog_report[settings.PAYROLL.BASIC_PAY_NAC] += periods * employee.basic_pay prog_report[settings.PAYROLL.PENSION_NAC] += periods * employee.pension @@ -109,6 +114,28 @@ def payroll_forecast_report( ) +def pay_uplift_to_numpy_array(instance): + if instance is None: + return np.ones(12) + + fields = [ + "apr", + "may", + "jun", + "jul", + "aug", + "sep", + "oct", + "nov", + "dec", + "jan", + "feb", + "mar", + ] + + return np.array([getattr(instance, field, 1.0) for field in fields]) + + # TODO (FFT-131): Apply caching to the average salary calculation def get_average_salary_for_grade(grade: Grade, cost_centre: CostCentre) -> int: employee_count_threshold = settings.PAYROLL.AVERAGE_SALARY_THRESHOLD From 2c3cdcd21d48b659e3d86d35a979f3b162e59a4f Mon Sep 17 00:00:00 2001 From: Caitlin Barnard Date: Mon, 9 Dec 2024 10:20:42 +0000 Subject: [PATCH 2/2] Move pay uplift numpy array method to model --- core/models.py | 6 ++++++ payroll/services/payroll.py | 31 ++++++++----------------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/core/models.py b/core/models.py index ceeca4435..fff434a5c 100644 --- a/core/models.py +++ b/core/models.py @@ -3,6 +3,8 @@ from django.db import models from simple_history import register +from core.constants import MONTHS + from .metamodels import BaseModel @@ -88,6 +90,10 @@ def __str__(self): class PayUplift(models.Model): + @property + def periods(self) -> list[float]: + return [getattr(self, month) for month in MONTHS] + financial_year = models.ForeignKey( FinancialYear, on_delete=models.PROTECT, diff --git a/payroll/services/payroll.py b/payroll/services/payroll.py index 28a3fa5c9..54e6bbf63 100644 --- a/payroll/services/payroll.py +++ b/payroll/services/payroll.py @@ -76,7 +76,14 @@ def payroll_forecast_report( pay_periods__year=financial_year, ) pay_uplift_obj = PayUplift.objects.filter(financial_year=financial_year).first() - pay_uplift = pay_uplift_to_numpy_array(pay_uplift_obj) + + pay_uplift = ( + np.array( + PayUplift.objects.filter(financial_year=financial_year).first().periods + ) + if pay_uplift_obj is not None + else np.ones(12) + ) for employee in employee_qs.iterator(): periods = employee.pay_periods.first().periods @@ -114,28 +121,6 @@ def payroll_forecast_report( ) -def pay_uplift_to_numpy_array(instance): - if instance is None: - return np.ones(12) - - fields = [ - "apr", - "may", - "jun", - "jul", - "aug", - "sep", - "oct", - "nov", - "dec", - "jan", - "feb", - "mar", - ] - - return np.array([getattr(instance, field, 1.0) for field in fields]) - - # TODO (FFT-131): Apply caching to the average salary calculation def get_average_salary_for_grade(grade: Grade, cost_centre: CostCentre) -> int: employee_count_threshold = settings.PAYROLL.AVERAGE_SALARY_THRESHOLD