Skip to content

Commit f473877

Browse files
authored
ORV2-2846: CV Client and Staff Applying for Motive-Fuel Permits (#1791)
1 parent 6984183 commit f473877

40 files changed

+572
-211
lines changed

frontend/src/common/constants/bannerMessages.ts

+2
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ export const BANNER_MESSAGES = {
3939
EXAMPLE:
4040
"e.g. If the origin is Victoria, BC and the destination is Hope, BC, the sequence of highways travelled in order will be 17 1 3.",
4141
},
42+
TOTAL_DISTANCE:
43+
"The total distance, in km, is the distance that will be travelled within BC (or from/to BC border). This is to include the return trip distance.",
4244
BRIDGE_FORMULA_CALCULATION_TOOL:
4345
"This tool only calculates Bridge Formula, which is a mathematical equation that is used to calculate the maximum allowable weight allowed by permit for various axle groups in a combination. This tool is not confirming compliance with the CTR or CTPM.\n\nThe image on the right is for illustration purposes only.",
4446
};

frontend/src/common/constants/validation_messages.json

+3
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,9 @@
1010
"NaN": {
1111
"defaultMessage": "Must be a number"
1212
},
13+
"invalid": {
14+
"defaultMessage": "Invalid input"
15+
},
1316
"greaterThan": {
1417
"messageTemplate": "Must be greater than :val.",
1518
"placeholders": [":val"]

frontend/src/common/helpers/validationMessages.ts

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ export const requiredMessage = () => validationMessages.required.defaultMessage;
1616
export const selectionRequired = () =>
1717
validationMessages.selectionRequired.defaultMessage;
1818
export const invalidNumber = () => validationMessages.NaN.defaultMessage;
19+
20+
export const invalidInput = () => validationMessages.invalid.defaultMessage;
21+
1922
export const mustBeGreaterThan = (val: number) => {
2023
const { messageTemplate, placeholders } = validationMessages.greaterThan;
2124
return replacePlaceholders(messageTemplate, placeholders, val);

frontend/src/features/permits/components/dashboard/ApplicationStepPage.tsx

+9-4
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ export const ApplicationStepPage = ({
6363

6464
const { data: featureFlags } = useFeatureFlagsQuery();
6565
const enableSTOS = featureFlags?.["STOS"] === "ENABLED";
66+
const enableMFP = featureFlags?.["MFP"] === "ENABLED";
6667

6768
// Query for the application data whenever this page is rendered
6869
const {
@@ -99,11 +100,15 @@ export const ApplicationStepPage = ({
99100
applicationData?.permitType,
100101
);
101102

102-
// Currently onRouteBC only handles TROS and TROW permits, and STOS only if feature flag is enabled
103+
// Currently onRouteBC only handles TROS and TROW permits, and STOS and MFP only if feature flag is enabled
103104
const isPermitTypeAllowed = () => {
104-
const allowedPermitTypes: string[] = enableSTOS
105-
? [PERMIT_TYPES.TROS, PERMIT_TYPES.TROW, PERMIT_TYPES.STOS]
106-
: [PERMIT_TYPES.TROS, PERMIT_TYPES.TROW];
105+
const allowedPermitTypes: string[] = [
106+
PERMIT_TYPES.TROS,
107+
PERMIT_TYPES.TROW,
108+
PERMIT_TYPES.STOS,
109+
PERMIT_TYPES.MFP,
110+
].filter(pType => enableSTOS ? true : pType !== PERMIT_TYPES.STOS)
111+
.filter(pType => enableMFP ? true : pType !== PERMIT_TYPES.MFP);
107112

108113
return allowedPermitTypes.includes(applicationPermitType);
109114
};

frontend/src/features/permits/components/feeSummary/FeeSummary.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
1+
import "./FeeSummary.scss";
12
import { Nullable } from "../../../../common/types/common";
23
import { feeSummaryDisplayText } from "../../helpers/feeSummary";
34
import { getPermitTypeName, PermitType } from "../../types/PermitType";
4-
import "./FeeSummary.scss";
55

66
export const FeeSummary = ({
77
permitType,
88
feeSummary,
99
permitDuration,
1010
hideDescriptions,
11+
permittedRouteTotalDistance,
1112
}: {
1213
permitType?: Nullable<PermitType>;
1314
feeSummary?: Nullable<string>;
1415
permitDuration?: number;
1516
hideDescriptions?: boolean;
17+
permittedRouteTotalDistance?: Nullable<number>;
1618
}) => {
1719
const feeDisplayText = feeSummaryDisplayText(
1820
feeSummary,
1921
permitDuration,
2022
permitType,
23+
permittedRouteTotalDistance,
2124
);
2225

2326
return (

frontend/src/features/permits/constants/constants.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -62,10 +62,10 @@ export const ALL_PERMIT_TYPE_CHOOSE_FROM_OPTIONS: PermitTypeChooseFromItem[] = [
6262
// label: getPermitTypeShortName(permitType),
6363
// })),
6464
// },
65-
// {
66-
// value: PERMIT_TYPES.MFP,
67-
// label: getPermitTypeShortName(PERMIT_TYPES.MFP),
68-
// },
65+
{
66+
value: PERMIT_TYPES.MFP,
67+
label: getPermitTypeShortName(PERMIT_TYPES.MFP),
68+
},
6969
];
7070

7171
export interface PermitTypeChooseFromItem {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { PermitCondition } from "../types/PermitCondition";
2+
3+
export const MFP_CONDITIONS: PermitCondition[] = [
4+
{
5+
description: "Motive Fuel Tax Act",
6+
condition: "MV4001",
7+
conditionLink: "https://www.th.gov.bc.ca/forms/getForm.aspx?formId=1539",
8+
checked: true,
9+
disabled: true
10+
},
11+
];
12+
13+
export const MANDATORY_MFP_CONDITIONS: PermitCondition[] = [...MFP_CONDITIONS];
14+
15+
export const MIN_MFP_DURATION = 1;
16+
export const MAX_MFP_DURATION = 7;
17+
export const MFP_DURATION_OPTIONS = [
18+
{ value: MIN_MFP_DURATION, label: "1 Day" },
19+
{ value: 2, label: "2 Days" },
20+
{ value: 3, label: "3 Days" },
21+
{ value: 4, label: "4 Days" },
22+
{ value: 5, label: "5 Days" },
23+
{ value: 6, label: "6 Days" },
24+
{ value: MAX_MFP_DURATION, label: "7 Days" },
25+
];
26+
27+
export const MFP_DURATION_INTERVAL_DAYS = 1;

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

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { isVehicleSubtypeLCV } from "../../manageVehicles/helpers/vehicleSubtypes";
22
import { LCV_CONDITION } from "../constants/constants";
3+
import { MANDATORY_MFP_CONDITIONS, MFP_CONDITIONS } from "../constants/mfp";
34
import { MANDATORY_STOS_CONDITIONS, STOS_CONDITIONS } from "../constants/stos";
45
import { MANDATORY_TROS_CONDITIONS, TROS_CONDITIONS } from "../constants/tros";
56
import { MANDATORY_TROW_CONDITIONS, TROW_CONDITIONS } from "../constants/trow";
@@ -18,6 +19,8 @@ export const getMandatoryConditions = (
1819
) => {
1920
const additionalConditions = includeLcvCondition ? [LCV_CONDITION] : [];
2021
switch (permitType) {
22+
case PERMIT_TYPES.MFP:
23+
return MANDATORY_MFP_CONDITIONS.concat(additionalConditions);
2124
case PERMIT_TYPES.STOS:
2225
return MANDATORY_STOS_CONDITIONS.concat(additionalConditions);
2326
case PERMIT_TYPES.TROW:
@@ -35,6 +38,8 @@ const getConditionsByPermitType = (
3538
) => {
3639
const additionalConditions = includeLcvCondition ? [LCV_CONDITION] : [];
3740
switch (permitType) {
41+
case PERMIT_TYPES.MFP:
42+
return MFP_CONDITIONS.concat(additionalConditions);
3843
case PERMIT_TYPES.STOS:
3944
return STOS_CONDITIONS.concat(additionalConditions);
4045
case PERMIT_TYPES.TROW:

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

+15
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,22 @@ import {
2929
STOS_DURATION_OPTIONS,
3030
} from "../constants/stos";
3131

32+
import {
33+
MAX_MFP_DURATION,
34+
MFP_DURATION_INTERVAL_DAYS,
35+
MFP_DURATION_OPTIONS,
36+
MIN_MFP_DURATION,
37+
} from "../constants/mfp";
38+
3239
/**
3340
* Get list of selectable duration options for a given permit type.
3441
* @param permitType Permit type to get duration options for
3542
* @returns List of selectable duration options for the given permit type
3643
*/
3744
export const durationOptionsForPermitType = (permitType: PermitType) => {
3845
switch (permitType) {
46+
case PERMIT_TYPES.MFP:
47+
return MFP_DURATION_OPTIONS;
3948
case PERMIT_TYPES.STOS:
4049
return STOS_DURATION_OPTIONS;
4150
case PERMIT_TYPES.TROW:
@@ -54,6 +63,8 @@ export const durationOptionsForPermitType = (permitType: PermitType) => {
5463
*/
5564
export const minDurationForPermitType = (permitType: PermitType) => {
5665
switch (permitType) {
66+
case PERMIT_TYPES.MFP:
67+
return MIN_MFP_DURATION;
5768
case PERMIT_TYPES.STOS:
5869
return MIN_STOS_DURATION;
5970
case PERMIT_TYPES.TROW:
@@ -72,6 +83,8 @@ export const minDurationForPermitType = (permitType: PermitType) => {
7283
*/
7384
export const maxDurationForPermitType = (permitType: PermitType) => {
7485
switch (permitType) {
86+
case PERMIT_TYPES.MFP:
87+
return MAX_MFP_DURATION;
7588
case PERMIT_TYPES.STOS:
7689
return MAX_STOS_DURATION;
7790
case PERMIT_TYPES.TROW:
@@ -90,6 +103,8 @@ export const maxDurationForPermitType = (permitType: PermitType) => {
90103
*/
91104
export const getDurationIntervalDays = (permitType: PermitType) => {
92105
switch (permitType) {
106+
case PERMIT_TYPES.MFP:
107+
return MFP_DURATION_INTERVAL_DAYS;
93108
case PERMIT_TYPES.STOS:
94109
return STOS_DURATION_INTERVAL_DAYS;
95110
case PERMIT_TYPES.TROW:

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

+33-17
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { arePermitLOADetailsEqual, PermitLOA } from "../types/PermitLOA";
99
import { areOrderedSequencesEqual, doUniqueArraysHaveSameObjects } from "../../../common/helpers/equality";
1010
import { ReplaceDayjsWithString } from "../types/utility";
1111
import { PermittedCommodity } from "../types/PermittedCommodity";
12-
import { PermittedRoute } from "../types/PermittedRoute";
12+
import { ManualRoute, PermittedRoute } from "../types/PermittedRoute";
1313
import { PermitVehicleConfiguration } from "../types/PermitVehicleConfiguration";
1414

1515
/**
@@ -191,6 +191,35 @@ export const areVehicleConfigurationsEqual = (
191191
);
192192
};
193193

194+
/**
195+
* Compare whether or not the manual route details for two permits are equal.
196+
* @param manualRoute1 Manual route details belonging to the first permit
197+
* @param manualRoute2 Manual route details belonging to the second permit
198+
* @returns true when the manual route details are considered equivalent, false otherwise
199+
*/
200+
export const areManualRoutesEqual = (
201+
manualRoute1?: Nullable<ManualRoute>,
202+
manualRoute2?: Nullable<ManualRoute>,
203+
) => {
204+
return (
205+
getDefaultRequiredVal("", manualRoute1?.origin)
206+
=== getDefaultRequiredVal("", manualRoute2?.origin)
207+
) && (
208+
getDefaultRequiredVal("", manualRoute1?.destination)
209+
=== getDefaultRequiredVal("", manualRoute2?.destination)
210+
) && (
211+
getDefaultRequiredVal("", manualRoute1?.exitPoint)
212+
=== getDefaultRequiredVal("", manualRoute2?.exitPoint)
213+
) && (
214+
getDefaultRequiredVal(0, manualRoute1?.totalDistance)
215+
=== getDefaultRequiredVal(0, manualRoute2?.totalDistance)
216+
) && areOrderedSequencesEqual(
217+
manualRoute1?.highwaySequence,
218+
manualRoute2?.highwaySequence,
219+
(seqNumber1, seqNumber2) => seqNumber1 === seqNumber2,
220+
);
221+
};
222+
194223
/**
195224
* Compare whether or not the permitted route details for two permits are equal.
196225
* @param permittedRoute1 Permitted route details belonging to the first permit
@@ -201,25 +230,12 @@ export const arePermittedRoutesEqual = (
201230
permittedRoute1?: Nullable<PermittedRoute>,
202231
permittedRoute2?: Nullable<PermittedRoute>,
203232
) => {
204-
return (
205-
getDefaultRequiredVal("", permittedRoute1?.manualRoute?.origin)
206-
=== getDefaultRequiredVal("", permittedRoute2?.manualRoute?.origin)
207-
) && (
208-
getDefaultRequiredVal("", permittedRoute1?.manualRoute?.destination)
209-
=== getDefaultRequiredVal("", permittedRoute2?.manualRoute?.destination)
210-
) && (
211-
getDefaultRequiredVal("", permittedRoute1?.manualRoute?.exitPoint)
212-
=== getDefaultRequiredVal("", permittedRoute2?.manualRoute?.exitPoint)
213-
) && (
214-
getDefaultRequiredVal(0, permittedRoute1?.manualRoute?.totalDistance)
215-
=== getDefaultRequiredVal(0, permittedRoute2?.manualRoute?.totalDistance)
233+
return areManualRoutesEqual(
234+
permittedRoute1?.manualRoute,
235+
permittedRoute2?.manualRoute,
216236
) && (
217237
getDefaultRequiredVal("", permittedRoute1?.routeDetails)
218238
=== getDefaultRequiredVal("", permittedRoute2?.routeDetails)
219-
) && areOrderedSequencesEqual(
220-
permittedRoute1?.manualRoute?.highwaySequence,
221-
permittedRoute2?.manualRoute?.highwaySequence,
222-
(seqNumber1, seqNumber2) => seqNumber1 === seqNumber2,
223239
);
224240
};
225241

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

+21-8
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,23 @@ import {
99
getDurationIntervalDays,
1010
maxDurationForPermitType,
1111
} from "./dateSelection";
12+
1213
import {
1314
applyWhenNotNullable,
1415
getDefaultRequiredVal,
1516
} from "../../../common/helpers/util";
1617

1718
/**
18-
* Calculates the fee for a permit only by its duration.
19+
* Calculates the fee for a permit.
1920
* @param permitType Type of permit
2021
* @param duration Number of days for duration of permit
21-
* @returns Fee to be paid for the permit duration
22+
* @param totalDistance Total distance to travel for the route trip of the permit, if applicable
23+
* @returns Fee to be paid for the permit
2224
*/
23-
export const calculateFeeByDuration = (
25+
export const calculatePermitFee = (
2426
permitType: PermitType,
2527
duration: number,
28+
totalDistance?: Nullable<number>,
2629
) => {
2730
const maxAllowableDuration = maxDurationForPermitType(permitType);
2831

@@ -43,6 +46,10 @@ export const calculateFeeByDuration = (
4346

4447
switch (permitType) {
4548
// Add more conditions for other permit types if needed
49+
case PERMIT_TYPES.MFP:
50+
// MFP only calculate fee based on totalDistance
51+
// minimum $20 no matter what, $0.11 per km
52+
return Math.max(20, 0.11 * getDefaultRequiredVal(0, totalDistance));
4653
case PERMIT_TYPES.STOS:
4754
// STOS have constant fee of $15 (regardless of duration)
4855
return 15;
@@ -61,20 +68,22 @@ export const calculateFeeByDuration = (
6168
* @param feeSummary fee summary field for a permit (if exists)
6269
* @param duration duration field for a permit (if exists)
6370
* @param permitType type of permit (if exists)
71+
* @param totalDistance total distance to travel for the route trip of a permit (if applicable)
6472
* @returns display text for the fee summary (currency amount to 2 decimal places)
6573
*/
6674
export const feeSummaryDisplayText = (
6775
feeSummary?: Nullable<string>,
6876
duration?: Nullable<number>,
6977
permitType?: Nullable<PermitType>,
78+
totalDistance?: Nullable<number>,
7079
) => {
7180
const feeFromSummary = applyWhenNotNullable(
7281
(numericStr) => Number(numericStr).toFixed(2),
7382
feeSummary,
7483
);
7584
const feeFromDuration =
7685
duration && permitType
77-
? calculateFeeByDuration(permitType, duration).toFixed(2)
86+
? calculatePermitFee(permitType, duration, totalDistance).toFixed(2)
7887
: null;
7988

8089
const fee = getDefaultRequiredVal("0.00", feeFromSummary, feeFromDuration);
@@ -113,25 +122,29 @@ export const calculateNetAmount = (permitHistory: PermitHistory[]) => {
113122
};
114123

115124
/**
116-
* Calculates the amount that needs to be refunded (or paid if amount is negative) for a permit given a new duration period.
125+
* Calculates the amount that needs to be refunded (or paid if amount is negative) for a permit.
117126
* @param permitHistory List of history objects that make up the history of a permit and its transactions
118127
* @param currDuration Current (updated) duration of the permit
119-
* @param currPermitType Permit type of current permit to refund
128+
* @param currPermitType Permit type of current permit to refund for
129+
* @param updatedTotalDistance Updated total distance of the route trip for the permit (if applicable)
120130
* @returns Amount that needs to be refunded, or if negative then the amount that still needs to be paid
121131
*/
122132
export const calculateAmountToRefund = (
123133
permitHistory: PermitHistory[],
124134
currDuration: number,
125135
currPermitType: PermitType,
136+
updatedTotalDistance?: Nullable<number>,
126137
) => {
127138
const netPaid = calculateNetAmount(permitHistory);
128139
if (isZeroAmount(netPaid)) return 0; // If total paid is $0 (eg. no-fee permits), then refund nothing
129140

130-
const feeForCurrDuration = calculateFeeByDuration(
141+
const updatedFee = calculatePermitFee(
131142
currPermitType,
132143
currDuration,
144+
updatedTotalDistance,
133145
);
134-
return netPaid - feeForCurrDuration;
146+
147+
return netPaid - updatedFee;
135148
};
136149

137150
/**

0 commit comments

Comments
 (0)