Skip to content

Commit

Permalink
FFT-28 v2.0 Edit payroll (#533)
Browse files Browse the repository at this point in the history
Co-authored-by: Caitlin Barnard <[email protected]>
  • Loading branch information
SamDudley and CaitBarnard authored Oct 22, 2024
1 parent 7fa497d commit 68b63f7
Show file tree
Hide file tree
Showing 32 changed files with 1,445 additions and 26 deletions.
34 changes: 20 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,36 +22,42 @@ jobs:
- name: Create a .env file
run: cp .env.ci .env

- name: Build images
run: make build

- name: Run containers
run: make up-detatched

- name: Install React app
run: npm ci
- name: Install poetry
run: pip install poetry

- name: Collect static
run: make collectstatic
- name: Install python dependencies
run: poetry install --with dev

- name: Run ruff
run: make ruff
run: make ruff-check

- name: Run isort
run: make isort-check

- name: Run black
run: make black-check

- name: Install React app
run: npm ci

- name: Build images
run: make build

- name: Run containers
run: make up-detatched

- name: Run makemigrations in check mode
run: make check-migrations

- name: Collect static
run: make collectstatic

- name: Run tests
run: make test-ci

- name: Bring up chrome
run: docker compose up -d chrome

- name: Run makemigrations in check mode
run: make check-migrations

- name: Run BDD tests
run: make bdd

Expand Down
1 change: 1 addition & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
VCAP_SERVICES = env.json("VCAP_SERVICES", {})

INSTALLED_APPS = [
"payroll.apps.PayrollConfig",
"user",
"authbroker_client",
"future_years.apps.FutureYearsConfig",
Expand Down
1 change: 1 addition & 0 deletions config/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
path("data-lake/", include("data_lake.urls")),
path("oscar_return/", include("oscar_return.urls")),
path("upload_split_file/", include("upload_split_file.urls")),
path("payroll/", include("payroll.urls")),
path("admin/", admin.site.urls),
# TODO - split below out into develop only?
path(
Expand Down
13 changes: 12 additions & 1 deletion core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,17 @@ def future_year_dictionary(self):
)


class FinancialYearQuerySet(models.QuerySet):
def current(self):
return self.filter(current=True).first()

def future(self):
current_financial_year = self.current().financial_year
return self.filter(financial_year__gt=current_financial_year).order_by(
"-financial_year"
)


class FinancialYear(BaseModel):
"""Key and representation of the financial year"""

Expand All @@ -69,7 +80,7 @@ class FinancialYear(BaseModel):
current = models.BooleanField(default=False)
archived = models.BooleanField(default=False)
archived_at = models.DateTimeField(blank=True, null=True)
objects = models.Manager() # The default manager.
objects = FinancialYearQuerySet.as_manager()
financial_year_objects = FinancialYearManager()

def __str__(self):
Expand Down
6 changes: 5 additions & 1 deletion core/templates/base_generic.html
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,11 @@
{% block scripts %}
<script src="/static/govuk/all.js"></script>
<script>
window.GOVUKFrontend.initAll()
window.GOVUKFrontend.initAll();

function getCsrfToken() {
return "{{ csrf_token }}";
}
</script>
{% endblock %}
{% vite_dev_client react=False %}
Expand Down
74 changes: 74 additions & 0 deletions costcentre/fixtures/test_costcentre_data.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
[
{
"model": "costcentre.departmentalgroup",
"pk": "8888AA",
"fields": {
"created": "2024-10-03T16:05:41.208Z",
"updated": "2024-10-03T16:05:41.208Z",
"active": true,
"group_name": "Departmental Group 0",
"director_general": null,
"treasury_segment_fk": null
}
},
{
"model": "costcentre.directorate",
"pk": "88880A",
"fields": {
"created": "2024-10-03T16:05:41.226Z",
"updated": "2024-10-03T16:05:41.226Z",
"active": true,
"directorate_name": "Directorate 1",
"director": null,
"group": "8888AA"
}
},
{
"model": "costcentre.costcentre",
"pk": "888812",
"fields": {
"created": "2024-10-03T16:05:41.230Z",
"updated": "2024-10-03T16:05:41.230Z",
"active": true,
"cost_centre_name": "Cost Centre 2",
"directorate": "88880A",
"deputy_director": null,
"business_partner": null,
"bsce_email": null,
"disabled_with_actual": false,
"used_for_travel": true
}
},
{
"model": "costcentre.costcentre",
"pk": "888813",
"fields": {
"created": "2024-10-03T16:05:41.233Z",
"updated": "2024-10-03T16:05:41.233Z",
"active": true,
"cost_centre_name": "Cost Centre 3",
"directorate": "88880A",
"deputy_director": null,
"business_partner": null,
"bsce_email": null,
"disabled_with_actual": false,
"used_for_travel": true
}
},
{
"model": "costcentre.costcentre",
"pk": "888814",
"fields": {
"created": "2024-10-03T16:05:41.236Z",
"updated": "2024-10-03T16:05:41.236Z",
"active": true,
"cost_centre_name": "Cost Centre 4",
"directorate": "88880A",
"deputy_director": null,
"business_partner": null,
"bsce_email": null,
"disabled_with_actual": false,
"used_for_travel": true
}
}
]
2 changes: 0 additions & 2 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: "3"

services:
web:
build:
Expand Down
8 changes: 7 additions & 1 deletion forecast/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,12 @@ def __str__(self):
return self.forecast_expenditure_type_name


class FinancialPeriodQuerySet(models.QuerySet):
def months(self):
"""Filter by real months, excluding the 3 adjustment periods (13, 14, 15)."""
return self.filter(financial_period_code__lte=12)


class FinancialPeriodManager(models.Manager):
def month_display_list(self):
return list(
Expand Down Expand Up @@ -296,7 +302,7 @@ class FinancialPeriod(BaseModel):
actual_loaded_previous_year = models.BooleanField(default=False)
display_figure = models.BooleanField(default=True)

objects = models.Manager() # The default manager.
objects = FinancialPeriodQuerySet.as_manager()
financial_period_info = FinancialPeriodManager()

class Meta:
Expand Down
62 changes: 62 additions & 0 deletions front_end/src/Apps/Payroll.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useEffect, useReducer } 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);

useEffect(() => {
api.getPayrollData().then((data) => dispatch({ type: "fetched", data }));
}, []);

// Handlers
async function handleSavePayroll() {
try {
api.postPayrollData(payroll);
} catch (error) {
console.error("Error saving payroll: ", error);
}
}

function handleTogglePayPeriods(employeeNo, index, enabled) {
dispatch({ type: "updatePayPeriods", employeeNo, index, enabled });
}

return (
<EditPayroll
payroll={payroll}
onSavePayroll={handleSavePayroll}
onTogglePayPeriods={handleTogglePayPeriods}
/>
);
}

