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-152 Commit payroll figures into forecast #610

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
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 .env.ci
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ AUTHBROKER_URL=
SENTRY_ENVIRONMENT=ci
SENTRY_DSN=
CSP_REPORT_URI=
PAYROLL='{"BASIC_PAY_NAC": "71111001", "PENSION_NAC": "71111002", "ERNIC_NAC": "71111003", "VACANCY_NAC": "71111001"}'
PAYROLL='{"BASIC_PAY_NAC": 71111001, "PENSION_NAC": 71111002, "ERNIC_NAC": 71111003, "VACANCY_NAC": 71111001}'
2 changes: 1 addition & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ CSP_REPORT_URI=
# Vite
VITE_DEV=True

PAYROLL='{"BASIC_PAY_NAC": "71111001", "PENSION_NAC": "71111002", "ERNIC_NAC": "71111003", "VACANCY_NAC": "71111001"}'
PAYROLL='{"BASIC_PAY_NAC": 71111001, "PENSION_NAC": 71111002, "ERNIC_NAC": 71111003, "VACANCY_NAC": 71111001}'

# Not documented (needed?)
# RESTRICT_ADMIN=True
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ The names of the management commands denote their function.
- Add the user app initial migration to the list of django migrations that have been run
- Deploy new codebase

# Setup DebugPy
## Setup DebugPy

Add environment variable in your .env file

