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

Feat: Enforce mandatory fuel code validation for schedules - 1429 #1461

Merged
merged 4 commits into from
Dec 17, 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
1 change: 1 addition & 0 deletions backend/lcfs/web/api/fuel_export/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ async def create_fuel_export(self, fuel_export: FuelExport) -> FuelExport:
"fuel_type",
"provision_of_the_act",
"end_use_type",
"fuel_code",
],
)
return fuel_export
Expand Down
2 changes: 2 additions & 0 deletions backend/lcfs/web/application.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import logging
import os
import debugpy
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need these new imports?

import uuid

import structlog
Expand Down
1 change: 1 addition & 0 deletions frontend/src/assets/locales/en/allocationAgreement.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"noAllocationAgreementsFound": "No allocation agreements found",
"addAllocationAgreementRowsTitle": "Allocation agreements (e.g., allocating responsibility for fuel)",
"allocationAgreementSubtitle": "Enter allocation agreement details below",
"fuelCodeFieldRequiredError": "Error updating row: Fuel code field required",
"allocationAgreementColLabels": {
"transaction": "Responsibility",
"transactionPartner": "Legal name of transaction partner",
Expand Down
3 changes: 2 additions & 1 deletion frontend/src/assets/locales/en/fuelExport.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@
},
"validateMsg": {
"isRequired": "{{field}} is required"
}
},
"fuelCodeFieldRequiredError": "Error updating row: Fuel code field required"
}
1 change: 1 addition & 0 deletions frontend/src/assets/locales/en/fuelSupply.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"LoadFailMsg": "Failed to load supply of fuel rows",
"addRow": "Add row",
"rows": "rows",
"fuelCodeFieldRequiredError": "Error updating row: Fuel code field required",
"fuelSupplyColLabels": {
"complianceReportId": "Compliance Report ID",
"fuelSupplyId": "Fuel supply ID",
Expand Down
1 change: 1 addition & 0 deletions frontend/src/assets/locales/en/otherUses.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"approveConfirmText": "Are you sure you want to approve this other use entry?",
"addRow": "Add row",
"rows": "rows",
"fuelCodeFieldRequiredError": "Error updating row: Fuel code field required",
"otherUsesColLabels": {
"complianceReportId": "Compliance report ID",
"fuelType": "Fuel type",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import BCTypography from '@/components/BCTypography'
import Grid2 from '@mui/material/Unstable_Grid2/Grid2'
import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { BCAlert2 } from '@/components/BCAlert'
import BCBox from '@/components/BCBox'
import { BCGridEditor } from '@/components/BCDataGrid/BCGridEditor'
import {
Expand Down Expand Up @@ -169,6 +168,29 @@ export const AddEditAllocationAgreements = () => {
updatedData.ciOfFuel = DEFAULT_CI_FUEL[updatedData.fuelCategory]
}

const isFuelCodeScenario =
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
if (isFuelCodeScenario && !updatedData.fuelCode) {
// Fuel code is required but not provided
setErrors((prevErrors) => ({
...prevErrors,
[params.node.data.id]: ['fuelCode']
}))

alertRef.current?.triggerAlert({
message: t('allocationAgreement:fuelCodeFieldRequiredError'),
severity: 'error'
})

updatedData = {
...updatedData,
validationStatus: 'error'
}

params.node.updateData(updatedData)
return // Stop execution, do not proceed to save
}

try {
setErrors({})
await saveRow(updatedData)
Expand Down
78 changes: 74 additions & 4 deletions frontend/src/views/AllocationAgreements/_schema.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ export const allocationAgreementColDefs = (optionsData, errors) => [
params.data.units = fuelType?.units
params.data.unrecognized = fuelType?.unrecognized
params.data.provisionOfTheAct = null
params.data.fuelCode = undefined
}
return true
},
Expand Down Expand Up @@ -302,16 +303,85 @@ export const allocationAgreementColDefs = (optionsData, errors) => [
}),
cellStyle: (params) => {
const style = StandardCellErrors(params, errors)
const conditionalStyle =
const isFuelCodeScenario =
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
? { backgroundColor: '#fff', borderColor: 'unset' }
: { backgroundColor: '#f2f2f2' }
const fuelType = optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
)
const fuelCodes = fuelType?.fuelCodes || []
const fuelCodeRequiredAndMissing =
isFuelCodeScenario && !params.data.fuelCode

let conditionalStyle = {}

// If required and missing, show red border and white background
if (fuelCodeRequiredAndMissing) {
style.borderColor = 'red'
style.backgroundColor = '#fff'
} else {
// Apply conditional styling if not missing
conditionalStyle =
isFuelCodeScenario && fuelCodes.length > 0
? {
backgroundColor: '#fff',
borderColor: style.borderColor || 'unset'
}
: { backgroundColor: '#f2f2f2' }
}

return { ...style, ...conditionalStyle }
},
suppressKeyboardEvent,
minWidth: 150,
editable: (params) =>
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE,
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE &&
optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
)?.fuelCodes?.length > 0,
valueGetter: (params) => {
const fuelTypeObj = optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
)
if (!fuelTypeObj) return params.data.fuelCode