function payrollReducer(payroll, action) {
switch (action.type) {
case "fetched": {
return action.data;
}
case "updatePayPeriods": {
return payroll.map((employeeRow) => {
if (employeeRow.employee_no == action.employeeNo) {
const updatedPayPeriods = employeeRow.pay_periods.map(
(period, index) => {
if (index + 1 >= action.index + 1) {
return !action.enabled;
}
return period;
}
);
return {
...employeeRow,
pay_periods: updatedPayPeriods,
};
}
return employeeRow;
});
}
}
}
25 changes: 25 additions & 0 deletions front_end/src/Components/EditPayroll/EmployeeRow/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useState } from "react";

const EmployeeRow = ({ row, onTogglePayPeriods }) => {
return (
<tr className="govuk-table__row">
<td className="govuk-table__cell">{row.name}</td>
<td className="govuk-table__cell">{row.employee_no}</td>
{row.pay_periods.map((enabled, index) => {
return (
<td className="govuk-table__cell" key={index}>
<input
type="checkbox"
checked={enabled}
onChange={() =>
onTogglePayPeriods(row.employee_no, index, enabled)
}
/>
</td>
);
})}
</tr>
);
};

export default EmployeeRow;
38 changes: 38 additions & 0 deletions front_end/src/Components/EditPayroll/PayrollTable/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import EmployeeRow from "../EmployeeRow";

/**
*
* @param {object} props
* @param {types.PayrollData[]} props.payroll
* @returns
*/
export default function PayrollTable({ headers, payroll, onTogglePayPeriods }) {
return (
<>
<table className="govuk-table">
<thead className="govuk-table__head">
<tr className="govuk-table__row">
{headers.map((header) => {
return (
<th scope="col" className="govuk-table__header" key={header}>
{header}
</th>
);
})}
</tr>
</thead>
<tbody className="govuk-table__body">
{payroll.map((row) => {
return (
<EmployeeRow
row={row}
key={row.employee_no}
onTogglePayPeriods={onTogglePayPeriods}
/>
);
})}
</tbody>
</table>
</>
);
}
35 changes: 35 additions & 0 deletions front_end/src/Components/EditPayroll/api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { getData, postData } from "../../Util";

import * as types from "./types";

/**
* Fetch payroll data and return it as a promise.
* @returns {Promise<types.PayrollData[]>} A promise resolving to an array of objects containing employee information.
*/
export function getPayrollData() {
return getData(getPayrollApiUrl()).then((data) => data.data);
}

/**
* Post modified payroll data.
*
* @param {types.PayrollData[]} payrollData - Payroll data to be sent.
* @returns {import("../../Util").PostDataResponse} Updated payroll data received.
*/
export function postPayrollData(payrollData) {
return postData(getPayrollApiUrl(), JSON.stringify(payrollData));
}

/**
* Return the payroll API URL.
*
* This function relies on the `costCentreCode` and `financialYear` being available on
* the `window` object.
*
* @returns {string} The payroll API URL.
*/
function getPayrollApiUrl() {
const costCentreCode = window.costCentreCode;
const financialYear = window.financialYear;
return `/payroll/api/${costCentreCode}/${financialYear}/`;
}
Loading

0 comments on commit 68b63f7

Please sign in to comment.