Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FFT-80 Integrate payroll into forecast #541

Merged
merged 6 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 [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 @@
if (window.financialYears){
setFinancialYears(window.financialYears)
}
setNextPage(window.nextPage)
} else {
timer()
}
Expand Down Expand Up @@ -68,7 +70,7 @@
<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>

Check warning

Code scanning / CodeQL

DOM text reinterpreted as HTML Medium

DOM text
is reinterpreted as HTML without escaping meta-characters.
</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