const isFuelCodeScenario =
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
const fuelCodes =
fuelTypeObj.fuelCodes?.map((item) => item.fuelCode) || []

if (isFuelCodeScenario && !params.data.fuelCode) {
// Autopopulate if only one fuel code is available
if (fuelCodes.length === 1) {
const singleFuelCode = fuelTypeObj.fuelCodes[0]
params.data.fuelCode = singleFuelCode.fuelCode
params.data.fuelCodeId = singleFuelCode.fuelCodeId
}
}

return params.data.fuelCode
},
valueSetter: (params) => {
if (params.newValue) {
params.data.fuelCode = params.newValue

const fuelType = optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
)
if (params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE) {
const matchingFuelCode = fuelType?.fuelCodes?.find(
(fuelCode) => params.data.fuelCode === fuelCode.fuelCode
)
if (matchingFuelCode) {
params.data.fuelCodeId = matchingFuelCode.fuelCodeId
}
}
} else {
// If user clears the value
params.data.fuelCode = undefined
params.data.fuelCodeId = undefined
}
return true
},
tooltipValueGetter: (p) => 'Select the approved fuel code'
},
{
Expand Down
34 changes: 32 additions & 2 deletions frontend/src/views/FuelExports/AddEditFuelExports.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { useLocation, useNavigate, useParams } from 'react-router-dom'
import { v4 as uuid } from 'uuid'
import { defaultColDef, fuelExportColDefs } from './_schema'
import {
defaultColDef,
fuelExportColDefs,
PROVISION_APPROVED_FUEL_CODE
} from './_schema'

export const AddEditFuelExports = () => {
const [rowData, setRowData] = useState([])
Expand Down Expand Up @@ -143,7 +147,33 @@ export const AddEditFuelExports = () => {
acc[key] = value
return acc
}, {})

updatedData.compliancePeriod = compliancePeriod

// Local validation before saving
const isFuelCodeScenario =
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE

if (isFuelCodeScenario && !updatedData.fuelCode) {
// Fuel code is required but not provided
setErrors((prevErrors) => ({
...prevErrors,
[params.node.data.id]: ['fuelCode']
}))

alertRef.current?.triggerAlert({
message: t('fuelExport:fuelCodeFieldRequiredError'),
severity: 'error'
})

updatedData = {
...updatedData,
validationStatus: 'error'
}
params.node.updateData(updatedData)
return // Don't proceed with save
}

try {
setErrors({})
await saveRow(updatedData)
Expand Down Expand Up @@ -189,7 +219,7 @@ export const AddEditFuelExports = () => {

params.node.updateData(updatedData)
},
[saveRow, t]
[saveRow, t, compliancePeriod]
)

const onAction = async (action, params) => {
Expand Down
99 changes: 83 additions & 16 deletions frontend/src/views/FuelExports/_schema.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
fuelTypeOtherConditionalStyle
} from '@/utils/fuelTypeOther'

export const PROVISION_APPROVED_FUEL_CODE = 'Fuel code - section 19 (b) (i)'

const cellErrorStyle = (params, errors) => {
let style = {}
if (
Expand Down Expand Up @@ -318,29 +320,94 @@ export const fuelExportColDefs = (optionsData, errors) => [
field: 'fuelCode',
headerName: i18n.t('fuelExport:fuelExportColLabels.fuelCode'),
cellEditor: 'agSelectCellEditor',
cellEditorParams: (params) => ({
values: optionsData?.fuelTypes
?.find((obj) => params.data.fuelType === obj.fuelType)
?.fuelCodes.map((item) => item.fuelCode)
}),
suppressKeyboardEvent,
minWidth: 135,
cellEditorParams: (params) => {
const fuelTypeObj = optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
)
return {
values: fuelTypeObj?.fuelCodes?.map((item) => item.fuelCode) || []
}
},
cellStyle: (params) => {
const style = cellErrorStyle(params, errors)
const fuelTypeObj = optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
)
const fuelCodes =
fuelTypeObj?.fuelCodes.map((item) => item.fuelCode) || []
const isFuelCodeScenario =
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE

// Check if fuel code is required (scenario) but missing
const fuelCodeRequiredAndMissing =
isFuelCodeScenario && !params.data.fuelCode

if (fuelCodeRequiredAndMissing) {
// If required and missing, force a red border
style.borderColor = 'red'
}

const conditionalStyle =
optionsData?.fuelTypes
?.find((obj) => params.data.fuelType === obj.fuelType)
?.fuelCodes.map((item) => item.fuelCode).length > 0 &&
/Fuel code/i.test(params.data.provisionOfTheAct)
fuelCodes.length > 0 &&
isFuelCodeScenario &&
!fuelCodeRequiredAndMissing
? { backgroundColor: '#fff', borderColor: 'unset' }
: { backgroundColor: '#f2f2f2' }
return { ...style, ...conditionalStyle }
},
suppressKeyboardEvent,
minWidth: 135,
editable: (params) =>
optionsData?.fuelTypes
?.find((obj) => params.data.fuelType === obj.fuelType)
?.fuelCodes.map((item) => item.fuelCode).length > 0 &&
/Fuel code/i.test(params.data.provisionOfTheAct)
editable: (params) => {
const fuelTypeObj = optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
)
const fuelCodes = fuelTypeObj?.fuelCodes || []
return (
fuelCodes.length > 0 &&
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
)
},
valueGetter: (params) => {
const fuelTypeObj = optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
)
if (!fuelTypeObj) return params.data.fuelCode

const isFuelCodeScenario =
params.data.provisionOfTheAct === PROVISION_APPROVED_FUEL_CODE
const fuelCodes =
fuelTypeObj.fuelCodes?.map((item) => item.fuelCode) || []

if (isFuelCodeScenario && !params.data.fuelCode) {
// Autopopulate if only one fuel code is available
if (fuelCodes.length === 1) {
const singleFuelCode = fuelTypeObj.fuelCodes[0]
params.data.fuelCode = singleFuelCode.fuelCode
params.data.fuelCodeId = singleFuelCode.fuelCodeId
}
}

return params.data.fuelCode
},
valueSetter: (params) => {
const newCode = params.newValue
const fuelTypeObj = optionsData?.fuelTypes?.find(
(obj) => params.data.fuelType === obj.fuelType
)
const selectedFuelCodeObj = fuelTypeObj?.fuelCodes.find(
(item) => item.fuelCode === newCode
)

if (selectedFuelCodeObj) {
params.data.fuelCode = selectedFuelCodeObj.fuelCode
params.data.fuelCodeId = selectedFuelCodeObj.fuelCodeId
} else {
params.data.fuelCode = undefined
params.data.fuelCodeId = undefined
}

return true
}
},
{
field: 'quantity',
Expand Down
Loading
Loading