Skip to content

Commit

Permalink
FFT-81 Add non-payroll table to edit payroll (#547)
Browse files Browse the repository at this point in the history
  • Loading branch information
SamDudley authored Nov 19, 2024
1 parent db9d16d commit 4e56d53
Show file tree
Hide file tree
Showing 8 changed files with 170 additions and 51 deletions.
52 changes: 43 additions & 9 deletions front_end/src/Apps/Payroll.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { useEffect, useReducer, useState } from "react";
import { useEffect, useReducer, useState, useMemo } from "react";

import EditPayroll from "../Components/EditPayroll";
import * as api from "../Components/EditPayroll/api";

const initialPayrollState = [];

export default function Payroll() {
const [payroll, dispatch] = useReducer(payrollReducer, initialPayrollState);
const [allPayroll, dispatch] = useReducer(
payrollReducer,
initialPayrollState
);
const [saveSuccess, setSaveSuccess] = useState(false);

useEffect(() => {
Expand All @@ -19,10 +22,20 @@ 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(payroll);
await api.postPayrollData(allPayroll);

setSaveSuccess(true);
localStorage.setItem("saveSuccess", "true");
Expand All @@ -38,12 +51,33 @@ export default function Payroll() {
}

return (
<EditPayroll
payroll={payroll}
onSavePayroll={handleSavePayroll}
onTogglePayPeriods={handleTogglePayPeriods}
saveSuccess={saveSuccess}
/>
<>
{saveSuccess && (
<div className="govuk-notification-banner govuk-notification-banner--success">
<div className="govuk-notification-banner__header">
<h2
className="govuk-notification-banner__title"
id="govuk-notification-banner-title"
>
Success
</h2>
</div>
</div>
)}
<h2 class="govuk-heading-m">Payroll</h2>
<EditPayroll
payroll={payroll}
onTogglePayPeriods={handleTogglePayPeriods}
/>
<h2 class="govuk-heading-m">Non-payroll</h2>
<EditPayroll
payroll={nonPayroll}
onTogglePayPeriods={handleTogglePayPeriods}
/>
<button className="govuk-button" onClick={handleSavePayroll}>
Save payroll
</button>
</>
);
}

Expand Down
5 changes: 5 additions & 0 deletions front_end/src/Components/EditPayroll/EmployeeRow/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ const EmployeeRow = ({ row, onTogglePayPeriods }) => {
return (
<tr className="govuk-table__row">
<td className="govuk-table__cell">{row.name}</td>
<td className="govuk-table__cell">{row.grade}</td>
<td className="govuk-table__cell">{row.employee_no}</td>
<td className="govuk-table__cell">{row.fte}</td>
<td className="govuk-table__cell">{row.programme_code}</td>
<td className="govuk-table__cell">{row.budget_type}</td>
<td className="govuk-table__cell">{row.assignment_status}</td>
{row.pay_periods.map((enabled, index) => {
return (
<td className="govuk-table__cell" key={index}>
Expand Down
39 changes: 11 additions & 28 deletions front_end/src/Components/EditPayroll/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ import PayrollTable from "./PayrollTable/index";
* @param {types.PayrollData[]} props.payroll
* @returns
*/
export default function EditPayroll({
payroll,
onSavePayroll,
onTogglePayPeriods,
saveSuccess,
}) {
export default function EditPayroll({ payroll, onTogglePayPeriods }) {
const headers = [
"Name",
"Grade",
"Employee No",
"FTE",
"Programme Code",
"Budget Type",
"Assignment Status",
"Apr",
"May",
"Jun",
Expand All @@ -30,27 +30,10 @@ export default function EditPayroll({
"Mar",
];
return (
<>
{saveSuccess && (
<div className="govuk-notification-banner govuk-notification-banner--success">
<div className="govuk-notification-banner__header">
<h2
className="govuk-notification-banner__title"
id="govuk-notification-banner-title"
>
Success
</h2>
</div>
</div>
)}
<PayrollTable
headers={headers}
payroll={payroll}
onTogglePayPeriods={onTogglePayPeriods}
></PayrollTable>
<button className="govuk-button" onClick={onSavePayroll}>
Save payroll
</button>
</>
<PayrollTable
headers={headers}
payroll={payroll}
onTogglePayPeriods={onTogglePayPeriods}
></PayrollTable>
);
}
6 changes: 6 additions & 0 deletions front_end/src/Components/EditPayroll/types.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
/**
* @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.
*/

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# 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",
),
),
]
34 changes: 34 additions & 0 deletions payroll/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,21 @@
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 Employee(models.Model):
Expand All @@ -19,6 +35,19 @@ class Employee(models.Model):
employee_no = models.CharField(max_length=8, unique=True)
first_name = models.CharField(max_length=32)
last_name = models.CharField(max_length=32)
grade = models.ForeignKey(
to="gifthospitality.Grade",
on_delete=models.PROTECT,
null=True,
blank=True,
)
fte = models.FloatField(default=1.0)
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}"
Expand Down Expand Up @@ -67,6 +96,11 @@ class Meta:
period_11 = models.BooleanField(default=True)
period_12 = models.BooleanField(default=True)

# 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)

@property
def periods(self) -> list[bool]:
return [getattr(self, f"period_{i + 1}") for i in range(12)]
Expand Down
36 changes: 29 additions & 7 deletions payroll/services/payroll.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ 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",
Expand All @@ -61,24 +62,45 @@ 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]


def get_payroll_data(
cost_centre: CostCentre,
financial_year: FinancialYear,
) -> Iterator[EmployeePayroll]:
qs = EmployeePayPeriods.objects.select_related("employee")
qs = qs.filter(
employee__cost_centre=cost_centre,
year=financial_year,
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()
)
for obj in qs:
yield EmployeePayroll(
name=obj.employee.get_full_name(),
employee_no=obj.employee.employee_no,
pay_periods=obj.periods,
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,
)


Expand Down
10 changes: 3 additions & 7 deletions payroll/templates/payroll/page/edit_payroll.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,10 @@

{% block content %}
<h1 class="govuk-heading-l">Edit payroll</h1>

<div id="payroll-app"></div>

<h1 class="govuk-heading-l">Forecast</h1>
<p class="govuk-body-s">
This is a temporary table to demonstrate the forecast figures. Eventually these
figures would end up in the "Edit forecast" table.
</p>
<h2 class="govuk-heading-m">Payroll forecast</h2>
<table class="govuk-table">
<thead class="govuk-table__head">
<tr class="govuk-table__row">
Expand Down Expand Up @@ -52,8 +49,7 @@ <h1 class="govuk-heading-l">Forecast</h1>
</tbody>
</table>

<h1 class="govuk-heading-l">Vacancies</h1>

<h2 class="govuk-heading-m">Vacancies</h2>
<table class="govuk-table">
<thead class="govuk-table__head">
<tr class="govuk-table__row">
Expand Down

0 comments on commit 4e56d53

Please sign in to comment.