Skip to content

Commit

Permalink
FFT-80 Integrate payroll into forecast (#541)
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 Nov 11, 2024
1 parent 95b6e49 commit d41004e
Show file tree
Hide file tree
Showing 19 changed files with 233 additions and 46 deletions.
2 changes: 1 addition & 1 deletion core/management/commands/create_stub_forecast_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def create_monthly_figures():
current_financial_year = FinancialYear.objects.get(current=True)
cost_centre_fk = CostCentre.objects.first()
programme_list = ProgrammeCode.objects.all()
project_list = ProjectCode.objects.all()
project_list = list(ProjectCode.objects.all()) + [None]
natural_account_list = NaturalCode.objects.all()
financial_periods = FinancialPeriod.objects.exclude(
period_long_name__icontains="adj"
Expand Down
3 changes: 2 additions & 1 deletion forecast/templates/forecast/edit/choose_cost_centre.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@
{% breadcrumb "Choose cost centre" "choose_cost_centre" %}
{% endblock %}

{% block title %}Edit Forecast - Choose Cost Centre{% endblock %}
{% block title %}Edit {{ view.get_page_header }} - Choose Cost Centre{% endblock %}

{% block content %}
<h3 class="govuk-heading-l">My cost centres</h3>
<div id="cost-centre-list-app"></div>
{% endblock %}
{% block scripts %}
<script>
window.nextPage = "{{ view.next_page }}";
window.costCentres = {{ view.get_user_cost_centres|safe }};
window.currentFinancialYear = {{ view.get_financial_year|safe }};
window.currentFinancialYearDisplay = "{{ view.get_financial_year_display|safe }}";
Expand Down
7 changes: 5 additions & 2 deletions forecast/templates/forecast/edit/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,17 @@
{% csrf_token %}
{{ paste_form }}
</form>
{% endblock %}
{% block scripts %}
{% endblock %}
{% block scripts %}
{{ view.get_payroll_forecast_report|json_script:'payroll_forecast_data' }}
<script>
window.actuals = {{ actuals|safe }};
window.period_display = {{ period_display|safe }};
window.table_data = {{ forecast_dump|safe }};
window.cost_centre = {{ view.cost_centre_details.cost_centre_code|safe }};
window.financial_year = {{ view.financial_year|safe }};
window.payroll_forecast_data = JSON.parse(document.getElementById("payroll_forecast_data").textContent);
window.can_toggle_payroll = "{{ can_toggle_payroll }}";
</script>
{% vite_dev_client %}
{% vite_js 'src/index.jsx' %}
Expand Down
17 changes: 14 additions & 3 deletions forecast/views/edit_forecast.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,12 @@
from django.db import transaction
from django.db.models import Exists, OuterRef, Prefetch, Q
from django.http import JsonResponse
from django.shortcuts import redirect
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.views.generic.base import TemplateView
from django.views.generic.edit import FormView

from core.models import FinancialYear
from core.utils.generic_helpers import get_current_financial_year, get_year_display
from costcentre.models import CostCentre
from forecast.forms import (
Expand Down Expand Up @@ -51,6 +52,7 @@
NoCostCentreCodeInURLError,
NoFinancialYearInURLError,
)
from payroll.services import payroll as payroll_service


UNAVAILABLE_FORECAST_EDIT_TITLE = "Forecast editing is locked"
Expand Down Expand Up @@ -472,12 +474,10 @@ def get_context_data(self, **kwargs):
"cost_centre_code": self.cost_centre_code,
}
)

financial_code_serialiser = get_financial_code_serialiser(
self.cost_centre_code,
self.financial_year,
)

serialiser_data = financial_code_serialiser.data
forecast_dump = json.dumps(serialiser_data)
if self.financial_year == get_current_financial_year():
Expand All @@ -498,9 +498,20 @@ def get_context_data(self, **kwargs):
context["forecast_dump"] = forecast_dump
context["actuals"] = actual_data
context["period_display"] = period_display
context["can_toggle_payroll"] = self.request.user.is_superuser

return context