Expand Down Expand Up @@ -173,3 +173,7 @@ Create launch.json file inside .vscode directory
]
}
```

## Code notes

All the reducers in `front_end/src/Reducers/` are for the `Forecast` React "app".
12 changes: 6 additions & 6 deletions chartofaccountDIT/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -313,6 +313,12 @@ class Meta:

# define level1 values: Capital, staff, etc is Level 1 in UKTI nac hierarchy
class NaturalCodeAbstract(models.Model):
class Meta:
abstract = True
verbose_name = "Natural Account Code (NAC)"
verbose_name_plural = "Natural Account Codes (NAC)"
ordering = ["natural_account_code"]

natural_account_code = models.IntegerField(primary_key=True, verbose_name="NAC",)
natural_account_code_description = models.CharField(
max_length=200, verbose_name="NAC Description"
Expand Down Expand Up @@ -354,12 +360,6 @@ def __str__(self):
self.natural_account_code, self.natural_account_code_description
)

class Meta:
abstract = True
verbose_name = "Natural Account Code (NAC)"
verbose_name_plural = "Natural Account Codes (NAC)"
ordering = ["natural_account_code"]


class NaturalCode(NaturalCodeAbstract, IsActiveModel):
expenditure_category = models.ForeignKey(
Expand Down
22 changes: 18 additions & 4 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,7 @@ def FILTERS_VERBOSE_LOOKUPS():
SETTINGS_EXPORT = [
"DEBUG",
"GTM_CODE",
"PAYROLL",
]

MIDDLEWARE = [
Expand Down Expand Up @@ -423,11 +424,24 @@ def FILTERS_VERBOSE_LOOKUPS():
# Payroll
@dataclass
class Payroll:
BASIC_PAY_NAC: str | None = None
PENSION_NAC: str | None = None
ERNIC_NAC: str | None = None
VACANCY_NAC: str | None = None
BASIC_PAY_NAC: int | None = None
PENSION_NAC: int | None = None
ERNIC_NAC: int | None = None
VACANCY_NAC: int | None = None
AVERAGE_SALARY_THRESHOLD: int = 2
# FIXME: switch to false
ENABLE_FORECAST: bool = True

@property
def nacs(self):
return set(
(
self.BASIC_PAY_NAC,
self.PENSION_NAC,
self.ERNIC_NAC,
self.VACANCY_NAC,
)
)


PAYROLL: Payroll = Payroll(**env.json("PAYROLL", default={}))
8 changes: 8 additions & 0 deletions core/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,14 @@ def clean(self):
"Monthly pay uplifts must be greater than or equal to 1.0"
)

def save(self, *args, **kwargs):
super().save(*args, **kwargs)

# Avoid circular import
from payroll.services.payroll import update_all_payroll_forecast

update_all_payroll_forecast(financial_year=self.financial_year)


class Attrition(PayModifiers):
class Meta:
Expand Down
29 changes: 29 additions & 0 deletions core/static/core/js/feature-flags.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
export default function FeatureFlags(namespace) {
const cache = new Map();

const featureFlagHandler = {
get(target, prop, receiver) {
const selector = `meta[name="${namespace}:${prop}"]`;

const el = document.querySelector(selector);

if (!el) {
return false;
}

if (cache.has(prop)) {
return cache.get(prop);
}

const value = el.content.toLowerCase() === "true";

cache.set(prop, value);

return value;
},
};

const featureFlagProxy = new Proxy({}, featureFlagHandler);

return featureFlagProxy;
}
23 changes: 15 additions & 8 deletions core/templates/base_generic.html
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@

<meta http-equiv="X-UA-Compatible" content="IE=edge"/>

<!-- Feature flags -->
<meta name="fft:features:payroll_enable_forecast" content="true">

<link rel="shortcut icon" sizes="16x16 32x32 48x48" href="{% static 'govuk/assets/images/favicon.ico' %}" type="image/x-icon"/>
<link rel="mask-icon" href="{% static 'govuk/assets/images/govuk-mask-icon.svg' %}" color="#0b0c0c">
<link rel="apple-touch-icon" sizes="180x180" href="{% static 'govuk/assets/images/govuk-apple-touch-icon-180x180.png' %}">
Expand Down Expand Up @@ -252,15 +255,19 @@
</div>
</footer>

{% block scripts %}
<script src="/static/govuk/all.js"></script>
<script>
window.GOVUKFrontend.initAll();
<script src="/static/govuk/all.js"></script>
<script type="module">
import FeatureFlags from "{% static 'core/js/feature-flags.js' %}";

function getCsrfToken() {
return "{{ csrf_token }}";
}
</script>
window.GOVUKFrontend.initAll();

function getCsrfToken() {
return "{{ csrf_token }}";
}

window.FEATURES = FeatureFlags("fft:features");
</script>
{% block scripts %}
{% endblock %}
{% vite_dev_client react=False %}
</body>
Expand Down
File renamed without changes.
10 changes: 10 additions & 0 deletions forecast/models.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import copy
import hashlib

from django.conf import settings
from django.contrib.auth import get_user_model
from django.core.cache import cache
from django.db import models
Expand Down Expand Up @@ -442,6 +443,15 @@ def save(self, *args, **kwargs):

super(FinancialCodeAbstract, self).save(*args, **kwargs)

@property
def is_locked(self) -> bool:
return (
self.natural_account_code_id in settings.PAYROLL.nacs
and self.analysis1_code is None
and self.analysis2_code is None
and self.project_code is None
)


class FinancialCode(FinancialCodeAbstract, BaseModel):
programme = models.ForeignKey(ProgrammeCode, on_delete=models.PROTECT)
Expand Down
5 changes: 5 additions & 0 deletions forecast/serialisers.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,15 @@ class FinancialCodeSerializer(serializers.ModelSerializer):
nac_description = serializers.SerializerMethodField(
"get_nac_description",
)
is_locked = serializers.SerializerMethodField()

class Meta:
model = FinancialCode
fields = [
"programme_description",
"nac_description",
"natural_account_code",
"is_locked",
"programme",
"cost_centre",
"analysis1_code",
Expand All @@ -62,3 +64,6 @@ def get_programme_description(self, obj):

def get_nac_description(self, obj):
return obj.natural_account_code.natural_account_code_description

def get_is_locked(self, obj):
return obj.is_locked
1 change: 1 addition & 0 deletions forecast/templates/forecast/edit/edit.html
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
window.financial_year = {{ view.financial_year|safe }};
window.payroll_forecast_data = JSON.parse(document.getElementById("payroll_forecast_data").textContent);
window.can_access_edit_payroll = "{{ user_can_access_edit_payroll }}";
window.PAYROLL_ENABLE_FORECAST = {% if settings.PAYROLL.ENABLE_FORECAST %}true{% else %}false{% endif %};
</script>
{% vite_dev_client %}
{% vite_js 'src/index.jsx' %}
Expand Down
3 changes: 1 addition & 2 deletions forecast/templates/forecast/view/cost_centre.html
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,7 @@ <h2 class="govuk-heading-l">{{ table.attrs.caption }}</h2>
{% endblock %}

{% block scripts %}
{{ block.super }}
<script type="application/javascript">
<script>
let allCC = document.getElementById("all-cc");
let myCC = document.getElementById("my-cc");

Expand Down
3 changes: 2 additions & 1 deletion forecast/utils/edit_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@ def set_monthly_figure_amount(cost_centre_code, cell_data, financial_year): # n
financial_code=financial_code,
)

if new_value != monthly_figure.amount:
# FIXME
if not financial_code.is_locked and new_value != monthly_figure.amount:
monthly_figure.amount = new_value
monthly_figure.save()

Expand Down
16 changes: 5 additions & 11 deletions front_end/src/Components/TableCell/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,13 +65,6 @@ const TableCell = ({
);
const allSelected = useSelector((state) => state.selected.all);

let isEditable = true;

// Check for actual
if (window.actuals.indexOf(cellKey) > -1) {
isEditable = false;
}

const getValue = () => {
if (cell && cell.amount) {
return (cell.amount / 100).toFixed(2);
Expand All @@ -97,21 +90,22 @@ const TableCell = ({
};

const wasEdited = () => {
if (!isEditable) return false;
if (!cell.isEditable) return false;

return cell.amount !== cell.startingAmount;
};

const getClasses = () => {
const classes = ["govuk-table__cell", "forecast-month-cell", "figure-cell"];

if (!isEditable) classes.push("not-editable");
if (!cell?.isEditable) classes.push("not-editable");
if (isSelected()) classes.push("selected");
if (!cell) return classes.join(" ");

if (cell && cell.amount < 0) classes.push("negative");
if (isOverride()) classes.push("override");
if (wasEdited()) classes.push("edited");
cell?.isActual ? classes.push("is-actual") : classes.push("is-forecast");

return classes.join(" ");
};
Expand Down Expand Up @@ -223,7 +217,7 @@ const TableCell = ({
};

const isCellUpdating = () => {
if (cell && !isEditable) return false;
if (cell && !cell.isEditable) return false;

if (isUpdating) return true;

Expand Down Expand Up @@ -268,7 +262,7 @@ const TableCell = ({
className={getClasses()}
id={getId()}
onDoubleClick={() => {
if (isEditable && !isOverride()) {
if (cell.isEditable && !isOverride()) {
dispatch(
SET_EDITING_CELL({
cellId: cellId,
Expand Down
30 changes: 16 additions & 14 deletions front_end/src/Reducers/Cells.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
export const SET_CELLS = "SET_CELLS";
import { createSlice } from "@reduxjs/toolkit";

const cellsInitial = {
cells: [],
};
const allCells = createSlice({
name: "allCells",
slice: "allCells",
initialState: {
cells: [],
},
reducers: {
SET_CELLS: (state, action) => {
state.cells = action.cells;
},
},
});

export const allCells = (state = cellsInitial, action) => {
switch (action.type) {
case SET_CELLS:
return Object.assign({}, state, {
cells: action.cells,
});
default:
return state;
}
};
export const { SET_CELLS } = allCells.actions;

export default allCells.reducer;
2 changes: 1 addition & 1 deletion front_end/src/Store.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createStore, combineReducers } from "redux";
// import { persistReducer } from 'redux-persist';
// import storage from 'redux-persist/lib/storage';

import { allCells } from "./Reducers/Cells";
import allCells from "./Reducers/Cells";
import selected from "./Reducers/Selected";
import edit from "./Reducers/Edit";
import error from "./Reducers/Error";
Expand Down
10 changes: 9 additions & 1 deletion front_end/src/Util.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,13 +186,21 @@ export const processForecastData = (
overrideAmount = mappedPayrollData[forecastKey][period];
}

// FIXME: mark post mvp cleanup
// FIXME: missing cells because 0 are not locked too because no row context
const isEditable = window.FEATURES.payroll_enable_forecast
? !monthlyFigure.actual && !rowData.is_locked
: !monthlyFigure.actual;

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

Expand Down
7 changes: 6 additions & 1 deletion front_end/styles/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,13 @@ th {
background-color: rgba(86, 148, 202, 0.25);
}

.override {
.override,
.not-editable.is-forecast {
background-color: rgba(201, 155, 75, 0.25);

&.selected {
background-color: hsla(38, 90%, 20%, 0.25);
}
}

.link-button {
Expand Down
Loading
Loading