Skip to content

Commit d08204a

Browse files
authored
ORV2-3701: Use LOAs for Term Permit Applications and Amendments (#1925)
1 parent f6c19b0 commit d08204a

File tree

21 files changed

+528
-189
lines changed

21 files changed

+528
-189
lines changed

frontend/src/features/permits/helpers/permitLOA.ts

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import dayjs, { Dayjs } from "dayjs";
22

33
import { LOADetail } from "../../settings/types/LOADetail";
44
import { PermitType } from "../types/PermitType";
5-
import { getEndOfDate, toLocalDayjs } from "../../../common/helpers/formatDate";
5+
import { getEndOfDate, getStartOfDate, toLocalDayjs } from "../../../common/helpers/formatDate";
66
import { Nullable } from "../../../common/types/common";
77
import { Application, ApplicationFormData } from "../types/application";
88
import { getDefaultRequiredVal } from "../../../common/helpers/util";
@@ -80,21 +80,30 @@ export const getUpdatedLOASelection = (
8080
upToDateLOAs: LOADetail[],
8181
prevSelectedLOAs: PermitLOA[],
8282
minPermitExpiryDate: Dayjs,
83+
permitStartDate: Dayjs,
8384
) => {
8485
// Each LOA should only be selected once, but there's a chance that an up-to-date LOA is also a previously selected LOA,
8586
// which means that LOA should only be shown once.
8687
// Thus, any overlapping LOA between the up-to-date LOAs and previously selected LOAs should only be included once,
8788
// and all non-overlapping LOAs that are not part of the up-to-date LOAs should be removed
8889
const prevSelectedLOANumbers = new Set([...prevSelectedLOAs.map(loa => loa.loaNumber)]);
8990

90-
return upToDateLOAs.map(loa => {
91+
// Updated selection for LOAs, not including empty selection option "None"
92+
const updatedSelection = upToDateLOAs.map(loa => {
9193
const wasSelected = prevSelectedLOANumbers.has(loa.loaNumber);
9294
const isExpiringBeforeMinPermitExpiry = Boolean(loa.expiryDate)
9395
&& minPermitExpiryDate.isAfter(getEndOfDate(dayjs(loa.expiryDate)));
9496

95-
// Deselect and disable any LOAs expiring before min permit expiry date
96-
const isSelected = wasSelected && !isExpiringBeforeMinPermitExpiry;
97-
const isEnabled = !isExpiringBeforeMinPermitExpiry;
97+
const isStartingAfterPermitStartDate =
98+
permitStartDate.isBefore(getStartOfDate(dayjs(loa.startDate)));
99+
100+
// Deselect and disable any LOAs expiring before min permit expiry date,
101+
// or hasn't started yet (ie. LOA starts after permit start date)
102+
const isSelected = wasSelected
103+
&& !isExpiringBeforeMinPermitExpiry
104+
&& !isStartingAfterPermitStartDate;
105+
106+
const isEnabled = !isExpiringBeforeMinPermitExpiry && !isStartingAfterPermitStartDate;
98107

99108
return {
100109
loa: {
@@ -113,14 +122,23 @@ export const getUpdatedLOASelection = (
113122
disabled: !isEnabled,
114123
};
115124
});
125+
126+
// Empty LOA selection state
127+
const emptyLOASelection = {
128+
loa: null,
129+
checked: updatedSelection.filter(({ checked }) => checked).length === 0,
130+
disabled: false,
131+
};
132+
133+
return [emptyLOASelection, ...updatedSelection];
116134
};
117135

118136
/**
119137
* Get updated vehicle details and options based on selected LOAs.
120138
* @param selectedLOAs Selected LOAs for the permit
121139
* @param vehicleOptions Provided vehicle options for selection
122140
* @param prevSelectedVehicle Previously selected vehicle details in the permit form
123-
* @param eligibleSubtypes Set of eligible vehicle subtypes
141+
* @param eligibleSubtypes Set of all possible eligible vehicle subtypes
124142
* @param vehicleRestrictions Restriction rules that each vehicle must meet
125143
* @returns Updated vehicle details and filtered vehicle options
126144
*/
@@ -131,36 +149,46 @@ export const getUpdatedVehicleDetailsForLOAs = (
131149
eligibleSubtypes: Set<string>,
132150
vehicleRestrictions: ((vehicle: Vehicle) => boolean)[],
133151
) => {
152+
const isLOAUsed = selectedLOAs.length > 0;
153+
134154
const filteredVehicles = getAllowedVehicles(
135155
vehicleOptions,
136156
eligibleSubtypes,
137157
selectedLOAs,
138158
vehicleRestrictions,
139159
);
140-
141-
const filteredVehicleIds = filteredVehicles.map(filteredVehicle => ({
142-
filteredVehicleType: filteredVehicle.vehicleType,
143-
filteredVehicleId: filteredVehicle.vehicleType === VEHICLE_TYPES.TRAILER
144-
? (filteredVehicle as Trailer).trailerId
145-
: (filteredVehicle as PowerUnit).powerUnitId,
146-
}));
147-
148-
// If vehicle selected is an existing vehicle but is not in list of vehicle options
149-
// Clear the selected vehicle
150-
const { vehicleId, vehicleType } = prevSelectedVehicle;
151-
if (vehicleId && !filteredVehicleIds.some(({
152-
filteredVehicleType,
153-
filteredVehicleId,
154-
}) => filteredVehicleType === vehicleType && filteredVehicleId === vehicleId)) {
160+
161+
// If an LOA is used, then the only allowable subtype is the one defined by the LOA
162+
// Otherwise, the allowable subtypes are the ones originally belonging to the permit type
163+
const allAllowableSubtypes = isLOAUsed
164+
? new Set<string>([selectedLOAs[0].vehicleSubType])
165+
: eligibleSubtypes;
166+
167+
// If vehicle selected is an existing vehicle (was originally chosen from inventory),
168+
// but its subtype is no longer valid, then clear the selected vehicle
169+
const { vehicleId, vehicleSubType } = prevSelectedVehicle;
170+
if (vehicleId && !allAllowableSubtypes.has(vehicleSubType)) {
171+
const defaultVehicleDetails = getDefaultVehicleDetails();
172+
155173
return {
156174
filteredVehicleOptions: filteredVehicles,
157-
updatedVehicle: getDefaultVehicleDetails(),
175+
// If an LOA is used, fill the vehicle type and subtype with the LOA's vehicle type/subtype
176+
// Otherwise, simply clear the vehicle details and fill with default empty values
177+
updatedVehicle: isLOAUsed ? {
178+
...defaultVehicleDetails,
179+
vehicleType: selectedLOAs[0].vehicleType,
180+
vehicleSubType: selectedLOAs[0].vehicleSubType,
181+
} : defaultVehicleDetails,
158182
};
159183
}
160184

161185
return {
162186
filteredVehicleOptions: filteredVehicles,
163-
updatedVehicle: prevSelectedVehicle,
187+
updatedVehicle: isLOAUsed ? {
188+
...prevSelectedVehicle,
189+
vehicleType: selectedLOAs[0].vehicleType,
190+
vehicleSubType: selectedLOAs[0].vehicleSubType,
191+
} : prevSelectedVehicle,
164192
};
165193
};
166194

@@ -203,9 +231,10 @@ export const applyUpToDateLOAsToApplication = <T extends Nullable<ApplicationFor
203231
applicableLOAs,
204232
prevSelectedLOAs,
205233
minPermitExpiryDate,
234+
applicationData.permitData.startDate,
206235
)
207-
.filter(({ checked }) => checked)
208-
.map(({ loa }) => loa);
236+
.filter(({ checked, loa }) => checked && Boolean(loa))
237+
.map(({ loa }) => loa) as PermitLOA[];
209238

210239
// Update duration in permit if selected LOAs changed
211240
const durationOptions = getAvailableDurationOptions(

frontend/src/features/permits/helpers/vehicles/getAllowedVehicles.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,24 @@ export const getAllowedVehicles = (
2121
restrictions: ((vehicle: Vehicle) => boolean)[],
2222
) => {
2323
return vehicles.filter((vehicle) => {
24+
const isLOAUsed = loas.length > 0;
25+
2426
if (vehicle.vehicleType === VEHICLE_TYPES.TRAILER) {
2527
const trailer = vehicle as Trailer;
26-
return loas.some(loa => (
28+
return isLOAUsed ? loas.some(loa => (
2729
trailer.vehicleType === loa.vehicleType
2830
&& trailer.trailerTypeCode === loa.vehicleSubType
29-
)) || (
31+
)) : (
3032
eligibleSubtypes.has(trailer.trailerTypeCode)
3133
&& restrictions.every(restriction => restriction(trailer))
3234
);
3335
}
3436

3537
const powerUnit = vehicle as PowerUnit;
36-
return loas.some(loa => (
38+
return isLOAUsed ? loas.some(loa => (
3739
powerUnit.vehicleType === loa.vehicleType
3840
&& powerUnit.powerUnitTypeCode === loa.vehicleSubType
39-
)) || (
41+
)) : (
4042
eligibleSubtypes.has(powerUnit.powerUnitTypeCode)
4143
&& restrictions.every(restriction => restriction(powerUnit))
4244
);

frontend/src/features/permits/helpers/vehicles/subtypes/getEligibleSubtypeOptions.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,9 @@ export const getEligibleSubtypeOptions = (
6565
getSubtypeOptions(vehicleType, powerUnitSubtypes, trailerSubtypes),
6666
);
6767

68+
const isLOAUsed = allowedLOASubtypeCodes.size > 0;
6869
return getAllowedVehicleSubtypes(
6970
sortedVehicleSubtypes,
70-
new Set([...eligibleSubtypesCodes, ...allowedLOASubtypeCodes]),
71+
isLOAUsed ? allowedLOASubtypeCodes : eligibleSubtypesCodes,
7172
);
7273
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { Nullable } from "../../../../../common/types/common";
2+
import { PermitLOA } from "../../../types/PermitLOA";
3+
4+
/**
5+
* Determines whether or not a vehicle subtype considered invalid by policy engine
6+
* is in fact valid (eg. due to LOA).
7+
* @param policyViolations Policy engine violations, with key being name of invalid field
8+
* @param vehicleSubtype Current vehicle subtype in the form
9+
* @param selectedLOAs Currently selected LOAs, if any
10+
* @returns true if vehicle subtype is indeed valid, false otherwise
11+
*/
12+
export const shouldOverridePolicyInvalidSubtype = (
13+
policyViolations: Record<string, string>,
14+
vehicleSubtype: string,
15+
selectedLOAs?: Nullable<PermitLOA[]>,
16+
) => {
17+
// By default, if policy violations don't include subtype,
18+
// it's safe to assume that subtype is valid and hence should let it pass
19+
if (!Object.keys(policyViolations).includes("permitData.vehicleDetails.vehicleSubType"))
20+
return true;
21+
22+
// If no LOAs were selected, then an invalid subtype stays invalid
23+
if (!selectedLOAs || selectedLOAs.length === 0) return false;
24+
25+
return selectedLOAs[0].vehicleSubType === vehicleSubtype;
26+
};

frontend/src/features/permits/hooks/form/useApplicationFormContext.ts

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useContext } from "react";
1+
import { useCallback, useContext } from "react";
22
import { Policy } from "onroute-policy-engine";
33

44
import { ApplicationFormContext } from "../../context/ApplicationFormContext";
@@ -15,6 +15,7 @@ import { useVehicleConfiguration } from "../useVehicleConfiguration";
1515
import { useApplicationFormUpdateMethods } from "./useApplicationFormUpdateMethods";
1616
import { usePermittedCommodity } from "../usePermittedCommodity";
1717
import { DEFAULT_EMPTY_SELECT_VALUE } from "../../../../common/constants/constants";
18+
import { PermitVehicleDetails } from "../../types/PermitVehicleDetails";
1819

1920
export const useApplicationFormContext = () => {
2021
const applicationFormContextData = useContext(ApplicationFormContext);
@@ -138,14 +139,34 @@ export const useApplicationFormContext = () => {
138139
onSetConditions,
139140
);
140141

142+
const handleClearVehicle = useCallback((saveVehicle: boolean) => {
143+
onClearVehicle(
144+
Boolean(saveVehicle),
145+
currentSelectedLOAs.length > 0 ? {
146+
vehicleType: currentSelectedLOAs[0].vehicleType,
147+
vehicleSubtype: currentSelectedLOAs[0].vehicleSubType,
148+
} : undefined,
149+
);
150+
}, [currentSelectedLOAs]);
151+
152+
const handleSetVehicle = useCallback((vehicleDetails: PermitVehicleDetails) => {
153+
onSetVehicle({
154+
...vehicleDetails,
155+
vehicleType: currentSelectedLOAs.length > 0 ?
156+
currentSelectedLOAs[0].vehicleType : vehicleDetails.vehicleType,
157+
vehicleSubType: currentSelectedLOAs.length > 0 ?
158+
currentSelectedLOAs[0].vehicleSubType : vehicleDetails.vehicleSubType,
159+
});
160+
}, [currentSelectedLOAs]);
161+
141162
const {
142163
commodityOptions,
143164
onChangeCommodityType,
144165
} = usePermittedCommodity(
145166
policyEngine,
146167
permitType,
147168
onSetCommodityType,
148-
() => onClearVehicle(Boolean(vehicleFormData.saveVehicle)),
169+
() => handleClearVehicle(Boolean(vehicleFormData.saveVehicle)),
149170
onClearVehicleConfig,
150171
permittedCommodity?.commodityType,
151172
);
@@ -164,7 +185,6 @@ export const useApplicationFormContext = () => {
164185
selectedLOAs: currentSelectedLOAs,
165186
powerUnitSubtypeNamesMap,
166187
trailerSubtypeNamesMap,
167-
onClearVehicle: () => onClearVehicle(Boolean(vehicleFormData.saveVehicle)),
168188
selectedCommodity: permittedCommodity?.commodityType,
169189
});
170190

@@ -253,8 +273,8 @@ export const useApplicationFormContext = () => {
253273
onSetExpiryDate,
254274
onSetConditions,
255275
onToggleSaveVehicle,
256-
onSetVehicle,
257-
onClearVehicle,
276+
onSetVehicle: handleSetVehicle,
277+
onClearVehicle: handleClearVehicle,
258278
onUpdateLOAs,
259279
onUpdateHighwaySequence,
260280
onUpdateTripOrigin,

frontend/src/features/permits/hooks/form/useApplicationFormUpdateMethods.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ import { EMPTY_VEHICLE_DETAILS, PermitVehicleDetails } from "../../types/PermitV
99
import { ApplicationFormData } from "../../types/application";
1010
import { getDefaultVehicleConfiguration } from "../../helpers/vehicles/configuration/getDefaultVehicleConfiguration";
1111
import { PermitType } from "../../types/PermitType";
12-
import { RequiredOrNull } from "../../../../common/types/common";
12+
import { Nullable, RequiredOrNull } from "../../../../common/types/common";
1313
import { ThirdPartyLiability } from "../../types/ThirdPartyLiability";
14+
import { VehicleType } from "../../../manageVehicles/types/Vehicle";
1415

1516
/**
1617
* Hook that returns custom methods that update specific values in the application form.
@@ -39,15 +40,67 @@ export const useApplicationFormUpdateMethods = () => {
3940
setValue("permitData.vehicleDetails.saveVehicle", saveVehicle);
4041
}, [setValue]);
4142

43+
const onSetVin = useCallback((vin: string) => {
44+
setValue("permitData.vehicleDetails.vin", vin);
45+
}, [setValue]);
46+
47+
const onSetPlate = useCallback((plate: string) => {
48+
setValue("permitData.vehicleDetails.plate", plate);
49+
}, [setValue]);
50+
51+
const onSetMake = useCallback((make: string) => {
52+
setValue("permitData.vehicleDetails.make", make);
53+
}, [setValue]);
54+
55+
const onSetYear = useCallback((year: Nullable<number>) => {
56+
setValue("permitData.vehicleDetails.year", year);
57+
}, [setValue]);
58+
59+
const onSetCountryCode = useCallback((countryCode: string) => {
60+
setValue("permitData.vehicleDetails.countryCode", countryCode);
61+
}, [setValue]);
62+
63+
const onSetProvinceCode = useCallback((provinceCode: string) => {
64+
setValue("permitData.vehicleDetails.provinceCode", provinceCode);
65+
}, [setValue]);
66+
67+
const onSetVehicleType = useCallback((vehicleType: string) => {
68+
setValue("permitData.vehicleDetails.vehicleType", vehicleType);
69+
}, [setValue]);
70+
71+
const onSetVehicleSubtype = useCallback((vehicleSubtype: string) => {
72+
setValue("permitData.vehicleDetails.vehicleSubType", vehicleSubtype);
73+
}, [setValue]);
74+
75+
const onSetUnitNumber = useCallback((unitNumber: Nullable<string>) => {
76+
setValue("permitData.vehicleDetails.unitNumber", unitNumber);
77+
}, [setValue]);
78+
79+
const onSetVehicleId = useCallback((vehicleId: Nullable<string>) => {
80+
setValue("permitData.vehicleDetails.vehicleId", vehicleId);
81+
}, [setValue]);
82+
83+
const onSetLicensedGVW = useCallback((licensedGVW?: Nullable<number>) => {
84+
setValue("permitData.vehicleDetails.licensedGVW", licensedGVW);
85+
}, [setValue]);
86+
4287
const onSetVehicle = useCallback((vehicleDetails: PermitVehicleDetails) => {
4388
setValue("permitData.vehicleDetails", {
4489
...vehicleDetails,
4590
});
4691
}, [setValue]);
4792

48-
const onClearVehicle = useCallback((saveVehicle: boolean) => {
93+
const onClearVehicle = useCallback((
94+
saveVehicle: boolean,
95+
defaultTypes?: Nullable<{
96+
vehicleType: VehicleType;
97+
vehicleSubtype: string;
98+
}>,
99+
) => {
49100
setValue("permitData.vehicleDetails", {
50101
...EMPTY_VEHICLE_DETAILS,
102+
vehicleType: defaultTypes ? defaultTypes.vehicleType : EMPTY_VEHICLE_DETAILS.vehicleType,
103+
vehicleSubType: defaultTypes ? defaultTypes.vehicleSubtype : EMPTY_VEHICLE_DETAILS.vehicleSubType,
51104
saveVehicle,
52105
});
53106
}, [setValue]);
@@ -133,6 +186,17 @@ export const useApplicationFormUpdateMethods = () => {
133186
onSetExpiryDate,
134187
onSetConditions,
135188
onToggleSaveVehicle,
189+
onSetVin,
190+
onSetPlate,
191+
onSetMake,
192+
onSetYear,
193+
onSetCountryCode,
194+
onSetProvinceCode,
195+
onSetVehicleType,
196+
onSetVehicleSubtype,
197+
onSetUnitNumber,
198+
onSetVehicleId,
199+
onSetLicensedGVW,
136200
onSetVehicle,
137201
onClearVehicle,
138202
onUpdateLOAs,

0 commit comments

Comments
 (0)