def get_payroll_forecast_report(self):
cost_centre_obj = get_object_or_404(CostCentre, pk=self.cost_centre_code)
financial_year_obj = get_object_or_404(FinancialYear, pk=self.financial_year)
queryset = payroll_service.payroll_forecast_report(
cost_centre_obj, financial_year_obj
)
data = list(queryset)

return data

@property
def future_year_display(self):
if self._future_year_display is None:
Expand Down
4 changes: 4 additions & 0 deletions forecast/views/edit_select_cost_centre.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class ChooseCostCentreView(
template_name = "forecast/edit/choose_cost_centre.html"
form_class = MyCostCentresForm
cost_centre = None
next_page = "forecast"

def test_func(self):
can_edit = can_edit_at_least_one_cost_centre(self.request.user)
Expand Down Expand Up @@ -80,6 +81,9 @@ def get_user_cost_centres(self):

return json.dumps(cost_centres)

def get_page_header(self):
return self.next_page.capitalize()

def get_form_kwargs(self):
kwargs = super(ChooseCostCentreView, self).get_form_kwargs()
kwargs["user"] = self.request.user
Expand Down
17 changes: 15 additions & 2 deletions front_end/src/Apps/Payroll.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useEffect, useReducer } from "react";
import { useEffect, useReducer, useState } from "react";

import EditPayroll from "../Components/EditPayroll";
import * as api from "../Components/EditPayroll/api";
Expand All @@ -7,15 +7,27 @@ const initialPayrollState = [];

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

