diff --git a/front_end/src/Apps/Payroll.jsx b/front_end/src/Apps/Payroll.jsx
index 273fa520..7da61e60 100644
--- a/front_end/src/Apps/Payroll.jsx
+++ b/front_end/src/Apps/Payroll.jsx
@@ -1,19 +1,12 @@
-import { useEffect, useReducer, useState, useMemo } from "react";
+import { useEffect, useReducer, useState } from "react";
import EditPayroll from "../Components/EditPayroll";
import * as api from "../Components/EditPayroll/api";
-import {
- payrollHeaders,
- vacancyHeaders,
-} from "../Components/EditPayroll/constants";
const initialPayrollState = [];
export default function Payroll() {
- const [allPayroll, dispatch] = useReducer(
- payrollReducer,
- initialPayrollState
- );
+ const [payroll, dispatch] = useReducer(payrollReducer, initialPayrollState);
const [saveSuccess, setSaveSuccess] = useState(false);
useEffect(() => {
@@ -26,20 +19,10 @@ export default function Payroll() {
api.getPayrollData().then((data) => dispatch({ type: "fetched", data }));
}, []);
- // Computed properties
- const payroll = useMemo(
- () => allPayroll.filter((payroll) => payroll.basic_pay > 0),
- [allPayroll]
- );
- const nonPayroll = useMemo(
- () => allPayroll.filter((payroll) => payroll.basic_pay <= 0),
- [allPayroll]
- );
-
// Handlers
async function handleSavePayroll() {
try {
- await api.postPayrollData(allPayroll);
+ await api.postPayrollData(payroll);
setSaveSuccess(true);
localStorage.setItem("saveSuccess", "true");
@@ -55,41 +38,12 @@ export default function Payroll() {
}
return (
- <>
- {saveSuccess && (
-
{row.name} |
- {row.grade} |
{row.employee_no} |
- {row.fte} |
- {row.programme_code} |
- {row.budget_type} |
- {row.assignment_status} |
{row.pay_periods.map((enabled, index) => {
return (
diff --git a/front_end/src/Components/EditPayroll/constants.js b/front_end/src/Components/EditPayroll/constants.js
deleted file mode 100644
index a2a362f2..00000000
--- a/front_end/src/Components/EditPayroll/constants.js
+++ /dev/null
@@ -1,35 +0,0 @@
-const monthHeaders = [
- "Apr",
- "May",
- "Jun",
- "Jul",
- "Aug",
- "Sep",
- "Oct",
- "Nov",
- "Dec",
- "Jan",
- "Feb",
- "Mar",
-];
-
-export const payrollHeaders = [
- "Name",
- "Grade",
- "Employee No",
- "FTE",
- "Programme Code",
- "Budget Type",
- "Assignment Status",
-].concat(monthHeaders);
-
-export const vacancyHeaders = [
- "Recruitment Type",
- "Grade",
- "Programme",
- "Budget Type",
- "Appointee Name",
- "Hiring Manager",
- "HR Ref",
- "Recruitment Stage",
-].concat(monthHeaders);
diff --git a/front_end/src/Components/EditPayroll/index.jsx b/front_end/src/Components/EditPayroll/index.jsx
index e87b0f8c..f930ff0d 100644
--- a/front_end/src/Components/EditPayroll/index.jsx
+++ b/front_end/src/Components/EditPayroll/index.jsx
@@ -7,12 +7,50 @@ import PayrollTable from "./PayrollTable/index";
* @param {types.PayrollData[]} props.payroll
* @returns
*/
-export default function EditPayroll({ payroll, headers, onTogglePayPeriods }) {
+export default function EditPayroll({
+ payroll,
+ onSavePayroll,
+ onTogglePayPeriods,
+ saveSuccess,
+}) {
+ const headers = [
+ "Name",
+ "Employee No",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+ "Jan",
+ "Feb",
+ "Mar",
+ ];
return (
-
+ <>
+ {saveSuccess && (
+
+ )}
+
+
+ >
);
}
diff --git a/front_end/src/Components/EditPayroll/types.js b/front_end/src/Components/EditPayroll/types.js
index 387c4836..13c2fada 100644
--- a/front_end/src/Components/EditPayroll/types.js
+++ b/front_end/src/Components/EditPayroll/types.js
@@ -1,13 +1,7 @@
/**
* @typedef {Object} PayrollData
* @property {string} name - The employee's name.
- * @property {string} grade - The employee's grade.
* @property {string} employee_no - The employee's number.
- * @property {number} fte - The employee's FTE.
- * @property {string} programme_code - The employee's programme code.
- * @property {string} budget_type - The employee's programme code budget type.
- * @property {string} assignment_status - The employee's assignment status.
- * @property {string} basic_pay - The employee's basic pay.
* @property {boolean[]} pay_periods - Whether the employee is being paid in periods.
*/
diff --git a/makefile b/makefile
index 7afd9ff8..0ef14953 100644
--- a/makefile
+++ b/makefile
@@ -41,13 +41,13 @@ create-stub-data: # Create stub data for testing
$(web) $(manage) create_stub_forecast_data
$(web) $(manage) create_stub_future_forecast_data
$(web) $(manage) create_data_lake_stub_data
- $(web) $(manage) populate_gift_hospitality_table
$(web) $(manage) loaddata test_payroll_data
$(web) $(manage) create_test_user --password=password
setup: # Set up the project from scratch
make down
make create-stub-data
+ make gift-hospitality-table
$(web) $(manage) create_test_user --password=password
$(web) $(manage) create_test_user --email=finance-admin@test.com --group="Finance Administrator" --password=password # /PS-IGNORE
$(web) $(manage) create_test_user --email=finance-bp@test.com --group="Finance Business Partner/BSCE" --password=password # /PS-IGNORE
diff --git a/payroll/admin.py b/payroll/admin.py
index 057b0e84..c761e0c8 100644
--- a/payroll/admin.py
+++ b/payroll/admin.py
@@ -1,6 +1,6 @@
from django.contrib import admin
-from payroll.services.payroll import employee_created, vacancy_created
+from payroll.services.payroll import employee_created
from .models import (
Employee,
@@ -8,7 +8,6 @@
EmployeePayPeriods,
PayElementType,
PayElementTypeGroup,
- Vacancy,
)
@@ -76,21 +75,3 @@ class PayElementTypeGroupAdmin(admin.ModelAdmin):
"name",
"natural_code",
]
-
-
-@admin.register(Vacancy)
-class VacancyAdmin(admin.ModelAdmin):
- list_display = [
- "cost_centre",
- "grade",
- "programme_code",
- "appointee_name",
- "hiring_manager",
- "hr_ref",
- ]
-
- def save_model(self, request, obj, form, change):
- super().save_model(request, obj, form, change)
-
- if not change:
- vacancy_created(obj)
diff --git a/payroll/fixtures/test_payroll_data.json b/payroll/fixtures/test_payroll_data.json
index 3c75997c..278efd6f 100644
--- a/payroll/fixtures/test_payroll_data.json
+++ b/payroll/fixtures/test_payroll_data.json
@@ -5,11 +5,8 @@
"fields": {
"cost_centre": "888812",
"employee_no": "00000001",
- "programme_code": "338887",
"first_name": "John",
- "last_name": "Smith",
- "grade": "Grade 7",
- "assignment_status": "Active Assignment"
+ "last_name": "Smith"
}
},
{
@@ -18,37 +15,8 @@
"fields": {
"cost_centre": "888812",
"employee_no": "00000002",
- "programme_code": "338887",
"first_name": "Jane",
- "last_name": "Doe",
- "grade": "Grade 7",
- "assignment_status": "Active Contingent Assignment"
- }
- },
- {
- "model": "payroll.employee",
- "pk": 3,
- "fields": {
- "cost_centre": "888812",
- "employee_no": "00000003",
- "programme_code": "338887",
- "first_name": "John",
- "last_name": "Doe",
- "grade": "Grade 7",
- "assignment_status": "Loan Out - Non Payroll"
- }
- },
- {
- "model": "payroll.employee",
- "pk": 4,
- "fields": {
- "cost_centre": "888812",
- "employee_no": "00000004",
- "programme_code": "338887",
- "first_name": "Jane",
- "last_name": "Smith",
- "grade": "Grade 7",
- "assignment_status": "Active Assignment"
+ "last_name": "Doe"
}
},
{
@@ -211,166 +179,6 @@
"period_12": true
}
},
- {
- "model": "payroll.employeepayperiods",
- "pk": 9,
- "fields": {
- "employee": 3,
- "year": 2024,
- "period_1": false,
- "period_2": false,
- "period_3": false,
- "period_4": false,
- "period_5": false,
- "period_6": false,
- "period_7": false,
- "period_8": false,
- "period_9": false,
- "period_10": false,
- "period_11": false,
- "period_12": false
- }
- },
- {
- "model": "payroll.employeepayperiods",
- "pk": 10,
- "fields": {
- "employee": 3,
- "year": 2025,
- "period_1": false,
- "period_2": false,
- "period_3": false,
- "period_4": false,
- "period_5": false,
- "period_6": false,
- "period_7": false,
- "period_8": false,
- "period_9": false,
- "period_10": false,
- "period_11": false,
- "period_12": false
- }
- },
- {
- "model": "payroll.employeepayperiods",
- "pk": 11,
- "fields": {
- "employee": 3,
- "year": 2026,
- "period_1": false,
- "period_2": false,
- "period_3": false,
- "period_4": false,
- "period_5": false,
- "period_6": false,
- "period_7": false,
- "period_8": false,
- "period_9": false,
- "period_10": false,
- "period_11": false,
- "period_12": false
- }
- },
- {
- "model": "payroll.employeepayperiods",
- "pk": 12,
- "fields": {
- "employee": 3,
- "year": 2027,
- "period_1": false,
- "period_2": false,
- "period_3": false,
- "period_4": false,
- "period_5": false,
- "period_6": false,
- "period_7": false,
- "period_8": false,
- "period_9": false,
- "period_10": false,
- "period_11": false,
- "period_12": false
- }
- },
- {
- "model": "payroll.employeepayperiods",
- "pk": 13,
- "fields": {
- "employee": 4,
- "year": 2024,
- "period_1": false,
- "period_2": false,
- "period_3": false,
- "period_4": false,
- "period_5": false,
- "period_6": false,
- "period_7": false,
- "period_8": false,
- "period_9": false,
- "period_10": false,
- "period_11": false,
- "period_12": false
- }
- },
- {
- "model": "payroll.employeepayperiods",
- "pk": 14,
- "fields": {
- "employee": 4,
- "year": 2025,
- "period_1": false,
- "period_2": false,
- "period_3": false,
- "period_4": false,
- "period_5": false,
- "period_6": false,
- "period_7": false,
- "period_8": false,
- "period_9": false,
- "period_10": false,
- "period_11": false,
- "period_12": false
- }
- },
- {
- "model": "payroll.employeepayperiods",
- "pk": 15,
- "fields": {
- "employee": 4,
- "year": 2026,
- "period_1": false,
- "period_2": false,
- "period_3": false,
- "period_4": false,
- "period_5": false,
- "period_6": false,
- "period_7": false,
- "period_8": false,
- "period_9": false,
- "period_10": false,
- "period_11": false,
- "period_12": false
- }
- },
- {
- "model": "payroll.employeepayperiods",
- "pk": 16,
- "fields": {
- "employee": 4,
- "year": 2027,
- "period_1": false,
- "period_2": false,
- "period_3": false,
- "period_4": false,
- "period_5": false,
- "period_6": false,
- "period_7": false,
- "period_8": false,
- "period_9": false,
- "period_10": false,
- "period_11": false,
- "period_12": false
- }
- },
{
"model": "payroll.payelementtypegroup",
"pk": 1,
diff --git a/payroll/forms.py b/payroll/forms.py
deleted file mode 100644
index 29760637..00000000
--- a/payroll/forms.py
+++ /dev/null
@@ -1,25 +0,0 @@
-from django import forms
-
-from payroll.models import Vacancy
-
-
-class VacancyForm(forms.ModelForm):
- class Meta:
- model = Vacancy
- fields = "__all__"
- exclude = ["cost_centre"]
- widgets = {
- "recruitment_type": forms.Select(attrs={"class": "govuk-select"}),
- "grade": forms.Select(attrs={"class": "govuk-select"}),
- "recruitment_stage": forms.Select(attrs={"class": "govuk-select"}),
- "programme_code": forms.Select(attrs={"class": "govuk-select"}),
- "appointee_name": forms.TextInput(
- attrs={"class": "govuk-input govuk-input--width-20"}
- ),
- "hiring_manager": forms.TextInput(
- attrs={"class": "govuk-input govuk-input--width-20"}
- ),
- "hr_ref": forms.TextInput(
- attrs={"class": "govuk-input govuk-input--width-20"}
- ),
- }
diff --git a/payroll/migrations/0004_vacancy.py b/payroll/migrations/0004_vacancy.py
deleted file mode 100644
index 32c92359..00000000
--- a/payroll/migrations/0004_vacancy.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# Generated by Django 4.2.16 on 2024-11-11 15:38
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- (
- "gifthospitality",
- "0006_alter_simplehistorygiftandhospitality_options_and_more",
- ),
- ("chartofaccountDIT", "0015_alter_simplehistoryanalysis1_options_and_more"),
- ("payroll", "0003_employee_programme_code"),
- ]
-
- operations = [
- migrations.CreateModel(
- name="Vacancy",
- fields=[
- (
- "id",
- models.BigAutoField(
- auto_created=True,
- primary_key=True,
- serialize=False,
- verbose_name="ID",
- ),
- ),
- (
- "programme_switch_vacancy",
- models.CharField(
- choices=[("PS", "Programme Switch"), ("V", "Vacancy")],
- max_length=2,
- verbose_name="Programme switch / Vacancy",
- ),
- ),
- (
- "appointee_name",
- models.CharField(blank=True, max_length=255, null=True),
- ),
- (
- "hiring_manager",
- models.CharField(blank=True, max_length=255, null=True),
- ),
- ("hr_ref", models.CharField(blank=True, max_length=255, null=True)),
- (
- "grade",
- models.ForeignKey(
- on_delete=django.db.models.deletion.PROTECT,
- to="gifthospitality.grade",
- ),
- ),
- (
- "programme_code",
- models.ForeignKey(
- on_delete=django.db.models.deletion.PROTECT,
- to="chartofaccountDIT.programmecode",
- ),
- ),
- ],
- ),
- ]
diff --git a/payroll/migrations/0005_alter_vacancy_options.py b/payroll/migrations/0005_alter_vacancy_options.py
deleted file mode 100644
index 50cbc1df..00000000
--- a/payroll/migrations/0005_alter_vacancy_options.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Generated by Django 4.2.16 on 2024-11-12 11:56
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("payroll", "0004_vacancy"),
- ]
-
- operations = [
- migrations.AlterModelOptions(
- name="vacancy",
- options={"verbose_name_plural": "Vacancies"},
- ),
- ]
diff --git a/payroll/migrations/0006_alter_vacancy_programme_switch_vacancy.py b/payroll/migrations/0006_alter_vacancy_programme_switch_vacancy.py
deleted file mode 100644
index 672aa3ea..00000000
--- a/payroll/migrations/0006_alter_vacancy_programme_switch_vacancy.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Generated by Django 4.2.16 on 2024-11-12 15:12
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("payroll", "0005_alter_vacancy_options"),
- ]
-
- operations = [
- migrations.AlterField(
- model_name="vacancy",
- name="programme_switch_vacancy",
- field=models.CharField(
- choices=[
- ("programme_switch", "Programme Switch"),
- ("vacancy", "Vacancy"),
- ],
- max_length=16,
- verbose_name="Programme switch / Vacancy",
- ),
- ),
- ]
diff --git a/payroll/migrations/0007_vacancy_cost_centre.py b/payroll/migrations/0007_vacancy_cost_centre.py
deleted file mode 100644
index f9ab69ab..00000000
--- a/payroll/migrations/0007_vacancy_cost_centre.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# Generated by Django 4.2.16 on 2024-11-13 13:44
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("costcentre", "0008_alter_simplehistoryarchivedcostcentre_options_and_more"),
- ("payroll", "0006_alter_vacancy_programme_switch_vacancy"),
- ]
-
- operations = [
- migrations.AddField(
- model_name="vacancy",
- name="cost_centre",
- field=models.ForeignKey(
- default="888812",
- on_delete=django.db.models.deletion.PROTECT,
- to="costcentre.costcentre",
- ),
- preserve_default=False,
- ),
- ]
diff --git a/payroll/migrations/0008_vacancy_recruitment_stage_vacancy_recruitment_type.py b/payroll/migrations/0008_vacancy_recruitment_stage_vacancy_recruitment_type.py
deleted file mode 100644
index e2768cde..00000000
--- a/payroll/migrations/0008_vacancy_recruitment_stage_vacancy_recruitment_type.py
+++ /dev/null
@@ -1,55 +0,0 @@
-# Generated by Django 4.2.16 on 2024-11-13 16:11
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("payroll", "0007_vacancy_cost_centre"),
- ]
-
- operations = [
- migrations.AddField(
- model_name="vacancy",
- name="recruitment_stage",
- field=models.IntegerField(
- choices=[
- (1, "Preparing"),
- (2, "Advert (Vac ref to be provided)"),
- (3, "Sift"),
- (4, "Interview"),
- (5, "Onboarding"),
- (6, "Unsuccessful recruitment"),
- (7, "Not (yet) advertised"),
- (8, "Not required"),
- ],
- default=1,
- ),
- ),
- migrations.AddField(
- model_name="vacancy",
- name="recruitment_type",
- field=models.CharField(
- choices=[
- ("expression_of_interest", "Expression of Interest"),
- (
- "external_recruitment_non_bulk",
- "External Recruitment (Non Bulk)",
- ),
- (
- "external_recruitment_bulk",
- "External Recruitment (Bulk campaign)",
- ),
- ("internal_managed_move", "Internal Managed Move"),
- ("internal_redeployment", "Internal Redeployment"),
- ("other", "Other"),
- ("inactive_post", "Inactive Post"),
- ("expected_unknown_leavers", "Expected Unknown Leavers"),
- ("missing_staff", "Missing Staff"),
- ],
- default="expression_of_interest",
- max_length=29,
- ),
- ),
- ]
diff --git a/payroll/migrations/0009_remove_vacancy_programme_switch_vacancy.py b/payroll/migrations/0009_remove_vacancy_programme_switch_vacancy.py
deleted file mode 100644
index 69e87876..00000000
--- a/payroll/migrations/0009_remove_vacancy_programme_switch_vacancy.py
+++ /dev/null
@@ -1,17 +0,0 @@
-# Generated by Django 4.2.16 on 2024-11-13 16:13
-
-from django.db import migrations
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("payroll", "0008_vacancy_recruitment_stage_vacancy_recruitment_type"),
- ]
-
- operations = [
- migrations.RemoveField(
- model_name="vacancy",
- name="programme_switch_vacancy",
- ),
- ]
diff --git a/payroll/migrations/0010_alter_vacancy_appointee_name_and_more.py b/payroll/migrations/0010_alter_vacancy_appointee_name_and_more.py
deleted file mode 100644
index 23dd74ba..00000000
--- a/payroll/migrations/0010_alter_vacancy_appointee_name_and_more.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Generated by Django 4.2.16 on 2024-11-18 12:07
-
-import django.core.validators
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("payroll", "0009_remove_vacancy_programme_switch_vacancy"),
- ]
-
- operations = [
- migrations.AlterField(
- model_name="vacancy",
- name="appointee_name",
- field=models.CharField(
- blank=True,
- max_length=255,
- null=True,
- validators=[
- django.core.validators.RegexValidator(
- message="Only letters, spaces, - and ' are allowed",
- regex="^[a-zA-Z '-]*$",
- )
- ],
- ),
- ),
- migrations.AlterField(
- model_name="vacancy",
- name="hiring_manager",
- field=models.CharField(
- blank=True,
- max_length=255,
- null=True,
- validators=[
- django.core.validators.RegexValidator(
- message="Only letters, spaces, - and ' are allowed",
- regex="^[a-zA-Z '-]*$",
- )
- ],
- ),
- ),
- migrations.AlterField(
- model_name="vacancy",
- name="hr_ref",
- field=models.CharField(
- blank=True,
- max_length=255,
- null=True,
- validators=[
- django.core.validators.RegexValidator(
- message="Only letters, spaces, - and ' are allowed",
- regex="^[a-zA-Z '-]*$",
- )
- ],
- ),
- ),
- ]
diff --git a/payroll/migrations/0011_alter_vacancy_appointee_name_and_more.py b/payroll/migrations/0011_alter_vacancy_appointee_name_and_more.py
deleted file mode 100644
index 61a1897a..00000000
--- a/payroll/migrations/0011_alter_vacancy_appointee_name_and_more.py
+++ /dev/null
@@ -1,59 +0,0 @@
-# Generated by Django 4.2.16 on 2024-11-18 13:52
-
-import django.core.validators
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ("payroll", "0010_alter_vacancy_appointee_name_and_more"),
- ]
-
- operations = [
- migrations.AlterField(
- model_name="vacancy",
- name="appointee_name",
- field=models.CharField(
- blank=True,
- max_length=255,
- null=True,
- validators=[
- django.core.validators.RegexValidator(
- message="Only letters, spaces, - and ' are allowed.",
- regex="^[a-zA-Z '-]*$",
- )
- ],
- ),
- ),
- migrations.AlterField(
- model_name="vacancy",
- name="hiring_manager",
- field=models.CharField(
- blank=True,
- max_length=255,
- null=True,
- validators=[
- django.core.validators.RegexValidator(
- message="Only letters, spaces, - and ' are allowed.",
- regex="^[a-zA-Z '-]*$",
- )
- ],
- ),
- ),
- migrations.AlterField(
- model_name="vacancy",
- name="hr_ref",
- field=models.CharField(
- blank=True,
- max_length=255,
- null=True,
- validators=[
- django.core.validators.RegexValidator(
- message="Only letters, spaces, - and ' are allowed.",
- regex="^[a-zA-Z '-]*$",
- )
- ],
- ),
- ),
- ]
diff --git a/payroll/migrations/0012_employee_assignment_status_employee_fte_and_more.py b/payroll/migrations/0012_employee_assignment_status_employee_fte_and_more.py
deleted file mode 100644
index 4a224034..00000000
--- a/payroll/migrations/0012_employee_assignment_status_employee_fte_and_more.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Generated by Django 4.2.16 on 2024-11-12 15:52
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- (
- "gifthospitality",
- "0006_alter_simplehistorygiftandhospitality_options_and_more",
- ),
- ("payroll", "0011_alter_vacancy_appointee_name_and_more"),
- ]
-
- operations = [
- migrations.AddField(
- model_name="employee",
- name="assignment_status",
- field=models.CharField(default="Active Assignment", max_length=32),
- preserve_default=False,
- ),
- migrations.AddField(
- model_name="employee",
- name="fte",
- field=models.FloatField(default=1.0),
- ),
- migrations.AddField(
- model_name="employee",
- name="grade",
- field=models.ForeignKey(
- blank=True,
- null=True,
- on_delete=django.db.models.deletion.PROTECT,
- to="gifthospitality.grade",
- ),
- ),
- ]
diff --git a/payroll/migrations/0013_vacancy_fte_alter_vacancy_grade_vacancypayperiods_and_more.py b/payroll/migrations/0013_vacancy_fte_alter_vacancy_grade_vacancypayperiods_and_more.py
deleted file mode 100644
index a4c8dd21..00000000
--- a/payroll/migrations/0013_vacancy_fte_alter_vacancy_grade_vacancypayperiods_and_more.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# Generated by Django 4.2.16 on 2024-11-19 16:21
-
-import django.db.models.deletion
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- (
- "gifthospitality",
- "0006_alter_simplehistorygiftandhospitality_options_and_more",
- ),
- ("core", "0013_alter_historicalgroup_options_and_more"),
- ("payroll", "0012_employee_assignment_status_employee_fte_and_more"),
- ]
-
- operations = [
- migrations.AddField(
- model_name="vacancy",
- name="fte",
- field=models.FloatField(default=1.0),
- ),
- migrations.AlterField(
- model_name="vacancy",
- name="grade",
- field=models.ForeignKey(
- blank=True,
- null=True,
- on_delete=django.db.models.deletion.PROTECT,
- to="gifthospitality.grade",
- ),
- ),
- migrations.CreateModel(
- name="VacancyPayPeriods",
- fields=[
- (
- "id",
- models.BigAutoField(
- auto_created=True,
- primary_key=True,
- serialize=False,
- verbose_name="ID",
- ),
- ),
- ("period_1", models.BooleanField(default=True)),
- ("period_2", models.BooleanField(default=True)),
- ("period_3", models.BooleanField(default=True)),
- ("period_4", models.BooleanField(default=True)),
- ("period_5", models.BooleanField(default=True)),
- ("period_6", models.BooleanField(default=True)),
- ("period_7", models.BooleanField(default=True)),
- ("period_8", models.BooleanField(default=True)),
- ("period_9", models.BooleanField(default=True)),
- ("period_10", models.BooleanField(default=True)),
- ("period_11", models.BooleanField(default=True)),
- ("period_12", models.BooleanField(default=True)),
- (
- "vacancy",
- models.ForeignKey(
- on_delete=django.db.models.deletion.PROTECT,
- related_name="pay_periods",
- to="payroll.vacancy",
- ),
- ),
- (
- "year",
- models.ForeignKey(
- on_delete=django.db.models.deletion.PROTECT,
- to="core.financialyear",
- ),
- ),
- ],
- options={
- "verbose_name_plural": "vacancy pay periods",
- },
- ),
- migrations.AddConstraint(
- model_name="vacancypayperiods",
- constraint=models.UniqueConstraint(
- fields=("vacancy", "year"), name="unique_vacancy_pay_periods"
- ),
- ),
- ]
diff --git a/payroll/models.py b/payroll/models.py
index ff1ab681..d7186f08 100644
--- a/payroll/models.py
+++ b/payroll/models.py
@@ -1,51 +1,45 @@
-from django.core.validators import RegexValidator
from django.db import models
-from django.db.models import F, Q, Sum
-
-
-class EmployeeQuerySet(models.QuerySet):
- def with_basic_pay(self):
- return self.annotate(
- basic_pay=Sum(
- F("pay_element__debit_amount") - F("pay_element__credit_amount"),
- # TODO (FFT-107): Resolve hard-coded references to "Basic Pay"
- # This might change when we get round to ingesting the data, so I'm OK
- # with it staying like this for now.
- filter=Q(pay_element__type__group__name="Basic Pay"),
- default=0,
- output_field=models.FloatField(),
- )
- )
-
-class Position(models.Model):
- class Meta:
- abstract = True
+class Employee(models.Model):
cost_centre = models.ForeignKey(
"costcentre.CostCentre",
models.PROTECT,
)
+ # I've been informed that an employee should only be associated to a single
+ # programme code. However, programme codes are actually assigned on a per pay
+ # element basis and in some cases an employee can be associated to multiple. This is
+ # seen as an edge case and we want to model it such that an employee only has a
+ # single programme code. We will have to handle this discrepancy somewhere.
programme_code = models.ForeignKey(
"chartofaccountDIT.ProgrammeCode",
models.PROTECT,
)
- grade = models.ForeignKey(
- to="gifthospitality.Grade",
- on_delete=models.PROTECT,
- null=True,
- blank=True,
- )
- fte = models.FloatField(default=1.0)
+ employee_no = models.CharField(max_length=8, unique=True)
+ first_name = models.CharField(max_length=32)
+ last_name = models.CharField(max_length=32)
+
+ def __str__(self) -> str:
+ return f"{self.employee_no} - {self.first_name} {self.last_name}"
+ def get_full_name(self) -> str:
+ return f"{self.first_name} {self.last_name}"
-class PositionPayPeriods(models.Model):
+
+class EmployeePayPeriods(models.Model):
class Meta:
- abstract = True
+ verbose_name_plural = "employee pay periods"
+ constraints = [
+ models.UniqueConstraint(
+ fields=("employee", "year"),
+ name="unique_employee_pay_periods",
+ )
+ ]
+ employee = models.ForeignKey(Employee, models.PROTECT, related_name="pay_periods")
year = models.ForeignKey("core.FinancialYear", models.PROTECT)
# period 1 = apr, period 2 = may, etc...
- # period 1 -> 12 = apr -> mar
+ # pariod 1 -> 12 = apr -> mar
# Here is a useful text snippet:
# apr period_1
# may period_2
@@ -82,42 +76,6 @@ def periods(self, value: list[bool]) -> None:
setattr(self, f"period_{i + 1}", enabled)
-class Employee(Position):
- employee_no = models.CharField(max_length=8, unique=True)
- first_name = models.CharField(max_length=32)
- last_name = models.CharField(max_length=32)
- assignment_status = models.CharField(max_length=32)
-
- # TODO: Missing fields from Admin Tool which aren't required yet.
- # EU/Non-EU (from programme code model)
-
- objects = EmployeeQuerySet.as_manager()
-
- def __str__(self) -> str:
- return f"{self.employee_no} - {self.first_name} {self.last_name}"
-
- def get_full_name(self) -> str:
- return f"{self.first_name} {self.last_name}"
-
-
-class EmployeePayPeriods(PositionPayPeriods):
- class Meta:
- verbose_name_plural = "employee pay periods"
- constraints = [
- models.UniqueConstraint(
- fields=("employee", "year"),
- name="unique_employee_pay_periods",
- )
- ]
-
- employee = models.ForeignKey(Employee, models.PROTECT, related_name="pay_periods")
-
- # TODO: Missing fields from Admin Tool which aren't required yet.
- # capital (Real colour of money)
- # recharge = models.CharField(max_length=50, null=True, blank=True)
- # recharge_reason = models.CharField(max_length=100, null=True, blank=True)
-
-
# aka "ToolTypePayment"
class PayElementTypeGroup(models.Model):
name = models.CharField(max_length=32, unique=True)
@@ -146,93 +104,3 @@ class EmployeePayElement(models.Model):
debit_amount = models.DecimalField(max_digits=9, decimal_places=2)
# Support up to 9,999,999.99.
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"
-
- recruitment_type = models.CharField(
- max_length=29,
- choices=RecruitmentType.choices,
- default=RecruitmentType.EXPRESSION_OF_INTEREST,
- )
- recruitment_stage = models.IntegerField(
- choices=RecruitmentStage.choices, default=RecruitmentStage.PREPARING
- )
-
- appointee_name = models.CharField(
- max_length=255,
- null=True,
- blank=True,
- validators=[
- RegexValidator(
- regex=r"^[a-zA-Z '-]*$",
- message="Only letters, spaces, - and ' are allowed.",
- )
- ],
- )
- hiring_manager = models.CharField(
- max_length=255,
- null=True,
- blank=True,
- validators=[
- RegexValidator(
- regex=r"^[a-zA-Z '-]*$",
- message="Only letters, spaces, - and ' are allowed.",
- )
- ],
- )
- hr_ref = models.CharField(
- max_length=255,
- null=True,
- blank=True,
- validators=[
- RegexValidator(
- regex=r"^[a-zA-Z '-]*$",
- message="Only letters, spaces, - and ' are allowed.",
- )
- ],
- )
-
-
-class VacancyPayPeriods(PositionPayPeriods):
- class Meta:
- verbose_name_plural = "vacancy pay periods"
- constraints = [
- models.UniqueConstraint(
- fields=("vacancy", "year"),
- name="unique_vacancy_pay_periods",
- )
- ]
-
- vacancy = models.ForeignKey(Vacancy, models.PROTECT, related_name="pay_periods")
diff --git a/payroll/services/payroll.py b/payroll/services/payroll.py
index 4d49683e..c7b8c6ea 100644
--- a/payroll/services/payroll.py
+++ b/payroll/services/payroll.py
@@ -7,43 +7,25 @@
from core.models import FinancialYear
from costcentre.models import CostCentre
-from ..models import Employee, EmployeePayPeriods, Vacancy, VacancyPayPeriods
+from ..models import Employee, EmployeePayPeriods
def employee_created(employee: Employee) -> None:
"""Hook to be called after an employee instance is created."""
- # Create EmployeePayPeriods records for current and future financial years.
- create_pay_periods(employee)
- return None
+ # Create EmployeePayPeriods records for current and future financial years.
+ create_employee_pay_periods(employee)
-def vacancy_created(vacancy: Vacancy) -> None:
- """Hook to be called after a vacancy instance is created."""
- # Create VacancyPayPeriods records for current and future financial years.
- create_pay_periods(vacancy)
return None
-def create_pay_periods(instance) -> None:
+def create_employee_pay_periods(employee: Employee) -> None:
current_financial_year = FinancialYear.objects.current()
future_financial_years = FinancialYear.objects.future()
financial_years = [current_financial_year] + list(future_financial_years)
- pay_periods_model, field_name = None
-
- if isinstance(instance, Employee):
- pay_periods_model = EmployeePayPeriods
- field_name = "employee"
- elif isinstance(instance, Vacancy):
- pay_periods_model = VacancyPayPeriods
- field_name = "vacancy"
- else:
- raise ValueError("Unsupported instance type for creating pay periods")
-
for financial_year in financial_years:
- pay_periods_model.objects.get_or_create(
- **{field_name: instance, "year": financial_year}
- )
+ EmployeePayPeriods.objects.get_or_create(employee=employee, year=financial_year)
def payroll_forecast_report(cost_centre: CostCentre, financial_year: FinancialYear):
@@ -60,7 +42,6 @@ def payroll_forecast_report(cost_centre: CostCentre, financial_year: FinancialYe
Employee.objects.filter(
cost_centre=cost_centre,
pay_periods__year=financial_year,
- pay_element__isnull=False,
)
.order_by(
"programme_code",
@@ -80,13 +61,7 @@ def payroll_forecast_report(cost_centre: CostCentre, financial_year: FinancialYe
class EmployeePayroll(TypedDict):
name: str
- grade: str
employee_no: str
- fte: float
- programme_code: str
- budget_type: str
- assignment_status: str
- basic_pay: float
pay_periods: list[bool]
@@ -94,31 +69,16 @@ def get_payroll_data(
cost_centre: CostCentre,
financial_year: FinancialYear,
) -> Iterator[EmployeePayroll]:
- qs = (
- Employee.objects.select_related(
- "programme_code__budget_type",
- )
- .prefetch_related(
- "pay_periods",
- )
- .filter(
- cost_centre=cost_centre,
- pay_periods__year=financial_year,
- )
- .with_basic_pay()
+ qs = EmployeePayPeriods.objects.select_related("employee")
+ qs = qs.filter(
+ employee__cost_centre=cost_centre,
+ year=financial_year,
)
for obj in qs:
yield EmployeePayroll(
- name=obj.get_full_name(),
- grade=obj.grade.pk,
- employee_no=obj.employee_no,
- fte=obj.fte,
- programme_code=obj.programme_code.pk,
- budget_type=obj.programme_code.budget_type.budget_type_display,
- assignment_status=obj.assignment_status,
- basic_pay=obj.basic_pay,
- # `first` is OK as there should only be one `pay_periods` with the filters.
- pay_periods=obj.pay_periods.first().periods,
+ name=obj.employee.get_full_name(),
+ employee_no=obj.employee.employee_no,
+ pay_periods=obj.periods,
)
@@ -155,78 +115,3 @@ def update_payroll_data(
)
pay_periods.periods = payroll["pay_periods"]
pay_periods.save()
-
-
-class Vacancies(TypedDict):
- id: str
- grade: str
- programme_code: str
- recruitment_type: str
- recruitment_stage: str
- appointee_name: str
- hiring_manager: str
- hr_ref: str
- pay_periods: list[bool] # Needs to be added to model
-
-
-def get_vacancies_data(
- cost_centre: CostCentre,
- financial_year: FinancialYear,
-) -> Iterator[Vacancies]:
- qs = (
- Vacancy.objects.filter(
- cost_centre=cost_centre,
- pay_periods__year=financial_year,
- )
- # .prefetch_related(
- # "pay_periods",
- # )
- )
- for obj in qs:
- yield Vacancies(
- grade=obj.grade.pk,
- programme_code=obj.programme_code.pk,
- recruitment_type=obj.get_recruitment_type_display,
- recruitment_stage=obj.get_recruitment_stage_display,
- appointee_name=obj.appointee_name,
- hiring_manager=obj.hiring_manager,
- hr_ref=obj.hr_ref,
- # `first` is OK as there should only be one `pay_periods` with the filters.
- # pay_periods=obj.pay_periods.first().periods,
- )
-
-
-@transaction.atomic
-def update_vacancies_data(
- cost_centre: CostCentre,
- financial_year: FinancialYear,
- vacancies_data: list[Vacancies],
-) -> None:
- """Update a cost centre vacancies for a given year using the provided list.
-
- This function is wrapped with a transaction, so if any of the vacancy updates fail,
- the whole batch will be rolled back.
-
- Raises:
- ValueError: If a vacancy id is empty.
- ValueError: If there are not 12 items in the pay_periods list.
- ValueError: If any of the pay_periods are not of type bool.
- """
-
- for vacancy in vacancies_data:
- if not vacancy["id"]:
- raise ValueError("id is empty")
-
- if len(vacancy["pay_periods"]) != 12:
- raise ValueError("pay_periods list should be of length 12")
-
- if not all(isinstance(x, bool) for x in vacancy["pay_periods"]):
- raise ValueError("pay_periods items should be of type bool")
-
- pay_periods = EmployeePayPeriods.objects.get(
- vacancy__id=vacancy["id"],
- vacancy__cost_centre=cost_centre,
- year=financial_year,
- )
- pay_periods.periods = vacancy["pay_periods"]
- pay_periods.save()
diff --git a/payroll/templates/payroll/page/add_vacancy.html b/payroll/templates/payroll/page/add_vacancy.html
deleted file mode 100644
index e824fa8e..00000000
--- a/payroll/templates/payroll/page/add_vacancy.html
+++ /dev/null
@@ -1,33 +0,0 @@
-{% extends "base_generic.html" %}
-{% load breadcrumbs %}
-
-{% block title %}Create Vacancy{% endblock title %}
-
-{% block breadcrumbs %}
- {{ block.super }}
- {% breadcrumb "Choose cost centre" "payroll:choose_cost_centre" %}
- {% breadcrumb "Edit payroll" "payroll:edit" cost_centre_code financial_year %}
- {% breadcrumb "Create Vacancy" "" %}
-{% endblock breadcrumbs %}
-
-{% block content %}
- Create Vacancy
-
- {% include "payroll/partials/_error_summary.html" with form=form %}
-
-
-
-{% endblock content %}
diff --git a/payroll/templates/payroll/page/edit_payroll.html b/payroll/templates/payroll/page/edit_payroll.html
index 30508ae3..83fdd1b7 100644
--- a/payroll/templates/payroll/page/edit_payroll.html
+++ b/payroll/templates/payroll/page/edit_payroll.html
@@ -6,15 +6,18 @@
{% block breadcrumbs %}
{{ block.super }}
{% breadcrumb "Choose cost centre" "payroll:choose_cost_centre" %}
- {% breadcrumb "Edit payroll" "" %}
+ {% breadcrumb "Edit payroll" "edit_payroll" %}
{% endblock breadcrumbs %}
{% block content %}
Edit payroll
-
- Payroll forecast
+ Forecast
+
+ This is a temporary table to demonstrate the forecast figures. Eventually these
+ figures would end up in the "Edit forecast" table.
+
@@ -48,39 +51,6 @@ Payroll forecast
{% endfor %}
-
- Vacancies
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- {% for vacancy in vacancies %}
-
- {{ vacancy.get_recruitment_type_display }} |
- {{ vacancy.grade }} |
- {{ vacancy.programme_code.programme_code }} |
- {{ vacancy.programme_code.budget_type }} |
- {{ vacancy.appointee_name|default:"" }} |
- {{ vacancy.hiring_manager|default:"" }} |
- {{ vacancy.hr_ref|default:"" }} |
- {{ vacancy.get_recruitment_stage_display }} |
-
-
- {% endfor %}
-
-
-
- Add Vacancy
{% endblock content %}
{% block scripts %}
diff --git a/payroll/templates/payroll/partials/_error_summary.html b/payroll/templates/payroll/partials/_error_summary.html
deleted file mode 100644
index e0864dfb..00000000
--- a/payroll/templates/payroll/partials/_error_summary.html
+++ /dev/null
@@ -1,26 +0,0 @@
-{% if form.errors %}
-
-
- There is a problem
-
-
-
- {% for field in form %}
- {% if field.errors %}
- {% for error in field.errors %}
- -
- {{ field.label }} - {{ error }}
-
- {% endfor %}
- {% endif %}
- {% endfor %}
-
- {% for error in form.non_field_errors %}
- -
- {{ error }}
-
- {% endfor %}
-
-
-
-{% endif %}
\ No newline at end of file
diff --git a/payroll/templates/payroll/partials/_form_field.html b/payroll/templates/payroll/partials/_form_field.html
deleted file mode 100644
index bbd7e8bb..00000000
--- a/payroll/templates/payroll/partials/_form_field.html
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
- {{ field }}
-
diff --git a/payroll/urls.py b/payroll/urls.py
index 14bef3a6..1e0d1678 100644
--- a/payroll/urls.py
+++ b/payroll/urls.py
@@ -23,9 +23,4 @@
ChooseCostCentreView.as_view(next_page="payroll"),
name="choose_cost_centre",
),
- path(
- "edit///vacancies/create",
- views.add_vacancy_page,
- name="add_vacancy",
- ),
]
diff --git a/payroll/views.py b/payroll/views.py
index ed326385..8e321856 100644
--- a/payroll/views.py
+++ b/payroll/views.py
@@ -3,14 +3,12 @@
from django.contrib.auth.mixins import UserPassesTestMixin
from django.core.exceptions import PermissionDenied
from django.http import HttpRequest, HttpResponse, JsonResponse
-from django.shortcuts import get_object_or_404, redirect, render
+from django.shortcuts import get_object_or_404
from django.template.response import TemplateResponse
from django.views import View
from core.models import FinancialYear
from costcentre.models import CostCentre
-from payroll.forms import VacancyForm
-from payroll.models import Vacancy
from .services import payroll as payroll_service
@@ -62,7 +60,6 @@ def edit_payroll_page(
payroll_forecast_report_data = payroll_service.payroll_forecast_report(
cost_centre_obj, financial_year_obj
)
- vacancies = Vacancy.objects.filter(cost_centre=cost_centre_code)
context = {
"cost_centre_code": cost_centre_obj.cost_centre_code,
@@ -82,40 +79,6 @@ def edit_payroll_page(
"Feb",
"Mar",
],
- "vacancies": vacancies,
}
return TemplateResponse(request, "payroll/page/edit_payroll.html", context)
-
-
-def add_vacancy_page(
- request: HttpRequest, cost_centre_code: str, financial_year: int
-) -> HttpResponse:
- if not request.user.is_superuser:
- raise PermissionDenied
-
- context = {
- "cost_centre_code": cost_centre_code,
- "financial_year": financial_year,
- }
- cost_centre_obj = get_object_or_404(CostCentre, pk=cost_centre_code)
-
- if request.method == "POST":
- form = VacancyForm(request.POST)
- if form.is_valid():
- vacancy = form.save(commit=False)
- vacancy.cost_centre = cost_centre_obj
- vacancy.save()
-
- return redirect(
- "payroll:edit",
- cost_centre_code=cost_centre_code,
- financial_year=financial_year,
- )
- else:
- context["form"] = form
- return render(request, "payroll/page/add_vacancy.html", context)
- else:
- form = VacancyForm()
- context["form"] = form
- return render(request, "payroll/page/add_vacancy.html", context)
|