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 Payroll override edit forecast #539

Merged
merged 32 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
ba5239d
Wip add override value to forecast table cells
CaitBarnard Oct 31, 2024
be28dab
Make overrided cells uneditable
CaitBarnard Oct 31, 2024
821ca3f
Pull payroll forecast data into edit Forecast page
CaitBarnard Oct 31, 2024
3149513
Basic functionality for toggling payroll forecast data
CaitBarnard Nov 4, 2024
cd4a6c2
Basic mapping for payrollData to forecastData
CaitBarnard Nov 4, 2024
c5df5e2
Tidy up toggle work
CaitBarnard Nov 4, 2024
9758bc4
Check for whether toggle is active
CaitBarnard Nov 4, 2024
a1eb32a
Remove isOverride check from edit Forecast logic
CaitBarnard Nov 4, 2024
7859262
Update project logic
CaitBarnard Nov 4, 2024
66f3176
Fix formatting
CaitBarnard Nov 4, 2024
29cecfe
Rename isChecked to payrollToggle
CaitBarnard Nov 5, 2024
2ca49f8
Persist toggle state on cell update
CaitBarnard Nov 5, 2024
dd75f08
Only show toggle to superusers
CaitBarnard Nov 5, 2024
32a3388
Remove comments
CaitBarnard Nov 5, 2024
90a1012
Refactor is_superuser to context
CaitBarnard Nov 5, 2024
45b6066
Formatting
CaitBarnard Nov 5, 2024
47cf3e8
Fix bug
CaitBarnard Nov 6, 2024
3da4698
Refactor bug fix
CaitBarnard Nov 6, 2024
83f6d8c
Fix minor PR comments
CaitBarnard Nov 7, 2024
d41dab7
Rename is_superuser to be more descriptive
CaitBarnard Nov 7, 2024
4d6b1bb
Rename variable
CaitBarnard Nov 7, 2024
22c9af2
Rename toggle to be more descriptive
CaitBarnard Nov 7, 2024
05b552f
Fix bug where payroll was displayed on any cell update
CaitBarnard Nov 7, 2024
0210e57
Refactor classes for EditForecast table
CaitBarnard Nov 7, 2024
30dd7b0
Refactor to use JsonEncoder to handle decimals
CaitBarnard Nov 7, 2024
0bd8d0d
Remove import
CaitBarnard Nov 7, 2024
5ceddcf
Refactor toggleCheckbox handler outside of component
CaitBarnard Nov 7, 2024
6dd2f18
Add TODO for decimals vs pence ticket
CaitBarnard Nov 7, 2024
daafd19
Add todo for FFT-100 for testing paste functionality with payroll row…
CaitBarnard Nov 7, 2024
cb60929
Refactor ToggleCheckbox into folder for reusuable components
CaitBarnard Nov 7, 2024
8c44b12
Use json script for payroll data on window
CaitBarnard Nov 7, 2024
92eaa19
Formatting
CaitBarnard Nov 7, 2024
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
2 changes: 2 additions & 0 deletions forecast/templates/forecast/edit/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@
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 = {{ view.get_payroll_forecast_report|safe }};
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved
window.is_superuser = "{{ is_superuser|safe }}";
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved
</script>
{% vite_dev_client %}
{% vite_js 'src/index.jsx' %}
Expand Down
23 changes: 20 additions & 3 deletions forecast/views/edit_forecast.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import json
import logging
import re
from decimal import Decimal

from django.conf import settings
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 +53,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 +475,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 +499,25 @@ def get_context_data(self, **kwargs):
context["forecast_dump"] = forecast_dump
context["actuals"] = actual_data
context["period_display"] = period_display
context["is_superuser"] = self.request.user.is_superuser
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved

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)

for item in data:
for key, value in item.items():
if isinstance(value, Decimal):
item[key] = str(value)

return json.dumps(data)
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved

@property
def future_year_display(self):
if self._future_year_display is None:
Expand Down
9 changes: 6 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 '../ToggleCheckbox';


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

const [sheetUpdating, setSheetUpdating] = useState(false)
const [payrollToggle, setPayrollToggle] = useState(false)

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

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

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