useEffect(() => {
const savedSuccessFlag = localStorage.getItem("saveSuccess");
if (savedSuccessFlag === "true") {
setSaveSuccess(true);
localStorage.removeItem("saveSuccess");
}

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

// Handlers
async function handleSavePayroll() {
try {
api.postPayrollData(payroll);
await api.postPayrollData(payroll);

setSaveSuccess(true);
localStorage.setItem("saveSuccess", "true");

window.location.reload();
} catch (error) {
console.error("Error saving payroll: ", error);
}
Expand All @@ -30,6 +42,7 @@ export default function Payroll() {
payroll={payroll}
onSavePayroll={handleSavePayroll}
onTogglePayPeriods={handleTogglePayPeriods}
saveSuccess={saveSuccess}
/>
);
}
Expand Down
26 changes: 26 additions & 0 deletions front_end/src/Components/Common/ToggleCheckbox/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export default function ToggleCheckbox({ toggle, handler, id, value, label }) {
return (
<>
<div className="govuk-form-group">
<fieldset className="govuk-fieldset" aria-describedby='toggle-hint'>
<div className="govuk-checkboxes govuk-checkboxes--small" data-module="govuk-checkboxes">
<div className="govuk-checkboxes__item">
<input
className="govuk-checkboxes__input"
id={id}
name={id}
type="checkbox"
checked={toggle}
onChange={handler}
value={value}
/>
<label className="govuk-label govuk-checkboxes__label" htmlFor={id}>
{ label }
</label>
</div>
</div>
</fieldset>
</div>
</>
);
}
4 changes: 3 additions & 1 deletion front_end/src/Components/CostCentreList/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const CostCentreList = ({rowIndex, cellKey, format}) => {
const [financialYears, setFinancialYears] = useState([])
const [forecastFinYearDisplay, setForecastFinYearDisplay] = useState(null)
const [forecastFinYear, setForecastFinYear] = useState(null)
const [nextPage, setNextPage] = useState(null)

useEffect(() => {
const timer = () => {
Expand All @@ -18,6 +19,7 @@ const CostCentreList = ({rowIndex, cellKey, format}) => {
if (window.financialYears){
setFinancialYears(window.financialYears)
}
setNextPage(window.nextPage)
} else {
timer()
}
Expand Down Expand Up @@ -68,7 +70,7 @@ const CostCentreList = ({rowIndex, cellKey, format}) => {
<ul className="cost-centre-list">
{displayedCentres.map((costCentre, index) => {
return <li key={index}>
<a href={ `/forecast/edit/${costCentre.code}/${forecastFinYear}` } className="govuk-link">{costCentre.code} - {costCentre.name}</a>
<a href={ `/${nextPage}/edit/${costCentre.code}/${forecastFinYear}` } className="govuk-link">{costCentre.code} - {costCentre.name}</a>
</li>
})}
</ul>
Expand Down
15 changes: 12 additions & 3 deletions front_end/src/Components/EditForecast/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
postData,
processForecastData,
} from '../../Util'
import ToggleCheckbox from '../Common/ToggleCheckbox';


function EditForecast() {
Expand All @@ -27,12 +28,19 @@ function EditForecast() {
const editCellId = useSelector(state => state.edit.cellId);

const [sheetUpdating, setSheetUpdating] = useState(false)
const [isPayrollEnabled, setIsPayrollEnabled] = useState(false)

const handleIsPayrollEnabled = () => {
setIsPayrollEnabled(!isPayrollEnabled);

localStorage.setItem('isPayrollEnabled', JSON.stringify(!isPayrollEnabled));
}

useEffect(() => {
const timer = () => {
setTimeout(() => {
if (window.table_data) {
let rows = processForecastData(window.table_data)
let rows = processForecastData(window.table_data, window.payroll_forecast_data, isPayrollEnabled)
dispatch({
type: SET_CELLS,
cells: rows
Expand All @@ -45,7 +53,7 @@ function EditForecast() {
}

timer()
}, [dispatch])
}, [dispatch, isPayrollEnabled])

useEffect(() => {
const capturePaste = (event) => {
Expand Down Expand Up @@ -317,6 +325,7 @@ function EditForecast() {

return (
<Fragment>
{window.can_toggle_payroll === "True" && <ToggleCheckbox toggle={isPayrollEnabled} handler={handleIsPayrollEnabled} id="payroll-forecast" value="payroll" label="Toggle payroll forecast rows" />}
{errorMessage != null &&
<div className="govuk-error-summary" aria-labelledby="error-summary-title" role="alert" tabIndex="-1" data-module="govuk-error-summary">
<h2 className="govuk-error-summary__title" id="error-summary-title">
Expand All @@ -332,7 +341,7 @@ function EditForecast() {
</div>
}
<EditActionBar />
<Table sheetUpdating={sheetUpdating} />
<Table sheetUpdating={sheetUpdating} payrollData={window.payroll_forecast_data}/>
</Fragment>
);
}
Expand Down
13 changes: 13 additions & 0 deletions front_end/src/Components/EditPayroll/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default function EditPayroll({
payroll,
onSavePayroll,
onTogglePayPeriods,
saveSuccess,
}) {
const headers = [
"Name",
Expand All @@ -30,6 +31,18 @@ export default function EditPayroll({
];
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}
Expand Down
4 changes: 2 additions & 2 deletions front_end/src/Components/Table/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { SET_EDITING_CELL } from '../../Reducers/Edit'
import { SET_SELECTED_ROW, SELECT_ALL, UNSELECT_ALL } from '../../Reducers/Selected'


function Table({rowData, sheetUpdating}) {
function Table({rowData, sheetUpdating, payrollData}) {
const dispatch = useDispatch();
const rows = useSelector(state => state.allCells.cells);

Expand Down Expand Up @@ -167,7 +167,7 @@ function Table({rowData, sheetUpdating}) {
<CellValue rowIndex={rowIndex} cellKey={"budget"} format={true} />
</InfoCell>
{window.period_display.map((value, index) => {
return <TableCell key={nanoid()} sheetUpdating={sheetUpdating} cellId={getCellId(rowIndex, value)} rowIndex={rowIndex} cellKey={value} />
return <TableCell key={nanoid()} sheetUpdating={sheetUpdating} payrollData={payrollData} cellId={getCellId(rowIndex, value)} rowIndex={rowIndex} cellKey={value} />
})}
<InfoCell className="figure-cell" rowIndex={rowIndex}>
<AggregateValue rowIndex={rowIndex} actualsOnly={false} extraClasses="" />
Expand Down
Loading

0 comments on commit d41004e

Please sign in to comment.