Skip to content

Commit

Permalink
Add vacancies to forecast (#554)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamDudley authored Dec 6, 2024
1 parent fac2b97 commit c390d06
Show file tree
Hide file tree
Showing 17 changed files with 497 additions and 179 deletions.
1 change: 1 addition & 0 deletions .env.ci
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ AUTHBROKER_URL=
SENTRY_ENVIRONMENT=ci
SENTRY_DSN=
CSP_REPORT_URI=
PAYROLL={"BASIC_PAY_NAC": "71111001", "PENSION_NAC": "71111002", "ERNIC_NAC": "71111003", "VACANCY_NAC": "71111001"}
2 changes: 2 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ CSP_REPORT_URI=
# Vite
VITE_DEV=True

PAYROLL={"BASIC_PAY_NAC": "71111001", "PENSION_NAC": "71111002", "ERNIC_NAC": "71111003", "VACANCY_NAC": "71111001"}

# Not documented (needed?)
# RESTRICT_ADMIN=True
# PUBLIC_PATH="http://localhost:8000"
Expand Down
14 changes: 14 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
"""

import os
from dataclasses import dataclass
from pathlib import Path

import dj_database_url
Expand Down Expand Up @@ -415,3 +416,16 @@ def FILTERS_VERBOSE_LOOKUPS():

CSP_REPORT_ONLY = True
CSP_REPORT_URI = env.str("CSP_REPORT_URI", default=None)


# Payroll
@dataclass
class Payroll:
BASIC_PAY_NAC: str | None = None
PENSION_NAC: str | None = None
ERNIC_NAC: str | None = None
VACANCY_NAC: str | None = None
AVERAGE_SALARY_THRESHOLD: int = 2


PAYROLL: Payroll = Payroll(**env.json("PAYROLL", default={}))
17 changes: 17 additions & 0 deletions core/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from .types import Months


MONTHS: Months = (
"apr",
"may",
"jun",
"jul",
"aug",
"sep",
"oct",
"nov",
"dec",
"jan",
"feb",
"mar",
)
46 changes: 46 additions & 0 deletions core/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from typing import Literal, TypedDict


Month = Literal[
"apr",
"may",
"jun",
"jul",
"aug",
"sep",
"oct",
"nov",
"dec",
"jan",
"feb",
"mar",
]
Months = tuple[
Literal["apr"],
Literal["may"],
Literal["jun"],
Literal["jul"],
Literal["aug"],
Literal["sep"],
Literal["oct"],
Literal["nov"],
Literal["dec"],
Literal["jan"],
Literal["feb"],
Literal["mar"],
]


class MonthsDict[T](TypedDict):
apr: T
may: T
jun: T
jul: T
aug: T
sep: T
oct: T
nov: T
dec: T
jan: T
feb: T
mar: T
2 changes: 1 addition & 1 deletion core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@
path("", views.index, name="index"),
path("logout", views.logout, name="logout"),
path("accessibility", views.AccessibilityPageView.as_view(), name="accessibility"),
path("report/budget-report", views.budget_report, name="report:budget_report"),
path("report/budget-report", views.budget_report, name="budget-report"),
]
13 changes: 13 additions & 0 deletions forecast/templatetags/forecast_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,19 @@
]


@register.filter
def format_money(value: float) -> str:
"""Format as a monetary value.
`value` is expected to be in pence and will be divided by 100.
Examples:
>>> format_money(1024312)
'10,243.12'
"""
return f"{value / 100:,.2f}"


@register.filter()
def is_forecast_figure(_, column):
if str(column) in forecast_figure_cols:
Expand Down
10 changes: 4 additions & 6 deletions front_end/src/Util.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,10 +168,8 @@ export const processForecastData = (forecastData, payrollData = null, isPayrollE
let overrideAmount = null

if (isPayrollEnabled && mappedPayrollData[forecastKey]) {
const period = `period_${(parseInt(key)+1)}_sum`
// TODO (FFT-99): Decide on decimal vs pence
// Old code stores monetary values in pence whereas new code has used decimals.
overrideAmount = mappedPayrollData[forecastKey][period] * 100
const period = months[parseInt(key)]
overrideAmount = mappedPayrollData[forecastKey][period]
}

cells[monthlyFigure.month] = {
Expand All @@ -197,8 +195,8 @@ const processPayrollData = (payrollData) => {
const results = {};

for (const [key, value] of Object.entries(payrollData)) {
const generatedKey = makeFinancialCodeKey(value.programme_code, value.pay_element__type__group__natural_code)
const generatedKey = makeFinancialCodeKey(value.programme_code, value.natural_account_code)

results[generatedKey] = value;
}

Expand Down
20 changes: 16 additions & 4 deletions payroll/fixtures/test_payroll_data.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@
"first_name": "John",
"last_name": "Smith",
"grade": "Grade 7",
"assignment_status": "Active Assignment"
"assignment_status": "Active Assignment",
"basic_pay": 230000,
"pension": 40000,
"ernic": 9000
}
},
{
Expand All @@ -22,7 +25,10 @@
"first_name": "Jane",
"last_name": "Doe",
"grade": "Grade 7",
"assignment_status": "Active Contingent Assignment"
"assignment_status": "Active Contingent Assignment",
"basic_pay": 268000,
"pension": 60000,
"ernic": 50000
}
},
{
Expand All @@ -35,7 +41,10 @@
"first_name": "John",
"last_name": "Doe",
"grade": "Grade 7",
"assignment_status": "Loan Out - Non Payroll"
"assignment_status": "Loan Out - Non Payroll",
"basic_pay": 0,
"pension": 0,
"ernic": 0
}
},
{
Expand All @@ -48,7 +57,10 @@
"first_name": "Jane",
"last_name": "Smith",
"grade": "Grade 7",
"assignment_status": "Active Assignment"
"assignment_status": "Active Assignment",
"basic_pay": 0,
"pension": 0,
"ernic": 0
}
},
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Generated by Django 4.2.16 on 2024-11-27 14:59

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("payroll", "0014_alter_vacancypayperiods_vacancy"),
]

operations = [
migrations.AddField(
model_name="employee",
name="basic_pay",
field=models.BigIntegerField(db_comment="pence", default=0),
),
migrations.AddField(
model_name="employee",
name="ernic",
field=models.BigIntegerField(db_comment="pence", default=0),
),
migrations.AddField(
model_name="employee",
name="pension",
field=models.BigIntegerField(db_comment="pence", default=0),
),
]
66 changes: 37 additions & 29 deletions payroll/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ def with_basic_pay(self):
)
)

def payroll(self):
return self.filter(basic_pay__gt=0)


class Position(models.Model):
class Meta:
Expand Down Expand Up @@ -87,6 +90,9 @@ class Employee(Position):
first_name = models.CharField(max_length=32)
last_name = models.CharField(max_length=32)
assignment_status = models.CharField(max_length=32)
basic_pay = models.BigIntegerField(default=0, db_comment="pence")
pension = models.BigIntegerField(default=0, db_comment="pence")
ernic = models.BigIntegerField(default=0, db_comment="pence")

# TODO: Missing fields from Admin Tool which aren't required yet.
# EU/Non-EU (from programme code model)
Expand Down Expand Up @@ -148,44 +154,46 @@ class EmployeePayElement(models.Model):
credit_amount = models.DecimalField(max_digits=9, decimal_places=2)


class RecruitmentType(models.TextChoices):
EXPRESSION_OF_INTEREST = "expression_of_interest", "Expression of Interest"
EXTERNAL_RECRUITMENT_NON_BULK = (
"external_recruitment_non_bulk",
"External Recruitment (Non Bulk)",
)
EXTERNAL_RECRUITMENT_BULK = (
"external_recruitment_bulk",
"External Recruitment (Bulk campaign)",
)
INTERNAL_MANAGED_MOVE = "internal_managed_move", "Internal Managed Move"
INTERNAL_REDEPLOYMENT = "internal_redeployment", "Internal Redeployment"
OTHER = "other", "Other"
INACTIVE_POST = "inactive_post", "Inactive Post"
EXPECTED_UNKNOWN_LEAVERS = "expected_unknown_leavers", "Expected Unknown Leavers"
MISSING_STAFF = "missing_staff", "Missing Staff"


class RecruitmentStage(models.IntegerChoices):
PREPARING = 1, "Preparing"
ADVERT = 2, "Advert (Vac ref to be provided)"
SIFT = 3, "Sift"
INTERVIEW = 4, "Interview"
ONBOARDING = 5, "Onboarding"
UNSUCCESSFUL_RECRUITMENT = 6, "Unsuccessful recruitment"
NOT_YET_ADVERTISED = 7, "Not (yet) advertised"
NOT_REQUIRED = 8, "Not required"


class Vacancy(Position):
class Meta:
verbose_name_plural = "Vacancies"

class RecruitmentType(models.TextChoices):
EXPRESSION_OF_INTEREST = "expression_of_interest", "Expression of Interest"
EXTERNAL_RECRUITMENT_NON_BULK = (
"external_recruitment_non_bulk",
"External Recruitment (Non Bulk)",
)
EXTERNAL_RECRUITMENT_BULK = (
"external_recruitment_bulk",
"External Recruitment (Bulk campaign)",
)
INTERNAL_MANAGED_MOVE = "internal_managed_move", "Internal Managed Move"
INTERNAL_REDEPLOYMENT = "internal_redeployment", "Internal Redeployment"
OTHER = "other", "Other"
INACTIVE_POST = "inactive_post", "Inactive Post"
EXPECTED_UNKNOWN_LEAVERS = (
"expected_unknown_leavers",
"Expected Unknown Leavers",
)
MISSING_STAFF = "missing_staff", "Missing Staff"

recruitment_type = models.CharField(
max_length=29,
choices=RecruitmentType.choices,
default=RecruitmentType.EXPRESSION_OF_INTEREST,
)

class RecruitmentStage(models.IntegerChoices):
PREPARING = 1, "Preparing"
ADVERT = 2, "Advert (Vac ref to be provided)"
SIFT = 3, "Sift"
INTERVIEW = 4, "Interview"
ONBOARDING = 5, "Onboarding"
UNSUCCESSFUL_RECRUITMENT = 6, "Unsuccessful recruitment"
NOT_YET_ADVERTISED = 7, "Not (yet) advertised"
NOT_REQUIRED = 8, "Not required"

recruitment_stage = models.IntegerField(
choices=RecruitmentStage.choices, default=RecruitmentStage.PREPARING
)
Expand Down
Loading

0 comments on commit c390d06

Please sign in to comment.