return (
<Fragment>
{window.is_superuser == "True" && <ToggleCheckbox toggle={payrollToggle} setToggle={setPayrollToggle} id="payroll-forecast" value="payroll" label="Toggle payroll forecast rows" />}
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved
{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 +335,7 @@ function EditForecast() {
</div>
}
<EditActionBar />
<Table sheetUpdating={sheetUpdating} />
<Table sheetUpdating={sheetUpdating} payrollData={window.payroll_forecast_data}/>
</Fragment>
);
}
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
19 changes: 15 additions & 4 deletions front_end/src/Components/TableCell/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import {
import { SET_ERROR } from '../../Reducers/Error'
import { SET_CELLS } from '../../Reducers/Cells'

const TableCell = ({rowIndex, cellId, cellKey, sheetUpdating}) => {
const TableCell = ({rowIndex, cellId, cellKey, sheetUpdating, payrollData}) => {

let editing = false
const isToggle = JSON.parse(localStorage.getItem('isToggle'))
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved

const checkValue = (val) => {
if (cellId === val) {
Expand Down Expand Up @@ -46,6 +47,16 @@ const TableCell = ({rowIndex, cellId, cellKey, sheetUpdating}) => {
const cell = useSelector(state => state.allCells.cells[rowIndex][cellKey]);
const editCellId = useSelector(state => state.edit.cellId, checkValue);

const isOverride = () => {
// Is override if cell exists, has an override amount and is not an actual
return (cell && cell.overrideAmount != null && cell.isEditable)
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved
}

if (isOverride()) {
console.log(cell.overrideAmount)
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved
cell.amount = cell.overrideAmount
}

const [isUpdating, setIsUpdating] = useState(false)

const selectedRow = useSelector(state => state.selected.selectedRow, checkSelectRow);
Expand Down Expand Up @@ -105,7 +116,7 @@ const TableCell = ({rowIndex, cellId, cellKey, sheetUpdating}) => {
negative = " negative"
}

return "govuk-table__cell forecast-month-cell figure-cell " + (wasEdited() ? 'edited ' : '') + (isSelected() ? 'selected' : '') + editable + negative
return "govuk-table__cell forecast-month-cell figure-cell " + (wasEdited() ? 'edited ' : '') + (isSelected() ? 'selected' : '') + (isOverride() ? 'override ' : '') + editable + negative
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved
}

const setContentState = (value) => {
Expand Down Expand Up @@ -160,7 +171,7 @@ const TableCell = ({rowIndex, cellId, cellKey, sheetUpdating}) => {
).then((response) => {
setIsUpdating(false)
if (response.status === 200) {
let rows = processForecastData(response.data)
let rows = processForecastData(response.data, payrollData, isToggle)
dispatch({
type: SET_CELLS,
cells: rows
Expand Down Expand Up @@ -256,7 +267,7 @@ const TableCell = ({rowIndex, cellId, cellKey, sheetUpdating}) => {
className={getClasses()}
id={getId()}
onDoubleClick={ () => {
if (isEditable) {
if (isEditable && !isOverride()) {
dispatch(
SET_EDITING_CELL({
"cellId": cellId
Expand Down
37 changes: 37 additions & 0 deletions front_end/src/Components/ToggleCheckbox/index.jsx
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { useState } from "react";

export default function ToggleCheckbox({ toggle, setToggle, id, value, label }) {

const handleToggle = () => {
setToggle(!toggle);

// To fix: Hacky method, set to local storage so this value can be passed
// to processForecastData when updating a cell
localStorage.setItem('isToggle', JSON.stringify(true));
}
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved

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={handleToggle}
value={value}
/>
<label className="govuk-label govuk-checkboxes__label" htmlFor={id}>
{ label }
</label>
</div>
</div>
</fieldset>
</div>
</>
);
}
41 changes: 39 additions & 2 deletions front_end/src/Util.js
Original file line number Diff line number Diff line change
Expand Up @@ -117,8 +117,13 @@ export async function postData(url = '', data = {}) {
}
}

export const processForecastData = (forecastData) => {
export const processForecastData = (forecastData, payrollData = null, payrollToggle = false) => {
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved
let rows = [];
let mappedPayrollData = null

if (payrollToggle) {
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved
mappedPayrollData = processPayrollData(payrollData)
}

let financialCodeCols = [
"analysis1_code",
Expand Down Expand Up @@ -149,15 +154,31 @@ export const processForecastData = (forecastData) => {
colIndex++
}

const forecastKey = makeKey(
rowData.programme,
rowData.natural_account_code,
rowData.analysis1_code,
rowData.analysis2_code,
rowData.project_code
);

// eslint-disable-next-line
for (const [key, monthlyFigure] of Object.entries(rowData["monthly_figures"])) {
let overrideAmount = null

if (payrollToggle && mappedPayrollData[forecastKey]) {
const period = `period_${(parseInt(key)+1)}_sum`
overrideAmount = mappedPayrollData[forecastKey][period] * 100
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved
}

cells[monthlyFigure.month] = {
rowIndex: rowIndex,
colIndex: colIndex,
key: monthlyFigure.month,
amount: monthlyFigure.amount,
startingAmount: monthlyFigure.starting_amount,
isEditable: !monthlyFigure.actual
isEditable: !monthlyFigure.actual,
overrideAmount: overrideAmount,
}

colIndex++
Expand All @@ -169,6 +190,22 @@ export const processForecastData = (forecastData) => {
return rows;
}

const processPayrollData = (payrollData) => {
const results = {};

for (const [key, value] of Object.entries(payrollData)) {
const generatedKey = makeKey(value.programme_code, value.pay_element__type__group__natural_code)

results[generatedKey] = value;
}

return results
}

const makeKey = (programme, nac, analysis1=null, analysis2=null, project=null) => {
CaitBarnard marked this conversation as resolved.
Show resolved Hide resolved
return `${programme}/${nac}/${analysis1}/${analysis2}/${project}`
}


/**
* Retrieves JSON data from an HTML element with the given ID.
Expand Down
4 changes: 4 additions & 0 deletions front_end/styles/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@ th {
background-color: rgba(86, 148, 202, 0.25);
}

.override {
background-color: rgba(201, 155, 75, 0.25);
}

.link-button {
border: none;
padding: 0 !important;
Expand Down