diff --git a/database/mssql/test/versions/v_43_1_test.sql b/database/mssql/test/versions/v_43_1_test.sql
index 92807f1ed..7b004ecec 100644
--- a/database/mssql/test/versions/v_43_1_test.sql
+++ b/database/mssql/test/versions/v_43_1_test.sql
@@ -2,4 +2,4 @@
SET NOCOUNT ON
SELECT COUNT(*) FROM $(DB_NAME).[dops].[ORBC_DOCUMENT_TEMPLATE]
-WHERE TEMPLATE_NAME IN ('PERMIT','PERMIT_STOS_VOID','PERMIT_STOS_REVOKED')
\ No newline at end of file
+WHERE TEMPLATE_NAME IN ('PERMIT','PERMIT_STOS_VOID','PERMIT_STOS_REVOKED')
diff --git a/database/mssql/test/versions/v_43_test.sh b/database/mssql/test/versions/v_43_test.sh
index 867f594cb..db30936c7 100644
--- a/database/mssql/test/versions/v_43_test.sh
+++ b/database/mssql/test/versions/v_43_test.sh
@@ -21,4 +21,4 @@ if [[ $TEST_43_2_RESULT -eq 3 ]]; then
echo "Test 43.2 passed: STOS templates setup successfully in ORBC_DOCUMENT"
else
echo "******** Test 43.2 failed: Failed to setup STOS permit templates"
-fi
\ No newline at end of file
+fi
diff --git a/database/mssql/test/versions/v_45_test.sh b/database/mssql/test/versions/v_45_test.sh
index e9e2da838..5d62366c2 100644
--- a/database/mssql/test/versions/v_45_test.sh
+++ b/database/mssql/test/versions/v_45_test.sh
@@ -13,4 +13,4 @@ if [[ $TEST_45_1_RESULT -eq 7 ]]; then
echo "Test 45.1 passed: GL_PROJ_CODE column created in ORBC_PAYMENT_METHOD_TYPE"
else
echo "******** Test 45.1 failed: GL_PROJ_CODE column missing in ORBC_PAYMENT_METHOD_TYPE"
-fi
\ No newline at end of file
+fi
diff --git a/frontend/src/common/components/form/CountryAndProvince.tsx b/frontend/src/common/components/form/CountryAndProvince.tsx
index cf7c35326..398e55681 100644
--- a/frontend/src/common/components/form/CountryAndProvince.tsx
+++ b/frontend/src/common/components/form/CountryAndProvince.tsx
@@ -44,6 +44,8 @@ interface CountryAndProvinceProps {
isProvinceRequired?: boolean;
countryClassName?: string;
provinceClassName?: string;
+ readOnly?: boolean;
+ disabled?: boolean;
}
/**
@@ -62,6 +64,8 @@ export const CountryAndProvince = ({
isProvinceRequired = true,
countryClassName,
provinceClassName,
+ disabled,
+ readOnly,
}: CountryAndProvinceProps): JSX.Element => {
const { resetField, watch, setValue } = useFormContext();
@@ -175,7 +179,10 @@ export const CountryAndProvince = ({
))}
className={countryClassName}
+ disabled={disabled}
+ readOnly={readOnly}
/>
+
{shouldDisplayProvince && (
({
))}
className={provinceClassName}
+ disabled={disabled}
+ readOnly={readOnly}
/>
)}
>
diff --git a/frontend/src/common/constants/bannerMessages.ts b/frontend/src/common/constants/bannerMessages.ts
index d7bb09f4d..c4e187dfd 100644
--- a/frontend/src/common/constants/bannerMessages.ts
+++ b/frontend/src/common/constants/bannerMessages.ts
@@ -17,6 +17,10 @@ export const BANNER_MESSAGES = {
"Only vehicles in the Vehicle Inventory can be designated to LOA(s).",
SELECT_VEHICLES_LOA_INFO:
"If you do not see the vehicle(s) you wish to designate here, please make sure you add them to the client's Vehicle Inventory first and come back to this page.",
+ FIND_LOA_DETAILS:
+ "To find details about the LOA go to the Special Authorizations page.",
+ LOA_VEHICLE_CANNOT_BE_EDITED_IN_PERMIT:
+ "Vehicle details cannot be edited in the permit application if you are using an LOA.",
REJECTED_APPLICATIONS:
"Rejected applications appear in Applications in Progress.",
};
diff --git a/frontend/src/common/helpers/util.ts b/frontend/src/common/helpers/util.ts
index 2f1eaa244..24b5e0b89 100644
--- a/frontend/src/common/helpers/util.ts
+++ b/frontend/src/common/helpers/util.ts
@@ -274,3 +274,27 @@ export const setRedirectInSession = (redirectUri: string) => {
}
}
};
+
+/**
+ * Determine whether or not two arrays have the same items.
+ * @param arr1 First array
+ * @param arr2 Second array
+ * @returns Whether or not the two arrays contain the same items
+ */
+export const areArraysEqual = (
+ arr1: T[],
+ arr2: T[],
+) => {
+ const set1 = new Set(arr1);
+ const set2 = new Set(arr2);
+
+ for (const val of set1) {
+ if (!set2.has(val)) return false;
+ }
+
+ for (const val of set2) {
+ if (!set1.has(val)) return false;
+ }
+
+ return true;
+};
diff --git a/frontend/src/features/permits/components/dashboard/tests/integration/fixtures/getActiveApplication.ts b/frontend/src/features/permits/components/dashboard/tests/integration/fixtures/getActiveApplication.ts
index d58558a43..4365f4503 100644
--- a/frontend/src/features/permits/components/dashboard/tests/integration/fixtures/getActiveApplication.ts
+++ b/frontend/src/features/permits/components/dashboard/tests/integration/fixtures/getActiveApplication.ts
@@ -192,6 +192,7 @@ export const getDefaultApplication = () => {
vehicleDetails,
commodities: conditions,
mailingAddress,
+ loas: [],
},
};
};
diff --git a/frontend/src/features/permits/context/ApplicationFormContext.ts b/frontend/src/features/permits/context/ApplicationFormContext.ts
new file mode 100644
index 000000000..835503fde
--- /dev/null
+++ b/frontend/src/features/permits/context/ApplicationFormContext.ts
@@ -0,0 +1,82 @@
+import { createContext } from "react";
+import { Dayjs } from "dayjs";
+
+import { PermitVehicleDetails } from "../types/PermitVehicleDetails";
+import { LOADetail } from "../../settings/types/SpecialAuthorization";
+import { ApplicationFormData } from "../types/application";
+import { getDefaultValues } from "../helpers/getDefaultApplicationFormData";
+import { DEFAULT_PERMIT_TYPE } from "../types/PermitType";
+import { PermitCondition } from "../types/PermitCondition";
+import { PowerUnit, Trailer, VehicleSubType } from "../../manageVehicles/types/Vehicle";
+import { Nullable } from "../../../common/types/common";
+import { CompanyProfile } from "../../manageProfile/types/manageProfile.d";
+import {
+ PAST_START_DATE_STATUSES,
+ PastStartDateStatus,
+} from "../../../common/components/form/subFormComponents/CustomDatePicker";
+
+interface ApplicationFormContextType {
+ initialFormData: ApplicationFormData;
+ formData: ApplicationFormData;
+ durationOptions: {
+ value: number;
+ label: string;
+ }[];
+ vehicleOptions: (PowerUnit | Trailer)[];
+ powerUnitSubtypes: VehicleSubType[];
+ trailerSubtypes: VehicleSubType[];
+ isLcvDesignated: boolean;
+ feature: string;
+ companyInfo?: Nullable;
+ isAmendAction: boolean;
+ createdDateTime?: Nullable;
+ updatedDateTime?: Nullable;
+ pastStartDateStatus: PastStartDateStatus;
+ companyLOAs: LOADetail[];
+ revisionHistory: {
+ permitId: number;
+ name: string;
+ revisionDateTime: string;
+ comment: string;
+ }[];
+ onLeave?: () => void;
+ onSave?: () => Promise;
+ onCancel?: () => void;
+ onContinue: () => Promise;
+ onSetDuration: (duration: number) => void;
+ onSetExpiryDate: (expiry: Dayjs) => void;
+ onSetConditions: (conditions: PermitCondition[]) => void;
+ onToggleSaveVehicle: (saveVehicle: boolean) => void;
+ onSetVehicle: (vehicleDetails: PermitVehicleDetails) => void;
+ onClearVehicle: (saveVehicle: boolean) => void;
+ onUpdateLOAs: (updatedLOAs: LOADetail[]) => void;
+}
+
+export const ApplicationFormContext = createContext({
+ initialFormData: getDefaultValues(DEFAULT_PERMIT_TYPE, undefined),
+ formData: getDefaultValues(DEFAULT_PERMIT_TYPE, undefined),
+ durationOptions: [],
+ vehicleOptions: [],
+ powerUnitSubtypes: [],
+ trailerSubtypes: [],
+ isLcvDesignated: false,
+ feature: "",
+ companyInfo: undefined,
+ isAmendAction: false,
+ createdDateTime: undefined,
+ updatedDateTime: undefined,
+ pastStartDateStatus: PAST_START_DATE_STATUSES.ALLOWED,
+ companyLOAs: [],
+ revisionHistory: [],
+ onLeave: undefined,
+ onSave: undefined,
+ onCancel: undefined,
+ onContinue: async () => undefined,
+ onSetDuration: () => undefined,
+ onSetExpiryDate: () => undefined,
+ onSetConditions: () => undefined,
+ onToggleSaveVehicle: () => undefined,
+ onSetVehicle: () => undefined,
+ onClearVehicle: () => undefined,
+ onUpdateLOAs: () => undefined,
+});
diff --git a/frontend/src/features/permits/helpers/dateSelection.ts b/frontend/src/features/permits/helpers/dateSelection.ts
index ac034f061..68d6a6cec 100644
--- a/frontend/src/features/permits/helpers/dateSelection.ts
+++ b/frontend/src/features/permits/helpers/dateSelection.ts
@@ -1,3 +1,5 @@
+import { Dayjs } from "dayjs";
+
import { BASE_DAYS_IN_YEAR, TERM_DURATION_INTERVAL_DAYS } from "../constants/constants";
import { PERMIT_TYPES, PermitType } from "../types/PermitType";
import {
@@ -13,6 +15,9 @@ import {
TROW_DURATION_INTERVAL_DAYS,
TROW_DURATION_OPTIONS,
} from "../constants/trow";
+import { getExpiryDate } from "./permitState";
+import { LOADetail } from "../../settings/types/SpecialAuthorization";
+import { getMostRecentExpiryFromLOAs } from "./permitLOA";
/**
* Get list of selectable duration options for a given permit type.
@@ -62,3 +67,68 @@ export const getDurationIntervalDays = (permitType: PermitType) => {
return TERM_DURATION_INTERVAL_DAYS; // This needs to be updated once more permit types are added
}
};
+
+/**
+ * Get the minimum permit expiry date.
+ * @param permitType Permit type
+ * @param startDate Expected start date of the permit
+ * @returns The earliest date that the permit will expire on
+ */
+export const getMinPermitExpiryDate = (
+ permitType: PermitType,
+ startDate: Dayjs,
+) => {
+ const minDuration = minDurationForPermitType(permitType);
+ return getExpiryDate(startDate, minDuration);
+};
+
+/**
+ * Get available duration options for a permit based on selected LOAs and start date.
+ * @param fullDurationOptions Full duration select options for a permit
+ * @param selectedLOAs Selected LOAs for a permit
+ * @param startDate Start date for a permit
+ * @returns Updated available duration select options
+ */
+export const getAvailableDurationOptions = (
+ fullDurationOptions: {
+ value: number;
+ label: string;
+ }[],
+ selectedLOAs: LOADetail[],
+ startDate: Dayjs,
+) => {
+ const mostRecentLOAExpiry = getMostRecentExpiryFromLOAs(selectedLOAs);
+ if (!mostRecentLOAExpiry) return fullDurationOptions;
+
+ return fullDurationOptions
+ .filter(({ value: durationDays }) => !mostRecentLOAExpiry.isBefore(getExpiryDate(startDate, durationDays)));
+};
+
+/**
+ * Update permit duration if durations options change.
+ * Selected duration must be between min allowable permit duration and max available duration in the options.
+ * @param permitType Permit type
+ * @param currentDuration Currently selected duration for the permit
+ * @param durationOptions Available list of selectable duration options for the permit
+ * @returns Current permit duration if valid, or updated duration if no longer valid
+ */
+export const handleUpdateDurationIfNeeded = (
+ permitType: PermitType,
+ currentDuration: number,
+ durationOptions: {
+ value: number;
+ label: string;
+ }[],
+) => {
+ const minAllowableDuration = minDurationForPermitType(permitType);
+ const maxDurationInOptions = Math.max(...durationOptions.map(durationOption => durationOption.value));
+
+ if (currentDuration > maxDurationInOptions) {
+ if (maxDurationInOptions < minAllowableDuration) {
+ return minAllowableDuration;
+ }
+ return maxDurationInOptions;
+ }
+
+ return currentDuration;
+};
diff --git a/frontend/src/features/permits/helpers/equality.ts b/frontend/src/features/permits/helpers/equality.ts
index 7ce7f3e7d..8d384c04b 100644
--- a/frontend/src/features/permits/helpers/equality.ts
+++ b/frontend/src/features/permits/helpers/equality.ts
@@ -4,6 +4,7 @@ import { PermitMailingAddress } from "../types/PermitMailingAddress";
import { PermitContactDetails } from "../types/PermitContactDetails";
import { PermitVehicleDetails } from "../types/PermitVehicleDetails";
import { PermitData } from "../types/PermitData";
+import { areLOADetailsEqual, LOADetail } from "../../settings/types/SpecialAuthorization";
import { PermitCondition } from "../types/PermitCondition";
import {
DATE_FORMATS,
@@ -113,6 +114,45 @@ const areVehicleDetailsEqual = (
.reduce((prevIsEqual, currIsEqual) => prevIsEqual && currIsEqual, true);
};
+/**
+ * Compare whether or not the LOAs for two permits are equal.
+ * @param loas1 LOAs for first permit
+ * @param loas2 LOAs for second permit
+ * @returns true when the selected LOAs are the same, false otherwise
+ */
+export const arePermitLOAsEqual = (
+ loas1: Nullable,
+ loas2: Nullable,
+) => {
+ const isLoas1Empty = !loas1 || loas1.length === 0;
+ const isLoas2Empty = !loas2 || loas2.length === 0;
+
+ if (isLoas1Empty && isLoas2Empty) return true;
+ if ((isLoas1Empty && !isLoas2Empty) || (!isLoas1Empty && isLoas2Empty))
+ return false;
+
+ const loaMap1 = new Map(
+ (loas1 as LOADetail[]).map((loa) => [loa.loaNumber, loa]),
+ );
+ const loaMap2 = new Map(
+ (loas2 as LOADetail[]).map((loa) => [loa.loaNumber, loa]),
+ );
+
+ for (const [loaNumber, loa] of loaMap1) {
+ if (!areLOADetailsEqual(loa, loaMap2.get(loaNumber))) {
+ return false;
+ }
+ }
+
+ for (const [loaNumber, loa] of loaMap2) {
+ if (!areLOADetailsEqual(loa, loaMap1.get(loaNumber))) {
+ return false;
+ }
+ }
+
+ return true;
+};
+
/**
* Compare whether or not two application data info are equal.
* @param data1 first application data info
@@ -133,6 +173,7 @@ export const areApplicationDataEqual = (
areVehicleDetailsEqual(data1.vehicleDetails, data2.vehicleDetails) &&
areConditionsEqual(data1.commodities, data2.commodities) &&
areMailingAddressesEqual(data1.mailingAddress, data2.mailingAddress) &&
+ arePermitLOAsEqual(data1.loas, data2.loas) &&
((!data1.companyName && !data2.companyName) ||
data1.companyName === data2.companyName) &&
((!data1.doingBusinessAs && !data2.doingBusinessAs) ||
diff --git a/frontend/src/features/permits/helpers/getDefaultApplicationFormData.ts b/frontend/src/features/permits/helpers/getDefaultApplicationFormData.ts
index f83f9e175..e08a2a6ce 100644
--- a/frontend/src/features/permits/helpers/getDefaultApplicationFormData.ts
+++ b/frontend/src/features/permits/helpers/getDefaultApplicationFormData.ts
@@ -1,7 +1,7 @@
import dayjs, { Dayjs } from "dayjs";
import { BCeIDUserDetailContext } from "../../../common/authentication/OnRouteBCContext";
-import { getMandatoryConditions, sortConditions } from "./conditions";
+import { getMandatoryConditions } from "./conditions";
import { Nullable } from "../../../common/types/common";
import { PERMIT_STATUSES } from "../types/PermitStatus";
import { calculateFeeByDuration } from "./feeSummary";
@@ -9,12 +9,9 @@ import { PermitType } from "../types/PermitType";
import { getExpiryDate } from "./permitState";
import { PermitMailingAddress } from "../types/PermitMailingAddress";
import { PermitContactDetails } from "../types/PermitContactDetails";
-import { PermitVehicleDetails } from "../types/PermitVehicleDetails";
import { Application, ApplicationFormData } from "../types/application";
import { minDurationForPermitType } from "./dateSelection";
-import { PermitCondition } from "../types/PermitCondition";
-import { LCV_CONDITION } from "../constants/constants";
-import { isVehicleSubtypeLCV } from "../../manageVehicles/helpers/vehicleSubtypes";
+import { getDefaultVehicleDetails } from "./permitVehicles";
import {
getEndOfDate,
getStartOfDate,
@@ -100,27 +97,6 @@ export const getDefaultMailingAddress = (
postalCode: getDefaultRequiredVal("", alternateAddress?.postalCode),
};
-/**
- * Gets default values for vehicle details, or populate with values from existing vehicle details.
- * @param vehicleDetails existing vehicle details, if any
- * @returns default values for vehicle details
- */
-export const getDefaultVehicleDetails = (
- vehicleDetails?: Nullable,
-) => ({
- vehicleId: getDefaultRequiredVal("", vehicleDetails?.vehicleId),
- unitNumber: getDefaultRequiredVal("", vehicleDetails?.unitNumber),
- vin: getDefaultRequiredVal("", vehicleDetails?.vin),
- plate: getDefaultRequiredVal("", vehicleDetails?.plate),
- make: getDefaultRequiredVal("", vehicleDetails?.make),
- year: applyWhenNotNullable((year) => year, vehicleDetails?.year, null),
- countryCode: getDefaultRequiredVal("", vehicleDetails?.countryCode),
- provinceCode: getDefaultRequiredVal("", vehicleDetails?.provinceCode),
- vehicleType: getDefaultRequiredVal("", vehicleDetails?.vehicleType),
- vehicleSubType: getDefaultRequiredVal("", vehicleDetails?.vehicleSubType),
- saveVehicle: false,
-});
-
export const getDurationOrDefault = (
defaultDuration: number,
duration?: Nullable,
@@ -155,87 +131,6 @@ export const getExpiryDateOrDefault = (
);
};
-/**
- * Applying LCV designation to application data.
- * @param applicationData Existing application data
- * @param isLcvDesignated Whether or not the LCV designation is to be used
- * @returns Application data after applying the LCV check
- */
-export const applyLCVToApplicationData = >(
- applicationData: T,
- isLcvDesignated: boolean,
-): T => {
- // If application doesn't exist, no need to apply LCV at all
- if (!applicationData) return applicationData;
-
- if (!isLcvDesignated) {
- // If LCV not designated, remove LCV condition from application data
- const filteredConditions = applicationData.permitData.commodities.filter(
- ({ condition }: PermitCondition) => condition !== LCV_CONDITION.condition,
- );
-
- if (isVehicleSubtypeLCV(applicationData.permitData.vehicleDetails.vehicleSubType)) {
- // Furthermore, if selected vehicle has LCV subtype, clear the vehicle
- return {
- ...applicationData,
- permitData: {
- ...applicationData.permitData,
- commodities: [...filteredConditions],
- vehicleDetails: getDefaultVehicleDetails(),
- },
- };
- }
-
- // Otherwise, keep the existing vehicle
- return {
- ...applicationData,
- permitData: {
- ...applicationData.permitData,
- commodities: [...filteredConditions],
- },
- };
- }
-
- // If LCV is designated, and vehicle subtype in the application isn't LCV but conditions have LCV,
- // then remove that LCV condition from the application
- if (
- !isVehicleSubtypeLCV(applicationData.permitData.vehicleDetails.vehicleSubType)
- && applicationData.permitData.commodities.some(({ condition }) => condition === LCV_CONDITION.condition)
- ) {
- const filteredConditions = applicationData.permitData.commodities.filter(
- ({ condition }: PermitCondition) => condition !== LCV_CONDITION.condition,
- );
-
- return {
- ...applicationData,
- permitData: {
- ...applicationData.permitData,
- commodities: [...filteredConditions],
- },
- };
- }
-
- // If LCV is designated, and vehicle subtype in the application is LCV but conditions don't have LCV,
- // then add that LCV condition into the application
- if (
- isVehicleSubtypeLCV(applicationData.permitData.vehicleDetails.vehicleSubType)
- && !applicationData.permitData.commodities.some(({ condition }) => condition === LCV_CONDITION.condition)
- ) {
- const conditionsWithLCV = sortConditions([...applicationData.permitData.commodities, LCV_CONDITION]);
-
- return {
- ...applicationData,
- permitData: {
- ...applicationData.permitData,
- commodities: [...conditionsWithLCV],
- },
- };
- }
-
- // In other cases, the application data is valid
- return applicationData;
-};
-
/**
* Gets default values for the application data, or populate with values from existing application and relevant data.
* @param permitType permit type for the application
@@ -329,6 +224,7 @@ export const getDefaultValues = (
applicationData?.permitData?.vehicleDetails,
),
feeSummary: `${calculateFeeByDuration(defaultPermitType, durationOrDefault)}`,
+ loas: getDefaultRequiredVal([], applicationData?.permitData?.loas),
},
};
};
diff --git a/frontend/src/features/permits/helpers/mappers.ts b/frontend/src/features/permits/helpers/mappers.ts
index 5903f48be..e60bc999b 100644
--- a/frontend/src/features/permits/helpers/mappers.ts
+++ b/frontend/src/features/permits/helpers/mappers.ts
@@ -99,6 +99,7 @@ export const clonePermit = (permit: Permit): Permit => {
...permit.permitData.vehicleDetails,
},
commodities: [...permit.permitData.commodities],
+ loas: [...getDefaultRequiredVal([], permit.permitData.loas)],
mailingAddress: {
...permit.permitData.mailingAddress,
},
diff --git a/frontend/src/features/permits/helpers/permitLCV.ts b/frontend/src/features/permits/helpers/permitLCV.ts
new file mode 100644
index 000000000..de37957f6
--- /dev/null
+++ b/frontend/src/features/permits/helpers/permitLCV.ts
@@ -0,0 +1,104 @@
+import { Nullable } from "../../../common/types/common";
+import { isVehicleSubtypeLCV } from "../../manageVehicles/helpers/vehicleSubtypes";
+import { LCV_CONDITION } from "../constants/constants";
+import { Application, ApplicationFormData } from "../types/application";
+import { PermitCondition } from "../types/PermitCondition";
+import { PermitVehicleDetails } from "../types/PermitVehicleDetails";
+import { sortConditions } from "./conditions";
+import { getDefaultVehicleDetails } from "./permitVehicles";
+
+/**
+ * Get updated vehicle details based on LCV designation.
+ * @param isLcvDesignated Whether or not the LCV designation is to be used
+ * @param prevSelectedVehicle Previous selected vehicle details
+ * @returns Updated vehicle details
+ */
+export const getUpdatedVehicleDetailsForLCV = (
+ isLcvDesignated: boolean,
+ prevSelectedVehicle: PermitVehicleDetails,
+) => {
+ if (!isLcvDesignated && isVehicleSubtypeLCV(prevSelectedVehicle.vehicleSubType)) {
+ // If LCV isn't designated, and selected vehicle has LCV subtype, clear the vehicle
+ return getDefaultVehicleDetails();
+ }
+
+ // Otherwise keep the existing vehicle details
+ return prevSelectedVehicle;
+};
+
+/**
+ * Get updated permit conditions based on LCV designation and selected vehicle subtype.
+ * @param isLcvDesignated Whether or not the LCV designation is to be used
+ * @param prevSelectedConditions Previously selected permit conditions
+ * @param vehicleSubtype Selected vehicle subtype
+ * @returns Updated permit conditions
+ */
+export const getUpdatedConditionsForLCV = (
+ isLcvDesignated: boolean,
+ prevSelectedConditions: PermitCondition[],
+ vehicleSubtype: string,
+) => {
+ if (!isLcvDesignated) {
+ // If LCV not designated, remove LCV condition
+ return prevSelectedConditions.filter(
+ ({ condition }: PermitCondition) => condition !== LCV_CONDITION.condition,
+ );
+ }
+
+ // If LCV is designated, and vehicle subtype isn't LCV but conditions have LCV,
+ // then remove that LCV condition
+ if (
+ !isVehicleSubtypeLCV(vehicleSubtype)
+ && prevSelectedConditions.some(({ condition }) => condition === LCV_CONDITION.condition)
+ ) {
+ return prevSelectedConditions.filter(
+ ({ condition }: PermitCondition) => condition !== LCV_CONDITION.condition,
+ );
+ }
+
+ // If LCV is designated, and vehicle subtype is LCV but conditions don't have LCV,
+ // then add that LCV condition
+ if (
+ isVehicleSubtypeLCV(vehicleSubtype)
+ && !prevSelectedConditions.some(({ condition }) => condition === LCV_CONDITION.condition)
+ ) {
+ return sortConditions([...prevSelectedConditions, LCV_CONDITION]);
+ }
+
+ // In other cases, the conditions are valid
+ return prevSelectedConditions;
+};
+
+/**
+ * Applying LCV designation to application data.
+ * @param applicationData Existing application data
+ * @param isLcvDesignated Whether or not the LCV designation is to be used
+ * @returns Application data after applying the LCV check
+ */
+export const applyLCVToApplicationData = >(
+ applicationData: T,
+ isLcvDesignated: boolean,
+): T => {
+ // If application doesn't exist, no need to apply LCV at all
+ if (!applicationData) return applicationData;
+
+ const updatedVehicleDetails = getUpdatedVehicleDetailsForLCV(
+ isLcvDesignated,
+ applicationData.permitData.vehicleDetails,
+ );
+
+ const updatedConditions = getUpdatedConditionsForLCV(
+ isLcvDesignated,
+ applicationData.permitData.commodities,
+ updatedVehicleDetails.vehicleSubType,
+ );
+
+ return {
+ ...applicationData,
+ permitData: {
+ ...applicationData.permitData,
+ commodities: [...updatedConditions],
+ vehicleDetails: updatedVehicleDetails,
+ },
+ };
+};
diff --git a/frontend/src/features/permits/helpers/permitLOA.ts b/frontend/src/features/permits/helpers/permitLOA.ts
new file mode 100644
index 000000000..afb151a0c
--- /dev/null
+++ b/frontend/src/features/permits/helpers/permitLOA.ts
@@ -0,0 +1,229 @@
+import dayjs, { Dayjs } from "dayjs";
+
+import { LOADetail } from "../../settings/types/SpecialAuthorization";
+import { PermitType } from "../types/PermitType";
+import { getEndOfDate, toLocalDayjs } from "../../../common/helpers/formatDate";
+import { Nullable } from "../../../common/types/common";
+import { Application, ApplicationFormData } from "../types/application";
+import { getDefaultRequiredVal } from "../../../common/helpers/util";
+import { PowerUnit, Trailer, VEHICLE_TYPES } from "../../manageVehicles/types/Vehicle";
+import { PermitVehicleDetails } from "../types/PermitVehicleDetails";
+import { filterVehicles, getDefaultVehicleDetails } from "./permitVehicles";
+import {
+ durationOptionsForPermitType,
+ getAvailableDurationOptions,
+ getMinPermitExpiryDate,
+ handleUpdateDurationIfNeeded,
+} from "./dateSelection";
+
+/**
+ * Filter valid LOAs for a given permit type.
+ * @param loas LOAs to filter
+ * @param permitType The permit type that the LOA can be applicable for
+ * @returns LOAs that can be applicable for the given permit type
+ */
+export const filterLOAsForPermitType = (
+ loas: LOADetail[],
+ permitType: PermitType,
+) => {
+ return loas.filter(loa => loa.loaPermitType.includes(permitType));
+};
+
+/**
+ * Filter non-expired LOAs that do not expire before the start date of a permit.
+ * @param loas LOAs to filter
+ * @param permitStart The start date of the permit
+ * @returns LOAs that do not expire before the start date of the permit
+ */
+export const filterNonExpiredLOAs = (
+ loas: LOADetail[],
+ permitStart: Dayjs,
+) => {
+ return loas.filter(loa => (
+ !loa.expiryDate
+ || !permitStart.isAfter(
+ getEndOfDate(toLocalDayjs(loa.expiryDate)),
+ )
+ ));
+};
+
+/**
+ * Get the most recent expiry date for a list of LOAs.
+ * @param loas LOAs with or without expiry dates
+ * @returns The most recent expiry date for all the LOAs, or null if none of the LOAs expire
+ */
+export const getMostRecentExpiryFromLOAs = (loas: LOADetail[]) => {
+ const expiringLOAs = loas.filter(loa => Boolean(loa.expiryDate));
+ if (expiringLOAs.length === 0) return null;
+
+ const firstLOAExpiryDate = getEndOfDate(dayjs(expiringLOAs[0].expiryDate));
+ return expiringLOAs.map(loa => loa.expiryDate)
+ .reduce((prevExpiry, currExpiry) => {
+ const prevExpiryDate = getEndOfDate(dayjs(prevExpiry));
+ const currExpiryDate = getEndOfDate(dayjs(currExpiry));
+ return prevExpiryDate.isAfter(currExpiryDate) ? currExpiryDate : prevExpiryDate;
+ }, firstLOAExpiryDate);
+};
+
+/**
+ * Get updated selectable LOAs with up-to-date information and selection state.
+ * This removes non-existent selected LOAs, and updates any existing selected LOAs with up-to-date info.
+ * @param upToDateLOAs Most recent up-to-date company LOAs
+ * @param prevSelectedLOAs Previously selected LOAs
+ * @param minPermitExpiryDate Min expiry date for a permit
+ * @returns Updated list of selectable LOAs with up-to-date information and selection state
+ */
+export const getUpdatedLOASelection = (
+ upToDateLOAs: LOADetail[],
+ prevSelectedLOAs: LOADetail[],
+ minPermitExpiryDate: Dayjs,
+) => {
+ // Each LOA should only be selected once, but there's a chance that an up-to-date LOA is also a previously selected LOA,
+ // which means that LOA should only be shown once.
+ // Thus, any overlapping LOA between the up-to-date LOAs and previously selected LOAs should only be included once,
+ // and all non-overlapping LOAs that are not part of the up-to-date LOAs should be removed
+ const prevSelectedLOANumbers = new Set([...prevSelectedLOAs.map(loa => loa.loaNumber)]);
+
+ return upToDateLOAs.map(loa => {
+ const wasSelected = prevSelectedLOANumbers.has(loa.loaNumber);
+ const isExpiringBeforeMinPermitExpiry = Boolean(loa.expiryDate)
+ && minPermitExpiryDate.isAfter(getEndOfDate(dayjs(loa.expiryDate)));
+
+ // Deselect and disable any LOAs expiring before min permit expiry date
+ const isSelected = wasSelected && !isExpiringBeforeMinPermitExpiry;
+ const isEnabled = !isExpiringBeforeMinPermitExpiry;
+
+ return {
+ loa,
+ checked: isSelected,
+ disabled: !isEnabled,
+ };
+ });
+};
+
+/**
+ * Get updated vehicle details and options based on selected LOAs.
+ * @param selectedLOAs LOAs that are selected for the permit
+ * @param vehicleOptions Provided vehicle options for selection
+ * @param prevSelectedVehicle Previously selected vehicle details in the permit form
+ * @param ineligiblePowerUnitSubtypes Ineligible power unit subtypes
+ * @param ineligibleTrailerSubtypes Ineligible trailer subtypes
+ * @returns Updated vehicle details and filtered vehicle options
+ */
+export const getUpdatedVehicleDetailsForLOAs = (
+ selectedLOAs: LOADetail[],
+ vehicleOptions: (PowerUnit | Trailer)[],
+ prevSelectedVehicle: PermitVehicleDetails,
+ ineligiblePowerUnitSubtypes: string[],
+ ineligibleTrailerSubtypes: string[],
+) => {
+ const filteredVehicles = filterVehicles(
+ vehicleOptions,
+ ineligiblePowerUnitSubtypes,
+ ineligibleTrailerSubtypes,
+ selectedLOAs,
+ );
+
+ const filteredVehicleIds = filteredVehicles.map(filteredVehicle => ({
+ filteredVehicleType: filteredVehicle.vehicleType,
+ filteredVehicleId: filteredVehicle.vehicleType === VEHICLE_TYPES.TRAILER
+ ? (filteredVehicle as Trailer).trailerId
+ : (filteredVehicle as PowerUnit).powerUnitId,
+ }));
+
+ // If vehicle selected is an existing vehicle but is not in list of vehicle options
+ // Clear the selected vehicle
+ const { vehicleId, vehicleType } = prevSelectedVehicle;
+ if (vehicleId && !filteredVehicleIds.some(({
+ filteredVehicleType,
+ filteredVehicleId,
+ }) => filteredVehicleType === vehicleType && filteredVehicleId === vehicleId)) {
+ return {
+ filteredVehicleOptions: filteredVehicles,
+ updatedVehicle: getDefaultVehicleDetails(),
+ };
+ }
+
+ return {
+ filteredVehicleOptions: filteredVehicles,
+ updatedVehicle: prevSelectedVehicle,
+ };
+};
+
+/**
+ * Applying the most up-to-date LOA info to application data.
+ * @param applicationData Existing application data
+ * @param upToDateLOAs Most recent up-to-date company LOAs
+ * @param inventoryVehicles Vehicle options from the inventory
+ * @param ineligiblePowerUnitSubtypes Ineligible power unit subtypes that cannot be used for vehicles
+ * @param ineligibleTrailerSubtypes Ineligible trailer subtypes that cannot be used for vehicles
+ * @returns Application data after applying the up-to-date LOAs
+ */
+export const applyUpToDateLOAsToApplication = >(
+ applicationData: T,
+ upToDateLOAs: LOADetail[],
+ inventoryVehicles: (PowerUnit | Trailer)[],
+ ineligiblePowerUnitSubtypes: string[],
+ ineligibleTrailerSubtypes: string[],
+): T => {
+ // If application doesn't exist, no need to apply LOAs to it at all
+ if (!applicationData) return applicationData;
+
+ // Applicable LOAs must be:
+ // 1. Applicable for the current permit type
+ // 2. Have expiry date that is on or after the start date for an application
+ const applicableLOAs = filterNonExpiredLOAs(
+ filterLOAsForPermitType(
+ getDefaultRequiredVal([], upToDateLOAs),
+ applicationData.permitType,
+ ),
+ applicationData.permitData.startDate,
+ );
+
+ // Update selected LOAs in the permit data
+ const prevSelectedLOAs = getDefaultRequiredVal([], applicationData.permitData.loas);
+ const minPermitExpiryDate = getMinPermitExpiryDate(
+ applicationData.permitType,
+ applicationData.permitData.startDate,
+ );
+
+ const newSelectedLOAs = getUpdatedLOASelection(
+ applicableLOAs,
+ prevSelectedLOAs,
+ minPermitExpiryDate,
+ )
+ .filter(({ checked }) => checked)
+ .map(({ loa }) => loa);
+
+ // Update duration in permit if selected LOAs changed
+ const durationOptions = getAvailableDurationOptions(
+ durationOptionsForPermitType(applicationData.permitType),
+ newSelectedLOAs,
+ applicationData.permitData.startDate,
+ );
+
+ const updatedDuration = handleUpdateDurationIfNeeded(
+ applicationData.permitType,
+ applicationData.permitData.permitDuration,
+ durationOptions,
+ );
+
+ // Update vehicle details in permit if selected LOAs changed
+ const { updatedVehicle } = getUpdatedVehicleDetailsForLOAs(
+ newSelectedLOAs,
+ inventoryVehicles,
+ applicationData.permitData.vehicleDetails,
+ ineligiblePowerUnitSubtypes,
+ ineligibleTrailerSubtypes,
+ );
+
+ return {
+ ...applicationData,
+ permitData: {
+ ...applicationData.permitData,
+ permitDuration: updatedDuration,
+ loas: newSelectedLOAs,
+ vehicleDetails: updatedVehicle,
+ },
+ };
+};
diff --git a/frontend/src/features/permits/helpers/permitVehicles.ts b/frontend/src/features/permits/helpers/permitVehicles.ts
index 0c158e37f..aa667e489 100644
--- a/frontend/src/features/permits/helpers/permitVehicles.ts
+++ b/frontend/src/features/permits/helpers/permitVehicles.ts
@@ -1,6 +1,11 @@
import { PERMIT_TYPES, PermitType } from "../types/PermitType";
import { TROW_INELIGIBLE_POWERUNITS, TROW_INELIGIBLE_TRAILERS } from "../constants/trow";
import { TROS_INELIGIBLE_POWERUNITS, TROS_INELIGIBLE_TRAILERS } from "../constants/tros";
+import { LOADetail } from "../../settings/types/SpecialAuthorization";
+import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../common/helpers/util";
+import { Nullable } from "../../../common/types/common";
+import { PermitVehicleDetails } from "../types/PermitVehicleDetails";
+import { isVehicleSubtypeLCV } from "../../manageVehicles/helpers/vehicleSubtypes";
import {
PowerUnit,
Trailer,
@@ -32,12 +37,52 @@ export const getIneligibleTrailerSubtypes = (permitType: PermitType) => {
}
};
+/**
+ * Get all ineligible power unit and trailer subtypes based on LCV designation and permit type.
+ * @param permitType Permit type
+ * @param isLcvDesignated Whether or not the LCV designation is used
+ * @returns All ineligible power unit and trailer subtypes
+ */
+export const getIneligibleSubtypes = (
+ permitType: PermitType,
+ isLcvDesignated: boolean,
+) => {
+ return {
+ ineligibleTrailerSubtypes: getIneligibleTrailerSubtypes(permitType),
+ ineligiblePowerUnitSubtypes: getIneligiblePowerUnitSubtypes(permitType)
+ .filter(subtype => !isLcvDesignated || !isVehicleSubtypeLCV(subtype.typeCode)),
+ };
+};
+
+/**
+ * Gets default values for vehicle details, or populate with values from existing vehicle details.
+ * @param vehicleDetails existing vehicle details, if any
+ * @returns default values for vehicle details
+ */
+export const getDefaultVehicleDetails = (
+ vehicleDetails?: Nullable,
+) => ({
+ vehicleId: getDefaultRequiredVal("", vehicleDetails?.vehicleId),
+ unitNumber: getDefaultRequiredVal("", vehicleDetails?.unitNumber),
+ vin: getDefaultRequiredVal("", vehicleDetails?.vin),
+ plate: getDefaultRequiredVal("", vehicleDetails?.plate),
+ make: getDefaultRequiredVal("", vehicleDetails?.make),
+ year: applyWhenNotNullable((year) => year, vehicleDetails?.year, null),
+ countryCode: getDefaultRequiredVal("", vehicleDetails?.countryCode),
+ provinceCode: getDefaultRequiredVal("", vehicleDetails?.provinceCode),
+ vehicleType: getDefaultRequiredVal("", vehicleDetails?.vehicleType),
+ vehicleSubType: getDefaultRequiredVal("", vehicleDetails?.vehicleSubType),
+ saveVehicle: false,
+});
+
/**
* A helper method that filters eligible power unit or trailer subtypes for dropdown lists.
* @param allVehicleSubtypes List of both eligible and ineligible vehicle subtypes
* @param vehicleType Type of vehicle
* @param ineligiblePowerUnitSubtypes List of provided ineligible power unit subtypes
* @param ineligibleTrailerSubtypes List of provided ineligible trailer subtypes
+ * @param allowedPowerUnitSubtypes List of provided allowed power unit subtypes
+ * @param allowedTrailerSubtypes List of provided allowed trailer subtypes
* @returns List of only eligible power unit or trailer subtypes
*/
export const filterVehicleSubtypes = (
@@ -45,12 +90,19 @@ export const filterVehicleSubtypes = (
vehicleType: VehicleType,
ineligiblePowerUnitSubtypes: VehicleSubType[],
ineligibleTrailerSubtypes: VehicleSubType[],
+ allowedPowerUnitSubtypes: string[],
+ allowedTrailerSubtypes: string[],
) => {
const ineligibleSubtypes = vehicleType === VEHICLE_TYPES.TRAILER
? ineligibleTrailerSubtypes : ineligiblePowerUnitSubtypes;
+ const allowedSubtypes = vehicleType === VEHICLE_TYPES.TRAILER
+ ? allowedTrailerSubtypes : allowedPowerUnitSubtypes;
+
return allVehicleSubtypes.filter((vehicleSubtype) => {
- return !ineligibleSubtypes.some(
+ return allowedSubtypes.some(
+ allowedSubtype => vehicleSubtype.typeCode === allowedSubtype
+ ) || !ineligibleSubtypes.some(
(ineligibleSubtype) => vehicleSubtype.typeCode === ineligibleSubtype.typeCode,
);
});
@@ -61,24 +113,44 @@ export const filterVehicleSubtypes = (
* @param vehicles List of both eligible and ineligible vehicles
* @param ineligiblePowerUnitSubtypes List of ineligible power unit subtypes
* @param ineligibleTrailerSubtypes List of ineligible trailer subtypes
+ * @param loas LOAs that potentially bypass ineligible vehicle restrictions
* @returns List of only eligible vehicles
*/
export const filterVehicles = (
vehicles: Vehicle[],
- ineligiblePowerUnitSubtypes: VehicleSubType[],
- ineligibleTrailerSubtypes: VehicleSubType[],
+ ineligiblePowerUnitSubtypes: string[],
+ ineligibleTrailerSubtypes: string[],
+ loas: LOADetail[],
) => {
+ const permittedPowerUnitIds = new Set([
+ ...loas.map(loa => loa.powerUnits)
+ .reduce((prevPowerUnits, currPowerUnits) => [
+ ...prevPowerUnits,
+ ...currPowerUnits,
+ ], []),
+ ]);
+
+ const permittedTrailerIds = new Set([
+ ...loas.map(loa => loa.trailers)
+ .reduce((prevTrailers, currTrailers) => [
+ ...prevTrailers,
+ ...currTrailers,
+ ], []),
+ ]);
+
return vehicles.filter((vehicle) => {
if (vehicle.vehicleType === VEHICLE_TYPES.TRAILER) {
const trailer = vehicle as Trailer;
- return !ineligibleTrailerSubtypes.some((ineligibleSubtype) => {
- return trailer.trailerTypeCode === ineligibleSubtype.typeCode;
- });
+ return permittedTrailerIds.has(trailer.trailerId as string)
+ || !ineligibleTrailerSubtypes.some((ineligibleSubtype) => {
+ return trailer.trailerTypeCode === ineligibleSubtype;
+ });
}
const powerUnit = vehicle as PowerUnit;
- return !ineligiblePowerUnitSubtypes.some((ineligibleSubtype) => {
- return powerUnit.powerUnitTypeCode === ineligibleSubtype.typeCode;
- });
+ return permittedPowerUnitIds.has(powerUnit.powerUnitId as string)
+ || !ineligiblePowerUnitSubtypes.some((ineligibleSubtype) => {
+ return powerUnit.powerUnitTypeCode === ineligibleSubtype;
+ });
});
};
diff --git a/frontend/src/features/permits/hooks/useApplicationFormContext.ts b/frontend/src/features/permits/hooks/useApplicationFormContext.ts
new file mode 100644
index 000000000..cb67852d6
--- /dev/null
+++ b/frontend/src/features/permits/hooks/useApplicationFormContext.ts
@@ -0,0 +1,114 @@
+import { useContext } from "react";
+
+import { ApplicationFormContext } from "../context/ApplicationFormContext";
+import { usePermitDateSelection } from "./usePermitDateSelection";
+import { LOADetail } from "../../settings/types/SpecialAuthorization";
+import { usePermitConditions } from "./usePermitConditions";
+import { getStartOfDate } from "../../../common/helpers/formatDate";
+import { getIneligibleSubtypes } from "../helpers/permitVehicles";
+import { usePermitVehicleForLOAs } from "./usePermitVehicleForLOAs";
+
+export const useApplicationFormContext = () => {
+ const {
+ initialFormData,
+ formData,
+ durationOptions,
+ vehicleOptions,
+ powerUnitSubtypes,
+ trailerSubtypes,
+ isLcvDesignated,
+ feature,
+ companyInfo,
+ isAmendAction,
+ createdDateTime,
+ updatedDateTime,
+ pastStartDateStatus,
+ companyLOAs,
+ revisionHistory,
+ onLeave,
+ onSave,
+ onCancel,
+ onContinue,
+ onSetDuration,
+ onSetExpiryDate,
+ onSetConditions,
+ onToggleSaveVehicle,
+ onSetVehicle,
+ onClearVehicle,
+ onUpdateLOAs,
+ } = useContext(ApplicationFormContext);
+
+ const permitType = formData.permitType;
+ const {
+ loas: currentSelectedLOAs,
+ permitDuration,
+ startDate: permitStartDate,
+ commodities: permitConditions,
+ vehicleDetails: vehicleFormData,
+ } = formData.permitData;
+
+ // Update duration options and expiry when needed
+ const { availableDurationOptions } = usePermitDateSelection(
+ permitType,
+ getStartOfDate(permitStartDate),
+ durationOptions,
+ currentSelectedLOAs as LOADetail[],
+ permitDuration,
+ onSetDuration,
+ onSetExpiryDate,
+ );
+
+ // Update permit conditions when LCV designation or vehicle subtype changes
+ usePermitConditions(
+ permitConditions,
+ isLcvDesignated,
+ vehicleFormData.vehicleSubType,
+ onSetConditions,
+ );
+
+ // Check to see if vehicle details is still valid after LOA has been deselected
+ const {
+ ineligiblePowerUnitSubtypes,
+ ineligibleTrailerSubtypes,
+ } = getIneligibleSubtypes(permitType, isLcvDesignated);
+
+ const { filteredVehicleOptions } = usePermitVehicleForLOAs(
+ vehicleFormData,
+ vehicleOptions,
+ currentSelectedLOAs as LOADetail[],
+ ineligiblePowerUnitSubtypes.map(({ typeCode }) => typeCode),
+ ineligibleTrailerSubtypes.map(({ typeCode }) => typeCode),
+ () => onClearVehicle(Boolean(vehicleFormData.saveVehicle)),
+ );
+
+ return {
+ initialFormData,
+ formData,
+ availableDurationOptions,
+ powerUnitSubtypes,
+ trailerSubtypes,
+ isLcvDesignated,
+ ineligiblePowerUnitSubtypes,
+ ineligibleTrailerSubtypes,
+ filteredVehicleOptions,
+ feature,
+ companyInfo,
+ isAmendAction,
+ createdDateTime,
+ updatedDateTime,
+ pastStartDateStatus,
+ companyLOAs,
+ revisionHistory,
+ onLeave,
+ onSave,
+ onCancel,
+ onContinue,
+ onSetDuration,
+ onSetExpiryDate,
+ onSetConditions,
+ onToggleSaveVehicle,
+ onSetVehicle,
+ onClearVehicle,
+ onUpdateLOAs,
+ };
+};
\ No newline at end of file
diff --git a/frontend/src/features/permits/hooks/useDefaultApplicationFormData.ts b/frontend/src/features/permits/hooks/useDefaultApplicationFormData.ts
deleted file mode 100644
index 52dcddfba..000000000
--- a/frontend/src/features/permits/hooks/useDefaultApplicationFormData.ts
+++ /dev/null
@@ -1,68 +0,0 @@
-import { useEffect, useMemo } from "react";
-import { useForm } from "react-hook-form";
-
-import { Application, ApplicationFormData } from "../types/application";
-import { BCeIDUserDetailContext } from "../../../common/authentication/OnRouteBCContext";
-import { CompanyProfile } from "../../manageProfile/types/manageProfile";
-import { Nullable } from "../../../common/types/common";
-import { PermitType } from "../types/PermitType";
-import {
- applyLCVToApplicationData,
- getDefaultValues,
-} from "../helpers/getDefaultApplicationFormData";
-
-/**
- * Custom hook for populating the form using fetched application data, as well as current company id and user details.
- * This also involves resetting certain form values whenever new/updated application data is fetched.
- * @param permitType Permit type for the application
- * @param isLcvDesignated Whether or not the company is designated to use LCV for permits
- * @param companyInfo Company information for filling out the form
- * @param applicationData Application data received to fill out the form, preferrably from ApplicationContext/backend
- * @param userDetails User details for filling out the form
- * @returns Current application form data and methods to manage the form
- */
-export const useDefaultApplicationFormData = (
- permitType: PermitType,
- isLcvDesignated: boolean,
- companyInfo: Nullable,
- applicationData?: Nullable,
- userDetails?: BCeIDUserDetailContext,
-) => {
- // Used to populate/initialize the form with
- // This will be updated whenever new application, company, and user data is fetched
- const initialFormData = useMemo(() => applyLCVToApplicationData(
- getDefaultValues(
- permitType,
- companyInfo,
- applicationData,
- userDetails,
- ),
- isLcvDesignated,
- ), [
- permitType,
- companyInfo,
- applicationData,
- userDetails,
- isLcvDesignated,
- ]);
-
- // Register default values with react-hook-form
- const formMethods = useForm({
- defaultValues: initialFormData,
- reValidateMode: "onBlur",
- });
-
- const { watch, reset } = formMethods;
- const currentFormData = watch();
-
- // Reset the form with updated default form data whenever fetched data changes
- useEffect(() => {
- reset(initialFormData);
- }, [initialFormData]);
-
- return {
- initialFormData,
- currentFormData,
- formMethods,
- };
-};
diff --git a/frontend/src/features/permits/hooks/useInitApplicationFormData.ts b/frontend/src/features/permits/hooks/useInitApplicationFormData.ts
new file mode 100644
index 000000000..4c60e4147
--- /dev/null
+++ b/frontend/src/features/permits/hooks/useInitApplicationFormData.ts
@@ -0,0 +1,133 @@
+import { useEffect, useMemo } from "react";
+import { useForm } from "react-hook-form";
+import dayjs, { Dayjs } from "dayjs";
+
+import { Application, ApplicationFormData } from "../types/application";
+import { BCeIDUserDetailContext } from "../../../common/authentication/OnRouteBCContext";
+import { CompanyProfile } from "../../manageProfile/types/manageProfile";
+import { Nullable } from "../../../common/types/common";
+import { PermitType } from "../types/PermitType";
+import { LOADetail } from "../../settings/types/SpecialAuthorization";
+import { applyUpToDateLOAsToApplication } from "../helpers/permitLOA";
+import { getDefaultValues } from "../helpers/getDefaultApplicationFormData";
+import { applyLCVToApplicationData } from "../helpers/permitLCV";
+import { PowerUnit, Trailer } from "../../manageVehicles/types/Vehicle";
+import { getIneligibleSubtypes } from "../helpers/permitVehicles";
+import { PermitCondition } from "../types/PermitCondition";
+import { EMPTY_VEHICLE_DETAILS, PermitVehicleDetails } from "../types/PermitVehicleDetails";
+
+/**
+ * Custom hook for populating the form using fetched application data, as well as current company id and user details.
+ * This also involves resetting certain form values whenever new/updated application data is fetched.
+ * @param permitType Permit type for the application
+ * @param isLcvDesignated Whether or not the company is designated to use LCV for permits
+ * @param loas Most up-to-date LOAs belonging to the company
+ * @param companyInfo Company information for filling out the form
+ * @param applicationData Application data received to fill out the form, preferrably from ApplicationContext/backend
+ * @param userDetails User details for filling out the form
+ * @returns Current application form data, methods to manage the form, and selectable input options
+ */
+export const useInitApplicationFormData = (
+ permitType: PermitType,
+ isLcvDesignated: boolean,
+ loas: LOADetail[],
+ inventoryVehicles: (PowerUnit | Trailer)[],
+ companyInfo: Nullable,
+ applicationData?: Nullable,
+ userDetails?: BCeIDUserDetailContext,
+) => {
+ // Used to populate/initialize the form with
+ // This will be updated whenever new application, company, and user data is fetched
+ const initialFormData = useMemo(() => {
+ const ineligibleSubtypes = getIneligibleSubtypes(permitType, isLcvDesignated);
+ const ineligiblePowerUnitSubtypes= ineligibleSubtypes.ineligiblePowerUnitSubtypes
+ .map(({ typeCode }) => typeCode);
+
+ const ineligibleTrailerSubtypes = ineligibleSubtypes.ineligibleTrailerSubtypes
+ .map(({ typeCode }) => typeCode);
+
+ return applyUpToDateLOAsToApplication(
+ applyLCVToApplicationData(
+ getDefaultValues(
+ permitType,
+ companyInfo,
+ applicationData,
+ userDetails,
+ ),
+ isLcvDesignated,
+ ),
+ loas,
+ inventoryVehicles,
+ ineligiblePowerUnitSubtypes,
+ ineligibleTrailerSubtypes,
+ );
+ }, [
+ permitType,
+ companyInfo,
+ applicationData,
+ userDetails,
+ isLcvDesignated,
+ loas,
+ inventoryVehicles,
+ ]);
+
+ // Register default values with react-hook-form
+ const formMethods = useForm({
+ defaultValues: initialFormData,
+ reValidateMode: "onBlur",
+ });
+
+ const { watch, reset, setValue } = formMethods;
+ const currentFormData = watch();
+
+ // Reset the form with updated default form data whenever fetched data changes
+ useEffect(() => {
+ reset(initialFormData);
+ }, [initialFormData]);
+
+ const onSetDuration = (duration: number) => {
+ setValue("permitData.permitDuration", duration);
+ };
+
+ const onSetExpiryDate = (expiry: Dayjs) => {
+ setValue("permitData.expiryDate", dayjs(expiry));
+ };
+
+ const onSetConditions = (conditions: PermitCondition[]) => {
+ setValue("permitData.commodities", [...conditions]);
+ };
+
+ const onToggleSaveVehicle = (saveVehicle: boolean) => {
+ setValue("permitData.vehicleDetails.saveVehicle", saveVehicle);
+ };
+
+ const onSetVehicle = (vehicleDetails: PermitVehicleDetails) => {
+ setValue("permitData.vehicleDetails", {
+ ...vehicleDetails,
+ });
+ };
+
+ const onClearVehicle = (saveVehicle: boolean) => {
+ setValue("permitData.vehicleDetails", {
+ ...EMPTY_VEHICLE_DETAILS,
+ saveVehicle,
+ });
+ };
+
+ const onUpdateLOAs = (updatedLOAs: LOADetail[]) => {
+ setValue("permitData.loas", updatedLOAs);
+ };
+
+ return {
+ initialFormData,
+ currentFormData,
+ formMethods,
+ onSetDuration,
+ onSetExpiryDate,
+ onSetConditions,
+ onToggleSaveVehicle,
+ onSetVehicle,
+ onClearVehicle,
+ onUpdateLOAs,
+ };
+};
diff --git a/frontend/src/features/permits/hooks/usePermitConditions.ts b/frontend/src/features/permits/hooks/usePermitConditions.ts
new file mode 100644
index 000000000..2d98456d9
--- /dev/null
+++ b/frontend/src/features/permits/hooks/usePermitConditions.ts
@@ -0,0 +1,34 @@
+import { useEffect } from "react";
+
+import { areArraysEqual } from "../../../common/helpers/util";
+import { PermitCondition } from "../types/PermitCondition";
+import { getUpdatedConditionsForLCV } from "../helpers/permitLCV";
+
+export const usePermitConditions = (
+ selectedConditions: PermitCondition[],
+ isLcvDesignated: boolean,
+ vehicleSubtype: string,
+ onSetConditions: (conditions: PermitCondition[]) => void,
+) => {
+ // If conditions were changed as a result of LCV or vehicle subtype, update permit conditions
+ const updatedConditions = getUpdatedConditionsForLCV(
+ isLcvDesignated,
+ selectedConditions,
+ vehicleSubtype,
+ );
+
+ useEffect(() => {
+ if (!areArraysEqual(
+ updatedConditions.map(({ condition }) => condition),
+ selectedConditions.map(({ condition }: PermitCondition) => condition),
+ )) {
+ onSetConditions(updatedConditions);
+ }
+ }, [
+ updatedConditions,
+ selectedConditions,
+ onSetConditions,
+ ]);
+
+ return { updatedConditions };
+};
diff --git a/frontend/src/features/permits/hooks/usePermitDateSelection.ts b/frontend/src/features/permits/hooks/usePermitDateSelection.ts
new file mode 100644
index 000000000..ca89a2f08
--- /dev/null
+++ b/frontend/src/features/permits/hooks/usePermitDateSelection.ts
@@ -0,0 +1,60 @@
+import { useEffect } from "react";
+import { Dayjs } from "dayjs";
+
+import { getExpiryDate } from "../helpers/permitState";
+import { LOADetail } from "../../settings/types/SpecialAuthorization";
+import { PermitType } from "../types/PermitType";
+import { getAvailableDurationOptions, handleUpdateDurationIfNeeded } from "../helpers/dateSelection";
+
+/**
+ * Hook that manages permit date selection based on changing permit data.
+ * @param permitType Permit type
+ * @param startDate Selected start date for the permit
+ * @param durationOptions All possible duration options for the permit
+ * @param selectedLOAs Selected LOAs for the permit
+ * @param selectedDuration Selected duration for the permit
+ * @returns Updated valid duration options
+ */
+export const usePermitDateSelection = (
+ permitType: PermitType,
+ startDate: Dayjs,
+ durationOptions: {
+ value: number;
+ label: string;
+ }[],
+ selectedLOAs: LOADetail[],
+ selectedDuration: number,
+ onSetDuration: (duration: number) => void,
+ onSetExpiryDate: (expiry: Dayjs) => void,
+) => {
+ // Limit permit duration options based on selected LOAs
+ const availableDurationOptions = getAvailableDurationOptions(
+ durationOptions,
+ selectedLOAs,
+ startDate,
+ );
+
+ // If duration options change, check if the current permit duration is still selectable
+ const updatedDuration = handleUpdateDurationIfNeeded(
+ permitType,
+ selectedDuration,
+ availableDurationOptions,
+ );
+
+ useEffect(() => {
+ onSetDuration(updatedDuration);
+ }, [
+ updatedDuration,
+ onSetDuration,
+ ]);
+
+ const expiryDate = getExpiryDate(startDate, selectedDuration);
+ useEffect(() => {
+ onSetExpiryDate(expiryDate);
+ }, [
+ expiryDate,
+ onSetExpiryDate,
+ ]);
+
+ return { availableDurationOptions };
+};
diff --git a/frontend/src/features/permits/hooks/usePermitVehicleForLOAs.ts b/frontend/src/features/permits/hooks/usePermitVehicleForLOAs.ts
new file mode 100644
index 000000000..082f1a1b5
--- /dev/null
+++ b/frontend/src/features/permits/hooks/usePermitVehicleForLOAs.ts
@@ -0,0 +1,44 @@
+import { useEffect } from "react";
+
+import { PermitVehicleDetails } from "../types/PermitVehicleDetails";
+import { PowerUnit, Trailer } from "../../manageVehicles/types/Vehicle";
+import { LOADetail } from "../../settings/types/SpecialAuthorization";
+import { getUpdatedVehicleDetailsForLOAs } from "../helpers/permitLOA";
+
+export const usePermitVehicleForLOAs = (
+ vehicleFormData: PermitVehicleDetails,
+ vehicleOptions: (PowerUnit | Trailer)[],
+ selectedLOAs: LOADetail[],
+ ineligiblePowerUnitSubtypes: string[],
+ ineligibleTrailerSubtypes: string[],
+ onClearVehicle: () => void,
+) => {
+ // Check to see if vehicle details is still valid after LOA has been deselected
+ const {
+ filteredVehicleOptions,
+ updatedVehicle,
+ } = getUpdatedVehicleDetailsForLOAs(
+ selectedLOAs,
+ vehicleOptions,
+ vehicleFormData,
+ ineligiblePowerUnitSubtypes,
+ ineligibleTrailerSubtypes,
+ );
+
+ const vehicleIdInForm = vehicleFormData.vehicleId;
+ const updatedVehicleId = updatedVehicle.vehicleId;
+ useEffect(() => {
+ // If vehicle originally selected exists but the updated vehicle is cleared, clear the vehicle
+ if (vehicleIdInForm && !updatedVehicleId) {
+ onClearVehicle();
+ }
+ }, [
+ vehicleIdInForm,
+ updatedVehicleId,
+ onClearVehicle,
+ ]);
+
+ return {
+ filteredVehicleOptions,
+ };
+};
diff --git a/frontend/src/features/permits/hooks/usePermitVehicleManagement.ts b/frontend/src/features/permits/hooks/usePermitVehicleManagement.ts
index 61cd9f57a..2eb6c3262 100644
--- a/frontend/src/features/permits/hooks/usePermitVehicleManagement.ts
+++ b/frontend/src/features/permits/hooks/usePermitVehicleManagement.ts
@@ -1,7 +1,9 @@
+import { useCallback, useMemo } from "react";
+
import { getDefaultRequiredVal } from "../../../common/helpers/util";
import { mapToVehicleObjectById } from "../helpers/mappers";
import { Nullable } from "../../../common/types/common";
-import { getDefaultVehicleDetails } from "../helpers/getDefaultApplicationFormData";
+import { getDefaultVehicleDetails } from "../helpers/permitVehicles";
import { PermitVehicleDetails } from "../types/PermitVehicleDetails";
import {
usePowerUnitSubTypesQuery,
@@ -25,6 +27,68 @@ import {
VehicleType,
} from "../../manageVehicles/types/Vehicle";
+const transformByVehicleType = (
+ vehicleFormData: PermitVehicleDetails,
+ existingVehicle?: Vehicle,
+): Vehicle => {
+ const defaultPowerUnit: PowerUnit = {
+ powerUnitId: "",
+ unitNumber: "",
+ vin: vehicleFormData.vin,
+ plate: vehicleFormData.plate,
+ make: vehicleFormData.make,
+ year: vehicleFormData.year,
+ countryCode: vehicleFormData.countryCode,
+ provinceCode: vehicleFormData.provinceCode,
+ powerUnitTypeCode: vehicleFormData.vehicleSubType,
+ };
+
+ const defaultTrailer: Trailer = {
+ trailerId: "",
+ unitNumber: "",
+ vin: vehicleFormData.vin,
+ plate: vehicleFormData.plate,
+ make: vehicleFormData.make,
+ year: vehicleFormData.year,
+ countryCode: vehicleFormData.countryCode,
+ provinceCode: vehicleFormData.provinceCode,
+ trailerTypeCode: vehicleFormData.vehicleSubType,
+ };
+
+ switch (vehicleFormData.vehicleType) {
+ case VEHICLE_TYPES.TRAILER:
+ return {
+ ...defaultTrailer,
+ trailerId: getDefaultRequiredVal(
+ "",
+ (existingVehicle as Trailer)?.trailerId,
+ ),
+ unitNumber: getDefaultRequiredVal(
+ "",
+ existingVehicle?.unitNumber,
+ vehicleFormData.unitNumber,
+ ),
+ } as Trailer;
+ case VEHICLE_TYPES.POWER_UNIT:
+ default:
+ return {
+ ...defaultPowerUnit,
+ unitNumber: getDefaultRequiredVal(
+ "",
+ existingVehicle?.unitNumber,
+ vehicleFormData.unitNumber,
+ ),
+ powerUnitId: getDefaultRequiredVal(
+ "",
+ (existingVehicle as PowerUnit)?.powerUnitId,
+ ),
+ } as PowerUnit;
+ }
+};
+
+const modifyVehicleSuccess = (status: number) =>
+ status === 201 || status === 200;
+
export const usePermitVehicleManagement = (companyId: number) => {
// Mutations used to add/update vehicle details
const addPowerUnitMutation = useAddPowerUnitMutation();
@@ -38,7 +102,7 @@ export const usePermitVehicleManagement = (companyId: number) => {
const { data: powerUnitSubtypesData } = usePowerUnitSubTypesQuery();
const { data: trailerSubtypesData } = useTrailerSubTypesQuery();
- const fetchedVehicles = [
+ const fetchedVehicles = useMemo(() => [
...getDefaultRequiredVal(
[],
powerUnitsData,
@@ -53,12 +117,12 @@ export const usePermitVehicleManagement = (companyId: number) => {
...trailer,
vehicleType: VEHICLE_TYPES.TRAILER,
})),
- ];
+ ], [powerUnitsData, trailersData]);
const powerUnitSubTypes = getDefaultRequiredVal([], powerUnitSubtypesData);
const trailerSubTypes = getDefaultRequiredVal([], trailerSubtypesData);
- const handleSaveVehicle = async (
+ const handleSaveVehicle = useCallback(async (
vehicleData?: Nullable,
): Promise> => {
// Check if the "add/update vehicle" checkbox was checked by the user
@@ -76,68 +140,6 @@ export const usePermitVehicleManagement = (companyId: number) => {
vehicleId,
);
- const transformByVehicleType = (
- vehicleFormData: PermitVehicleDetails,
- existingVehicle?: Vehicle,
- ): Vehicle => {
- const defaultPowerUnit: PowerUnit = {
- powerUnitId: "",
- unitNumber: "",
- vin: vehicleFormData.vin,
- plate: vehicleFormData.plate,
- make: vehicleFormData.make,
- year: vehicleFormData.year,
- countryCode: vehicleFormData.countryCode,
- provinceCode: vehicleFormData.provinceCode,
- powerUnitTypeCode: vehicleFormData.vehicleSubType,
- };
-
- const defaultTrailer: Trailer = {
- trailerId: "",
- unitNumber: "",
- vin: vehicleFormData.vin,
- plate: vehicleFormData.plate,
- make: vehicleFormData.make,
- year: vehicleFormData.year,
- countryCode: vehicleFormData.countryCode,
- provinceCode: vehicleFormData.provinceCode,
- trailerTypeCode: vehicleFormData.vehicleSubType,
- };
-
- switch (vehicleFormData.vehicleType) {
- case VEHICLE_TYPES.TRAILER:
- return {
- ...defaultTrailer,
- trailerId: getDefaultRequiredVal(
- "",
- (existingVehicle as Trailer)?.trailerId,
- ),
- unitNumber: getDefaultRequiredVal(
- "",
- existingVehicle?.unitNumber,
- vehicleFormData.unitNumber,
- ),
- } as Trailer;
- case VEHICLE_TYPES.POWER_UNIT:
- default:
- return {
- ...defaultPowerUnit,
- unitNumber: getDefaultRequiredVal(
- "",
- existingVehicle?.unitNumber,
- vehicleFormData.unitNumber,
- ),
- powerUnitId: getDefaultRequiredVal(
- "",
- (existingVehicle as PowerUnit)?.powerUnitId,
- ),
- } as PowerUnit;
- }
- };
-
- const modifyVehicleSuccess = (status: number) =>
- status === 201 || status === 200;
-
// If the vehicle type is a power unit then create a power unit object
if (vehicle.vehicleType === VEHICLE_TYPES.POWER_UNIT) {
const powerUnit = transformByVehicleType(
@@ -204,7 +206,7 @@ export const usePermitVehicleManagement = (companyId: number) => {
}
return undefined;
- };
+ }, [fetchedVehicles]);
return {
handleSaveVehicle,
diff --git a/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx b/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx
index eb59e5c27..4379cda5b 100644
--- a/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx
+++ b/frontend/src/features/permits/pages/Amend/components/AmendPermitForm.tsx
@@ -1,4 +1,4 @@
-import { useContext } from "react";
+import { useContext, useMemo } from "react";
import { FieldValues, FormProvider } from "react-hook-form";
import { useNavigate, useParams } from "react-router-dom";
@@ -11,8 +11,7 @@ import { PermitForm } from "../../Application/components/form/PermitForm";
import { Application } from "../../../types/application";
import { useCompanyInfoDetailsQuery } from "../../../../manageProfile/apiManager/hooks";
import { Breadcrumb } from "../../../../../common/components/breadcrumb/Breadcrumb";
-import { AmendRevisionHistory } from "./form/AmendRevisionHistory";
-import { AmendReason } from "./form/AmendReason";
+import { ApplicationFormContext } from "../../../context/ApplicationFormContext";
import { Nullable } from "../../../../../common/types/common";
import { ERROR_ROUTES } from "../../../../../routes/constants";
import { applyWhenNotNullable, getDefaultRequiredVal } from "../../../../../common/helpers/util";
@@ -20,6 +19,7 @@ import { PermitVehicleDetails } from "../../../types/PermitVehicleDetails";
import { AmendPermitFormData } from "../types/AmendPermitFormData";
import { getDatetimes } from "./helpers/getDatetimes";
import { PAST_START_DATE_STATUSES } from "../../../../../common/components/form/subFormComponents/CustomDatePicker";
+import { useFetchLOAs } from "../../../../settings/hooks/LOA";
import { useFetchSpecialAuthorizations } from "../../../../settings/hooks/specialAuthorizations";
import {
dayjsToUtcStr,
@@ -54,15 +54,34 @@ export const AmendPermitForm = () => {
const companyId: number = applyWhenNotNullable(id => Number(id), companyIdParam, 0);
const navigate = useNavigate();
+ const { data: activeLOAs } = useFetchLOAs(companyId, false);
const { data: companyInfo } = useCompanyInfoDetailsQuery(companyId);
- const doingBusinessAs = companyInfo?.alternateName;
-
const { data: specialAuthorizations } = useFetchSpecialAuthorizations(companyId);
const isLcvDesignated = Boolean(specialAuthorizations?.isLcvAllowed);
- const { formData, formMethods } = useAmendPermitForm(
+ const {
+ handleSaveVehicle,
+ vehicleOptions,
+ powerUnitSubTypes,
+ trailerSubTypes,
+ } = usePermitVehicleManagement(companyId);
+
+ const {
+ initialFormData,
+ formData,
+ formMethods,
+ onSetDuration,
+ onSetExpiryDate,
+ onSetConditions,
+ onToggleSaveVehicle,
+ onSetVehicle,
+ onClearVehicle,
+ onUpdateLOAs,
+ } = useAmendPermitForm(
currentStepIndex === 0,
isLcvDesignated,
+ getDefaultRequiredVal([], activeLOAs),
+ vehicleOptions,
companyInfo,
permit,
amendmentApplication,
@@ -73,17 +92,13 @@ export const AmendPermitForm = () => {
permit,
);
+ const applicableLOAs = getDefaultRequiredVal([], activeLOAs)
+ .filter(loa => loa.loaPermitType.includes(formData.permitType));
+
const amendPermitMutation = useAmendPermit(companyId);
const modifyAmendmentMutation = useModifyAmendmentApplication();
const snackBar = useContext(SnackBarContext);
- const {
- handleSaveVehicle,
- vehicleOptions,
- powerUnitSubTypes,
- trailerSubTypes,
- } = usePermitVehicleManagement(companyId);
-
const { handleSubmit } = formMethods;
// Helper method to return form field values as an Permit object
@@ -198,31 +213,67 @@ export const AmendPermitForm = () => {
(duration) => duration.value <= permitOldDuration,
);
+ const applicationFormContextData = useMemo(() => ({
+ initialFormData,
+ formData,
+ durationOptions,
+ vehicleOptions,
+ powerUnitSubtypes: powerUnitSubTypes,
+ trailerSubtypes: trailerSubTypes,
+ isLcvDesignated,
+ feature: FEATURE,
+ companyInfo,
+ isAmendAction: true,
+ createdDateTime,
+ updatedDateTime,
+ pastStartDateStatus: PAST_START_DATE_STATUSES.WARNING,
+ companyLOAs: applicableLOAs,
+ revisionHistory,
+ onLeave: undefined,
+ onSave: undefined,
+ onCancel: goHome,
+ onContinue: handleSubmit(onContinue),
+ onSetDuration,
+ onSetExpiryDate,
+ onSetConditions,
+ onToggleSaveVehicle,
+ onSetVehicle,
+ onClearVehicle,
+ onUpdateLOAs,
+ }), [
+ initialFormData,
+ formData,
+ durationOptions,
+ vehicleOptions,
+ powerUnitSubTypes,
+ trailerSubTypes,
+ isLcvDesignated,
+ companyInfo,
+ createdDateTime,
+ updatedDateTime,
+ applicableLOAs,
+ revisionHistory,
+ goHome,
+ onContinue,
+ onSetDuration,
+ onSetExpiryDate,
+ onSetConditions,
+ onToggleSaveVehicle,
+ onSetVehicle,
+ onClearVehicle,
+ onUpdateLOAs,
+ ]);
+
return (
);
diff --git a/frontend/src/features/permits/pages/Amend/components/AmendPermitReview.tsx b/frontend/src/features/permits/pages/Amend/components/AmendPermitReview.tsx
index 1448d1e60..91a08b3b3 100644
--- a/frontend/src/features/permits/pages/Amend/components/AmendPermitReview.tsx
+++ b/frontend/src/features/permits/pages/Amend/components/AmendPermitReview.tsx
@@ -160,6 +160,7 @@ export const AmendPermitReview = () => {
}}
calculatedFee={`${amountToRefund}`}
doingBusinessAs={doingBusinessAs}
+ loas={amendmentApplication?.permitData?.loas}
>
{amendmentApplication?.comment ? (
diff --git a/frontend/src/features/permits/pages/Amend/hooks/useAmendPermitForm.ts b/frontend/src/features/permits/pages/Amend/hooks/useAmendPermitForm.ts
index 1f1d665d6..275194cde 100644
--- a/frontend/src/features/permits/pages/Amend/hooks/useAmendPermitForm.ts
+++ b/frontend/src/features/permits/pages/Amend/hooks/useAmendPermitForm.ts
@@ -1,21 +1,30 @@
import { useEffect, useMemo } from "react";
import { useForm } from "react-hook-form";
+import dayjs, { Dayjs } from "dayjs";
import { Nullable } from "../../../../../common/types/common";
import { Permit } from "../../../types/permit";
import { Application } from "../../../types/application";
import { applyWhenNotNullable } from "../../../../../common/helpers/util";
import { CompanyProfile } from "../../../../manageProfile/types/manageProfile";
-import { applyLCVToApplicationData } from "../../../helpers/getDefaultApplicationFormData";
+import { applyLCVToApplicationData } from "../../../helpers/permitLCV";
+import { PermitCondition } from "../../../types/PermitCondition";
+import { EMPTY_VEHICLE_DETAILS, PermitVehicleDetails } from "../../../types/PermitVehicleDetails";
+import { LOADetail } from "../../../../settings/types/SpecialAuthorization";
+import { getIneligibleSubtypes } from "../../../helpers/permitVehicles";
import {
AmendPermitFormData,
getDefaultFormDataFromApplication,
getDefaultFormDataFromPermit,
} from "../types/AmendPermitFormData";
+import { applyUpToDateLOAsToApplication } from "../../../helpers/permitLOA";
+import { PowerUnit, Trailer } from "../../../../manageVehicles/types/Vehicle";
export const useAmendPermitForm = (
repopulateFormData: boolean,
isLcvDesignated: boolean,
+ loas: LOADetail[],
+ inventoryVehicles: (PowerUnit | Trailer)[],
companyInfo: Nullable,
permit?: Nullable,
amendmentApplication?: Nullable,
@@ -23,36 +32,74 @@ export const useAmendPermitForm = (
// Default form data values to populate the amend form with
const defaultFormData = useMemo(() => {
if (amendmentApplication) {
- return applyLCVToApplicationData(
- getDefaultFormDataFromApplication(
- companyInfo,
- amendmentApplication,
- ),
+ const ineligibleSubtypes = getIneligibleSubtypes(
+ amendmentApplication.permitType,
isLcvDesignated,
);
+
+ const ineligiblePowerUnitSubtypes= ineligibleSubtypes.ineligiblePowerUnitSubtypes
+ .map(({ typeCode }) => typeCode);
+
+ const ineligibleTrailerSubtypes = ineligibleSubtypes.ineligibleTrailerSubtypes
+ .map(({ typeCode }) => typeCode);
+
+ return applyUpToDateLOAsToApplication(
+ applyLCVToApplicationData(
+ getDefaultFormDataFromApplication(
+ companyInfo,
+ amendmentApplication,
+ ),
+ isLcvDesignated,
+ ),
+ loas,
+ inventoryVehicles,
+ ineligiblePowerUnitSubtypes,
+ ineligibleTrailerSubtypes,
+ );
}
// Permit doesn't have existing amendment application
// Populate form data with permit, with initial empty comment
- return applyLCVToApplicationData(
- getDefaultFormDataFromPermit(
- companyInfo,
- applyWhenNotNullable(
- (p) => ({
- ...p,
- comment: "",
- }),
- permit,
- ),
+ const defaultPermitFormData = getDefaultFormDataFromPermit(
+ companyInfo,
+ applyWhenNotNullable(
+ (p) => ({
+ ...p,
+ comment: "",
+ }),
+ permit,
),
+ );
+
+ const ineligibleSubtypes = getIneligibleSubtypes(
+ defaultPermitFormData.permitType,
isLcvDesignated,
);
+
+ const ineligiblePowerUnitSubtypes= ineligibleSubtypes.ineligiblePowerUnitSubtypes
+ .map(({ typeCode }) => typeCode);
+
+ const ineligibleTrailerSubtypes = ineligibleSubtypes.ineligibleTrailerSubtypes
+ .map(({ typeCode }) => typeCode);
+
+ return applyUpToDateLOAsToApplication(
+ applyLCVToApplicationData(
+ defaultPermitFormData,
+ isLcvDesignated,
+ ),
+ loas,
+ inventoryVehicles,
+ ineligiblePowerUnitSubtypes,
+ ineligibleTrailerSubtypes,
+ );
}, [
amendmentApplication,
permit,
repopulateFormData,
companyInfo,
isLcvDesignated,
+ loas,
+ inventoryVehicles,
]);
// Register default values with react-hook-form
@@ -61,15 +108,56 @@ export const useAmendPermitForm = (
reValidateMode: "onBlur",
});
- const { reset, watch } = formMethods;
+ const { reset, watch, setValue } = formMethods;
const formData = watch();
useEffect(() => {
reset(defaultFormData);
}, [defaultFormData]);
+ const onSetDuration = (duration: number) => {
+ setValue("permitData.permitDuration", duration);
+ };
+
+ const onSetExpiryDate = (expiry: Dayjs) => {
+ setValue("permitData.expiryDate", dayjs(expiry));
+ };
+
+ const onSetConditions = (conditions: PermitCondition[]) => {
+ setValue("permitData.commodities", [...conditions]);
+ };
+
+ const onToggleSaveVehicle = (saveVehicle: boolean) => {
+ setValue("permitData.vehicleDetails.saveVehicle", saveVehicle);
+ };
+
+ const onSetVehicle = (vehicleDetails: PermitVehicleDetails) => {
+ setValue("permitData.vehicleDetails", {
+ ...vehicleDetails,
+ });
+ };
+
+ const onClearVehicle = (saveVehicle: boolean) => {
+ setValue("permitData.vehicleDetails", {
+ ...EMPTY_VEHICLE_DETAILS,
+ saveVehicle,
+ });
+ };
+
+ const onUpdateLOAs = (updatedLOAs: LOADetail[]) => {
+ setValue("permitData.loas", updatedLOAs);
+ };
+
return {
+ initialFormData: defaultFormData,
formData,
formMethods,
+ onSetDuration,
+ onSetExpiryDate,
+ onSetConditions,
+ onToggleSaveVehicle,
+ onSetVehicle,
+ onClearVehicle,
+ onUpdateLOAs,
};
};
diff --git a/frontend/src/features/permits/pages/Application/ApplicationForm.tsx b/frontend/src/features/permits/pages/Application/ApplicationForm.tsx
index 0b3f7ac88..c5d9b48d6 100644
--- a/frontend/src/features/permits/pages/Application/ApplicationForm.tsx
+++ b/frontend/src/features/permits/pages/Application/ApplicationForm.tsx
@@ -1,6 +1,6 @@
import { FieldValues, FormProvider } from "react-hook-form";
import { useNavigate } from "react-router-dom";
-import { useContext, useState } from "react";
+import { useContext, useMemo, useState } from "react";
import dayjs from "dayjs";
import "./ApplicationForm.scss";
@@ -11,7 +11,7 @@ import { useSaveApplicationMutation } from "../../hooks/hooks";
import { SnackBarContext } from "../../../../App";
import { LeaveApplicationDialog } from "../../components/dialog/LeaveApplicationDialog";
import { areApplicationDataEqual } from "../../helpers/equality";
-import { useDefaultApplicationFormData } from "../../hooks/useDefaultApplicationFormData";
+import { useInitApplicationFormData } from "../../hooks/useInitApplicationFormData";
import OnRouteBCContext from "../../../../common/authentication/OnRouteBCContext";
import { PermitForm } from "./components/form/PermitForm";
import { usePermitVehicleManagement } from "../../hooks/usePermitVehicleManagement";
@@ -22,7 +22,10 @@ import { PermitVehicleDetails } from "../../types/PermitVehicleDetails";
import { durationOptionsForPermitType } from "../../helpers/dateSelection";
import { getCompanyIdFromSession } from "../../../../common/apiManager/httpRequestHandler";
import { PAST_START_DATE_STATUSES } from "../../../../common/components/form/subFormComponents/CustomDatePicker";
+import { useFetchLOAs } from "../../../settings/hooks/LOA";
import { useFetchSpecialAuthorizations } from "../../../settings/hooks/specialAuthorizations";
+import { ApplicationFormContext } from "../../context/ApplicationFormContext";
+import { filterLOAsForPermitType, filterNonExpiredLOAs } from "../../helpers/permitLOA";
import {
applyWhenNotNullable,
getDefaultRequiredVal,
@@ -64,11 +67,19 @@ export const ApplicationForm = ({ permitType }: { permitType: PermitType }) => {
companyInfo?.companyId,
);
+ const { data: activeLOAs } = useFetchLOAs(companyId, false);
const { data: specialAuthorizations } = useFetchSpecialAuthorizations(companyId);
const isLcvDesignated = Boolean(specialAuthorizations?.isLcvAllowed);
-
+
+ const {
+ handleSaveVehicle,
+ vehicleOptions,
+ powerUnitSubTypes,
+ trailerSubTypes,
+ } = usePermitVehicleManagement(companyId);
+
// Use a custom hook that performs the following whenever page is rendered (or when application context is updated/changed):
- // 1. Get all data needed to generate default values for the application form (from application context, company, user details)
+ // 1. Get all data needed to initialize the application form (from application context, company, user details)
// 2. Generate those default values and register them to the form
// 3. Listens for changes to application context (which happens when application is fetched/submitted/updated)
// 4. Updates form values (override existing ones) whenever the application context data changes
@@ -76,14 +87,34 @@ export const ApplicationForm = ({ permitType }: { permitType: PermitType }) => {
initialFormData,
currentFormData,
formMethods,
- } = useDefaultApplicationFormData(
+ onSetDuration,
+ onSetExpiryDate,
+ onSetConditions,
+ onToggleSaveVehicle,
+ onSetVehicle,
+ onClearVehicle,
+ onUpdateLOAs,
+ } = useInitApplicationFormData(
permitType,
isLcvDesignated,
+ getDefaultRequiredVal([], activeLOAs),
+ vehicleOptions,
companyInfo,
applicationContext?.applicationData,
userDetails,
);
+ // Applicable LOAs must be:
+ // 1. Applicable for the current permit type
+ // 2. Have expiry date that is on or after the start date for an application
+ const applicableLOAs = filterNonExpiredLOAs(
+ filterLOAsForPermitType(
+ getDefaultRequiredVal([], activeLOAs),
+ permitType,
+ ),
+ currentFormData.permitData.startDate,
+ );
+
const createdDateTime = applyWhenNotNullable(
(date) => dayjs(date),
applicationContext?.applicationData?.createdDateTime,
@@ -94,18 +125,9 @@ export const ApplicationForm = ({ permitType }: { permitType: PermitType }) => {
applicationContext?.applicationData?.updatedDateTime,
);
- const doingBusinessAs = companyInfo?.alternateName;
-
const saveApplicationMutation = useSaveApplicationMutation();
const snackBar = useContext(SnackBarContext);
- const {
- handleSaveVehicle,
- vehicleOptions,
- powerUnitSubTypes,
- trailerSubTypes,
- } = usePermitVehicleManagement(companyId);
-
// Show leave application dialog
const [showLeaveApplicationDialog, setShowLeaveApplicationDialog] =
useState(false);
@@ -242,33 +264,73 @@ export const ApplicationForm = ({ permitType }: { permitType: PermitType }) => {
setShowLeaveApplicationDialog(false);
};
+ const durationOptions = durationOptionsForPermitType(permitType);
+ const pastStartDateStatus = isStaffUser
+ ? PAST_START_DATE_STATUSES.WARNING
+ : PAST_START_DATE_STATUSES.FAIL;
+
+ const applicationFormContextData = useMemo(() => ({
+ initialFormData,
+ formData: currentFormData,
+ durationOptions,
+ vehicleOptions,
+ powerUnitSubtypes: powerUnitSubTypes,
+ trailerSubtypes: trailerSubTypes,
+ isLcvDesignated,
+ feature: FEATURE,
+ companyInfo,
+ isAmendAction: false,
+ createdDateTime,
+ updatedDateTime,
+ pastStartDateStatus,
+ companyLOAs: applicableLOAs,
+ revisionHistory: [],
+ onLeave: handleLeaveApplication,
+ onSave,
+ onCancel: undefined,
+ onContinue: handleSubmit(onContinue),
+ onSetDuration,
+ onSetExpiryDate,
+ onSetConditions,
+ onToggleSaveVehicle,
+ onSetVehicle,
+ onClearVehicle,
+ onUpdateLOAs,
+ }), [
+ initialFormData,
+ currentFormData,
+ durationOptions,
+ vehicleOptions,
+ powerUnitSubTypes,
+ trailerSubTypes,
+ isLcvDesignated,
+ companyInfo,
+ createdDateTime,
+ updatedDateTime,
+ pastStartDateStatus,
+ applicableLOAs,
+ handleLeaveApplication,
+ onSave,
+ onContinue,
+ onSetDuration,
+ onSetExpiryDate,
+ onSetConditions,
+ onToggleSaveVehicle,
+ onSetVehicle,
+ onClearVehicle,
+ onUpdateLOAs,
+ ]);
+
return (
-
+
+
+
{
const {
- applicationData: applicationContextData,
+ applicationData,
setApplicationData: setApplicationContextData,
} = useContext(ApplicationContext);
- const companyId = getDefaultRequiredVal(0, applicationContextData?.companyId);
+ const companyId = getDefaultRequiredVal(0, applicationData?.companyId);
const { data: specialAuth } = useFetchSpecialAuthorizations(companyId);
- const isLcvDesignated = Boolean(specialAuth?.isLcvAllowed);
const isNoFeePermitType = Boolean(specialAuth?.noFeeType);
const { data: companyInfo } = useCompanyInfoQuery();
const doingBusinessAs = companyInfo?.alternateName;
- const applicationData = applyLCVToApplicationData(
- applicationContextData,
- isLcvDesignated,
- );
-
const fee = isNoFeePermitType
? "0"
: `${calculateFeeByDuration(
@@ -177,6 +170,7 @@ export const ApplicationReview = () => {
}
doingBusinessAs={doingBusinessAs}
calculatedFee={fee}
+ loas={applicationData?.permitData?.loas}
/>
diff --git a/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.scss b/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.scss
index 751c404d9..1761d3f56 100644
--- a/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.scss
+++ b/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.scss
@@ -19,6 +19,9 @@
}
& &__cell {
+ padding-top: 0.5rem;
+ padding-bottom: 0.5rem;
+
&--checkbox {
padding-left: 0;
}
@@ -30,7 +33,13 @@
}
}
- & &__checkbox#{&}__checkbox--disabled {
- color: $disabled-colour;
+ & &__form-control#{&}__form-control--disabled {
+ .condition-checkbox {
+ color: $disabled-colour;
+ }
+
+ .condition-description {
+ color: $bc-black;
+ }
}
}
diff --git a/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.tsx b/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.tsx
index a7dd946d6..3cb90f67d 100644
--- a/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.tsx
+++ b/frontend/src/features/permits/pages/Application/components/form/ConditionsTable.tsx
@@ -73,15 +73,15 @@ export const ConditionsTable = ({
void;
+}) => {
+ return (
+
+
+
+
+
+ LOA #
+
+
+
+ Expiry Date
+
+
+
+
+
+ {loas.map((selectableLOA) => (
+
+
+ onSelectLOA?.(selectableLOA.loa.loaNumber)}
+ />
+ }
+ key={selectableLOA.loa.loaNumber}
+ label={selectableLOA.loa.loaNumber}
+ classes={{
+ root: "loa-table__form-control",
+ disabled: "loa-table__form-control loa-table__form-control--disabled",
+ }}
+ slotProps={{
+ typography: {
+ className: "loa-number",
+ },
+ }}
+ />
+
+
+
+ {applyWhenNotNullable(
+ expiryDate => toLocal(expiryDate, DATE_FORMATS.DATEONLY_SLASH),
+ selectableLOA.loa.expiryDate,
+ "Never expires",
+ )}
+
+
+ ))}
+
+
+
+ );
+};
diff --git a/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx b/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx
index 11356c1b1..245f1315b 100644
--- a/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx
+++ b/frontend/src/features/permits/pages/Application/components/form/PermitForm.tsx
@@ -1,7 +1,4 @@
import { Box } from "@mui/material";
-import dayjs, { Dayjs } from "dayjs";
-import { useFormContext } from "react-hook-form";
-import { useEffect } from "react";
import "./PermitForm.scss";
import { FormActions } from "./FormActions";
@@ -9,170 +6,120 @@ import { ApplicationDetails } from "../../../../components/form/ApplicationDetai
import { ContactDetails } from "../../../../components/form/ContactDetails";
import { PermitDetails } from "./PermitDetails";
import { VehicleDetails } from "./VehicleDetails/VehicleDetails";
-import { CompanyProfile } from "../../../../../manageProfile/types/manageProfile.d";
-import { Nullable } from "../../../../../../common/types/common";
-import { EMPTY_VEHICLE_DETAILS, PermitVehicleDetails } from "../../../../types/PermitVehicleDetails";
-import { PastStartDateStatus } from "../../../../../../common/components/form/subFormComponents/CustomDatePicker";
+import { PermitLOA } from "./PermitLOA";
+import { LOADetail } from "../../../../../settings/types/SpecialAuthorization";
import { isVehicleSubtypeLCV } from "../../../../../manageVehicles/helpers/vehicleSubtypes";
-import { PermitCondition } from "../../../../types/PermitCondition";
-import { LCV_CONDITION } from "../../../../constants/constants";
-import { sortConditions } from "../../../../helpers/conditions";
import { getStartOfDate } from "../../../../../../common/helpers/formatDate";
-import { getExpiryDate } from "../../../../helpers/permitState";
-import {
- PowerUnit,
- Trailer,
- VehicleSubType,
-} from "../../../../../manageVehicles/types/Vehicle";
+import { useApplicationFormContext } from "../../../../hooks/useApplicationFormContext";
+import { AmendReason } from "../../../Amend/components/form/AmendReason";
+import { AmendRevisionHistory } from "../../../Amend/components/form/AmendRevisionHistory";
+
+export const PermitForm = () => {
+ const {
+ formData,
+ availableDurationOptions,
+ powerUnitSubtypes,
+ trailerSubtypes,
+ isLcvDesignated,
+ ineligiblePowerUnitSubtypes,
+ ineligibleTrailerSubtypes,
+ filteredVehicleOptions,
+ feature,
+ companyInfo,
+ isAmendAction,
+ createdDateTime,
+ updatedDateTime,
+ pastStartDateStatus,
+ companyLOAs,
+ revisionHistory,
+ onLeave,
+ onSave,
+ onCancel,
+ onContinue,
+ onSetConditions,
+ onToggleSaveVehicle,
+ onSetVehicle,
+ onClearVehicle,
+ onUpdateLOAs,
+ } = useApplicationFormContext();
+
+ const permitType = formData.permitType;
+ const applicationNumber = formData.applicationNumber;
+ const permitNumber = formData.permitNumber;
+ const startDate = getStartOfDate(formData.permitData.startDate);
+ const expiryDate = formData.permitData.expiryDate;
+ const permitConditions = formData.permitData.commodities;
+ const vehicleFormData = formData.permitData.vehicleDetails;
+ const currentSelectedLOAs = formData.permitData.loas as LOADetail[];
-import {
- getIneligiblePowerUnitSubtypes,
- getIneligibleTrailerSubtypes,
-} from "../../../../helpers/permitVehicles";
-
-interface PermitFormProps {
- feature: string;
- onLeave?: () => void;
- onSave?: () => Promise;
- onCancel?: () => void;
- onContinue: () => Promise;
- isAmendAction: boolean;
- permitNumber?: Nullable;
- createdDateTime?: Nullable;
- updatedDateTime?: Nullable;
- vehicleOptions: (PowerUnit | Trailer)[];
- powerUnitSubTypes: VehicleSubType[];
- trailerSubTypes: VehicleSubType[];
- children?: React.ReactNode;
- companyInfo?: Nullable;
- durationOptions: {
- value: number;
- label: string;
- }[];
- doingBusinessAs?: Nullable;
- pastStartDateStatus: PastStartDateStatus;
- isLcvDesignated: boolean;
-}
-
-export const PermitForm = (props: PermitFormProps) => {
- const { watch, setValue } = useFormContext();
-
- const permitType = watch("permitType");
- const applicationNumber = watch("applicationNumber");
- const permitStartDate = watch("permitData.startDate");
- const startDate = getStartOfDate(permitStartDate);
- const permitDuration = watch("permitData.permitDuration");
- const permitConditions = watch("permitData.commodities");
- const vehicleFormData = watch("permitData.vehicleDetails");
-
- const handleSetConditions = (conditions: PermitCondition[]) => {
- setValue("permitData.commodities", [...conditions]);
- };
-
- const handleToggleSaveVehicle = (saveVehicle: boolean) => {
- setValue("permitData.vehicleDetails.saveVehicle", saveVehicle);
- };
-
- const handleSetVehicle = (vehicleDetails: PermitVehicleDetails) => {
- setValue("permitData.vehicleDetails", {
- ...vehicleDetails,
- });
- };
-
- const handleClearVehicle = (saveVehicle: boolean) => {
- setValue("permitData.vehicleDetails", {
- ...EMPTY_VEHICLE_DETAILS,
- saveVehicle,
- });
- };
-
- const handleSetExpiryDate = (expiry: Dayjs) => {
- setValue("permitData.expiryDate", dayjs(expiry));
- };
-
- const isLcvDesignated = props.isLcvDesignated;
- const ineligiblePowerUnitSubtypes = getIneligiblePowerUnitSubtypes(permitType)
- .filter(subtype => !isLcvDesignated || !isVehicleSubtypeLCV(subtype.typeCode));
-
- // Permit expiry date === Permit start date + Permit duration - 1
- const expiryDate = getExpiryDate(startDate, permitDuration);
- useEffect(() => {
- handleSetExpiryDate(expiryDate);
- }, [expiryDate]);
-
- const isAmendAction = props.isAmendAction;
-
- const vehicleSubtype = vehicleFormData.vehicleSubType;
- useEffect(() => {
- if (
- !isVehicleSubtypeLCV(vehicleSubtype)
- && permitConditions.some(({ condition }: PermitCondition) => condition === LCV_CONDITION.condition)
- ) {
- // If vehicle subtype in the form isn't LCV but conditions have LCV,
- // then remove that LCV condition from the form
- handleSetConditions(permitConditions.filter(
- ({ condition }: PermitCondition) => condition !== LCV_CONDITION.condition,
- ));
- } else if (
- isVehicleSubtypeLCV(vehicleSubtype)
- && !permitConditions.some(({ condition }: PermitCondition) => condition === LCV_CONDITION.condition)
- ) {
- // If vehicle subtype in the form is LCV but conditions don't have LCV,
- // then add that LCV condition into the form
- handleSetConditions(sortConditions([...permitConditions, LCV_CONDITION]));
- }
- }, [vehicleSubtype, permitConditions]);
-
return (
-
+
+
+
- {props.children}
+
+ {isAmendAction ? (
+ <>
+
+
+ >
+ ) : null}
);
diff --git a/frontend/src/features/permits/pages/Application/components/form/PermitLOA.scss b/frontend/src/features/permits/pages/Application/components/form/PermitLOA.scss
new file mode 100644
index 000000000..dea8fc6f2
--- /dev/null
+++ b/frontend/src/features/permits/pages/Application/components/form/PermitLOA.scss
@@ -0,0 +1,40 @@
+@use "../../../../../../themes/orbcStyles";
+
+@include orbcStyles.permit-main-box-style(".permit-loa");
+@include orbcStyles.permit-left-box-style(".permit-loa__header");
+@include orbcStyles.permit-right-box-style(".permit-loa__body");
+
+.permit-loa {
+ & &__header {
+ h3 {
+ padding-top: 1rem;
+ }
+ }
+
+ .loa-title {
+ color: orbcStyles.$bc-black;
+ padding-top: 1rem;
+
+ &__title {
+ font-weight: bold;
+ font-size: 1.25rem;
+
+ &--optional {
+ font-weight: normal;
+ font-size: 1.25rem;
+ margin-left: 0.25rem;
+ }
+ }
+ }
+
+ .loa-info {
+ margin-top: 1.5rem;
+ margin-bottom: 1.5rem;
+ width: calc(100% - 3rem);
+
+ &__message {
+ font-size: 1rem;
+ font-weight: normal;
+ }
+ }
+}
diff --git a/frontend/src/features/permits/pages/Application/components/form/PermitLOA.tsx b/frontend/src/features/permits/pages/Application/components/form/PermitLOA.tsx
new file mode 100644
index 000000000..04d79e251
--- /dev/null
+++ b/frontend/src/features/permits/pages/Application/components/form/PermitLOA.tsx
@@ -0,0 +1,104 @@
+import { useEffect, useMemo } from "react";
+import { Dayjs } from "dayjs";
+import { Box, Typography } from "@mui/material";
+
+import "./PermitLOA.scss";
+import { InfoBcGovBanner } from "../../../../../../common/components/banners/InfoBcGovBanner";
+import { BANNER_MESSAGES } from "../../../../../../common/constants/bannerMessages";
+import { LOADetail } from "../../../../../settings/types/SpecialAuthorization";
+import { LOATable } from "./LOATable";
+import { PermitType } from "../../../../types/PermitType";
+import { getMinPermitExpiryDate } from "../../../../helpers/dateSelection";
+import { areArraysEqual } from "../../../../../../common/helpers/util";
+import { getUpdatedLOASelection } from "../../../../helpers/permitLOA";
+
+export const PermitLOA = ({
+ permitType,
+ startDate,
+ selectedLOAs,
+ companyLOAs,
+ onUpdateLOAs,
+}: {
+ permitType: PermitType;
+ startDate: Dayjs;
+ selectedLOAs: LOADetail[];
+ companyLOAs: LOADetail[];
+ onUpdateLOAs: (updatedLOAs: LOADetail[]) => void,
+}) => {
+ const minPermitExpiryDate = getMinPermitExpiryDate(permitType, startDate);
+
+ // Only show the current active company LOAs as selectable LOAs
+ const loasForTable = useMemo(() => getUpdatedLOASelection(
+ companyLOAs,
+ selectedLOAs,
+ minPermitExpiryDate,
+ ), [
+ companyLOAs,
+ selectedLOAs,
+ minPermitExpiryDate,
+ ]);
+
+ // Since certain LOAs might have been removed from the table, we need to make sure
+ // that the selected LOAs in the permit form matches the selection state of the table
+ const selectedLOAsInTable = loasForTable
+ .filter(selectableLOA => selectableLOA.checked)
+ .map(selectableLOA => selectableLOA.loa);
+
+ const selectedLOANumbers = selectedLOAs.map(loa => loa.loaNumber);
+
+ useEffect(() => {
+ const selectedNumbersInTable = selectedLOAsInTable.map(loa => loa.loaNumber);
+ if (!areArraysEqual(selectedLOANumbers, selectedNumbersInTable)) {
+ onUpdateLOAs([...selectedLOAsInTable]);
+ }
+ }, [selectedLOANumbers, selectedLOAsInTable]);
+
+ const handleSelectLOA = (loaNumber: number) => {
+ const loa = loasForTable.find(loaRow => loaRow.loa.loaNumber === loaNumber);
+ if (!loa || loa?.disabled) return;
+
+ const isLOASelected = Boolean(loa?.checked);
+ if (isLOASelected) {
+ // Deselect the LOA
+ onUpdateLOAs(
+ selectedLOAs.filter(selectedLOA => selectedLOA.loaNumber !== loaNumber),
+ );
+ } else {
+ // Select the LOA
+ const { loa: loaToSelect } = loa;
+ onUpdateLOAs([...selectedLOAs, loaToSelect]);
+ }
+ };
+
+ return (
+
+
+
+ Letter of Authorization (LOA)
+
+
+
+
+
+ Select the relevant LOA(s)
+ (optional)
+
+
+
+ {BANNER_MESSAGES.LOA_VEHICLE_CANNOT_BE_EDITED_IN_PERMIT}
+
+ }
+ />
+
+
+
+
+ );
+};
diff --git a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx
index c9b3acba4..1e0667ece 100644
--- a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx
+++ b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/VehicleDetails.tsx
@@ -25,6 +25,7 @@ import { SelectVehicleDropdown } from "./customFields/SelectVehicleDropdown";
import { BANNER_MESSAGES } from "../../../../../../../common/constants/bannerMessages";
import { PermitVehicleDetails } from "../../../../../types/PermitVehicleDetails";
import { EMPTY_VEHICLE_SUBTYPE } from "../../../../../../manageVehicles/helpers/vehicleSubtypes";
+import { LOADetail } from "../../../../../../settings/types/SpecialAuthorization";
import {
PowerUnit,
Trailer,
@@ -82,6 +83,8 @@ const getEligibleSubtypeOptions = (
trailerSubtypes: VehicleSubType[],
ineligiblePowerUnitSubtypes: VehicleSubType[],
ineligibleTrailerSubtypes: VehicleSubType[],
+ allowedLOAPowerUnitSubtypes: string[],
+ allowedLOATrailerSubtypes: string[],
vehicleType?: string,
) => {
if (
@@ -102,6 +105,8 @@ const getEligibleSubtypeOptions = (
vehicleType,
ineligiblePowerUnitSubtypes,
ineligibleTrailerSubtypes,
+ allowedLOAPowerUnitSubtypes,
+ allowedLOATrailerSubtypes,
);
};
@@ -113,6 +118,7 @@ export const VehicleDetails = ({
trailerSubtypes,
ineligiblePowerUnitSubtypes,
ineligibleTrailerSubtypes,
+ selectedLOAs,
onSetSaveVehicle,
onSetVehicle,
onClearVehicle,
@@ -124,6 +130,7 @@ export const VehicleDetails = ({
trailerSubtypes: VehicleSubType[];
ineligiblePowerUnitSubtypes: VehicleSubType[];
ineligibleTrailerSubtypes: VehicleSubType[];
+ selectedLOAs: LOADetail[];
onSetSaveVehicle: (saveVehicle: boolean) => void;
onSetVehicle: (vehicleDetails: PermitVehicleDetails) => void;
onClearVehicle: (saveVehicle: boolean) => void;
@@ -166,6 +173,50 @@ export const VehicleDetails = ({
EMPTY_VEHICLE_SUBTYPE,
]);
+ // Find vehicle subtypes that are allowed by LOAs
+ const permittedLOAPowerUnitIds = new Set([
+ ...selectedLOAs.map(loa => loa.powerUnits)
+ .reduce((prevPowerUnits, currPowerUnits) => [
+ ...prevPowerUnits,
+ ...currPowerUnits,
+ ], []),
+ ]);
+
+ const permittedLOATrailerIds = new Set([
+ ...selectedLOAs.map(loa => loa.trailers)
+ .reduce((prevTrailers, currTrailers) => [
+ ...prevTrailers,
+ ...currTrailers,
+ ], []),
+ ]);
+
+ const powerUnitsInInventory = vehicleOptions
+ .filter(vehicle => vehicle.vehicleType === VEHICLE_TYPES.POWER_UNIT) as PowerUnit[];
+
+ const trailersInInventory = vehicleOptions
+ .filter(vehicle => vehicle.vehicleType === VEHICLE_TYPES.TRAILER) as Trailer[];
+
+ const permittedLOAPowerUnitSubtypes = powerUnitsInInventory
+ .filter(powerUnit => permittedLOAPowerUnitIds.has(powerUnit.powerUnitId as string))
+ .map(powerUnit => powerUnit.powerUnitTypeCode);
+
+ const permittedLOATrailerSubtypes = trailersInInventory
+ .filter(trailer => permittedLOATrailerIds.has(trailer.trailerId as string))
+ .map(trailer => trailer.trailerTypeCode);
+
+ // Check if selected vehicle is an LOA vehicle
+ const isSelectedVehicleAllowedByLOA = Boolean(vehicleFormData.vehicleId)
+ && (
+ permittedLOAPowerUnitIds.has(vehicleFormData.vehicleId as string)
+ || permittedLOATrailerIds.has(vehicleFormData.vehicleId as string)
+ )
+ && (
+ powerUnitsInInventory.map(powerUnit => powerUnit.powerUnitId)
+ .includes(vehicleFormData.vehicleId as string)
+ || trailersInInventory.map(trailer => trailer.trailerId)
+ .includes(vehicleFormData.vehicleId as string)
+ );
+
useEffect(() => {
// Update subtype options when vehicle type changes
const subtypes = getEligibleSubtypeOptions(
@@ -173,6 +224,8 @@ export const VehicleDetails = ({
trailerSubtypes,
ineligiblePowerUnitSubtypes,
ineligibleTrailerSubtypes,
+ permittedLOAPowerUnitSubtypes,
+ permittedLOATrailerSubtypes,
vehicleType,
);
setSubtypeOptions(subtypes);
@@ -182,6 +235,8 @@ export const VehicleDetails = ({
ineligiblePowerUnitSubtypes,
ineligibleTrailerSubtypes,
vehicleType,
+ permittedLOAPowerUnitSubtypes,
+ permittedLOATrailerSubtypes,
]);
// Set the "Save to Inventory" radio button to false on render
@@ -257,6 +312,13 @@ export const VehicleDetails = ({
}
};
+ // If the selected vehicle is an LOA vehicle, it should not be edited/saved to inventory
+ useEffect(() => {
+ if (isSelectedVehicleAllowedByLOA) {
+ setSaveVehicle(false);
+ }
+ }, [isSelectedVehicleAllowedByLOA]);
+
return (
@@ -305,8 +367,9 @@ export const VehicleDetails = ({
vehicleOptions={vehicleOptions}
handleClearVehicle={() => onClearVehicle(saveVehicle)}
handleSelectVehicle={onSelectVehicle}
- ineligiblePowerUnitSubtypes={ineligiblePowerUnitSubtypes}
- ineligibleTrailerSubtypes={ineligibleTrailerSubtypes}
+ ineligiblePowerUnitSubtypes={ineligiblePowerUnitSubtypes.map(({ typeCode }) => typeCode)}
+ ineligibleTrailerSubtypes={ineligibleTrailerSubtypes.map(({ typeCode }) => typeCode)}
+ loas={selectedLOAs}
/>
@@ -324,6 +387,8 @@ export const VehicleDetails = ({
width: formFieldStyle.width,
customHelperText: "last 6 digits",
}}
+ readOnly={isSelectedVehicleAllowedByLOA}
+ disabled={isSelectedVehicleAllowedByLOA}
/>
))}
+ readOnly={isSelectedVehicleAllowedByLOA}
+ disabled={isSelectedVehicleAllowedByLOA}
/>
@@ -459,6 +534,8 @@ export const VehicleDetails = ({
"data-testid": "save-vehicle-yes",
} as CustomInputHTMLAttributes
}
+ readOnly={isSelectedVehicleAllowedByLOA}
+ disabled={isSelectedVehicleAllowedByLOA}
/>
}
label="Yes"
@@ -473,6 +550,8 @@ export const VehicleDetails = ({
"data-testid": "save-vehicle-no",
} as CustomInputHTMLAttributes
}
+ readOnly={isSelectedVehicleAllowedByLOA}
+ disabled={isSelectedVehicleAllowedByLOA}
/>
}
label="No"
diff --git a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx
index 06f7b96cc..4505eefc4 100644
--- a/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx
+++ b/frontend/src/features/permits/pages/Application/components/form/VehicleDetails/customFields/SelectVehicleDropdown.tsx
@@ -17,13 +17,12 @@ import { VEHICLE_CHOOSE_FROM } from "../../../../../../constants/constants";
import { EMPTY_VEHICLE_UNIT_NUMBER } from "../../../../../../../../common/constants/constants";
import { Nullable } from "../../../../../../../../common/types/common";
import { PermitVehicleDetails } from "../../../../../../types/PermitVehicleDetails";
-
+import { LOADetail } from "../../../../../../../settings/types/SpecialAuthorization";
import {
PowerUnit,
Trailer,
VEHICLE_TYPES,
Vehicle,
- VehicleSubType,
} from "../../../../../../../manageVehicles/types/Vehicle";
const GroupHeader = styled("div")(({ theme }) => ({
@@ -53,6 +52,7 @@ export const SelectVehicleDropdown = ({
handleClearVehicle,
ineligiblePowerUnitSubtypes,
ineligibleTrailerSubtypes,
+ loas,
}: {
chooseFrom: string;
selectedVehicle: Nullable;
@@ -60,8 +60,9 @@ export const SelectVehicleDropdown = ({
vehicleOptions: Vehicle[];
handleSelectVehicle: (vehicle: Vehicle) => void;
handleClearVehicle: () => void;
- ineligiblePowerUnitSubtypes: VehicleSubType[];
- ineligibleTrailerSubtypes: VehicleSubType[];
+ ineligiblePowerUnitSubtypes: string[];
+ ineligibleTrailerSubtypes: string[];
+ loas: LOADetail[];
}) => {
const sortedVehicles = sortVehicles(chooseFrom, vehicleOptions);
@@ -69,6 +70,7 @@ export const SelectVehicleDropdown = ({
sortedVehicles,
ineligiblePowerUnitSubtypes,
ineligibleTrailerSubtypes,
+ loas,
);
const selectedOption = selectedVehicle
diff --git a/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx b/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx
index 700da08c0..6abb845f1 100644
--- a/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx
+++ b/frontend/src/features/permits/pages/Application/components/form/tests/helpers/prepare.tsx
@@ -61,6 +61,7 @@ const TestFormWrapper = (props: React.PropsWithChildren) => {
permitDuration: defaultDuration,
expiryDate: getExpiryDate(currentDt, defaultDuration),
commodities: [],
+ loas: [],
},
},
reValidateMode: "onBlur",
diff --git a/frontend/src/features/permits/pages/Application/components/pay/CVPayInPersonInfo.scss b/frontend/src/features/permits/pages/Application/components/pay/CVPayInPersonInfo.scss
index 575c02b8c..b33423f60 100644
--- a/frontend/src/features/permits/pages/Application/components/pay/CVPayInPersonInfo.scss
+++ b/frontend/src/features/permits/pages/Application/components/pay/CVPayInPersonInfo.scss
@@ -1,7 +1,7 @@
@import "../../../../../../themes/orbcStyles";
.cv-pay-in-person-info {
- padding: 1.5rem 0;
+ padding: 0;
&__heading {
color: $bc-black;
diff --git a/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.tsx b/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.tsx
index d624a74ff..b5587df1d 100644
--- a/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.tsx
+++ b/frontend/src/features/permits/pages/Application/components/pay/ChoosePaymentMethod.tsx
@@ -1,17 +1,21 @@
import { Controller, useFormContext } from "react-hook-form";
import { Box, RadioGroup, Typography } from "@mui/material";
+
+import "./ChoosePaymentMethod.scss";
+import { CVPayInPersonInfo } from "./CVPayInPersonInfo";
import { PaymentOption } from "./PaymentOption";
import { PaymentMethodTypeCode } from "../../../../../../common/types/paymentMethods";
import {
DEFAULT_EMPTY_CARD_TYPE,
DEFAULT_EMPTY_PAYMENT_TYPE,
} from "./types/PaymentMethodData";
-import "./ChoosePaymentMethod.scss";
export const ChoosePaymentMethod = ({
availablePaymentMethods,
+ showPayInPersonInfo,
}: {
availablePaymentMethods: PaymentMethodTypeCode[];
+ showPayInPersonInfo: boolean;
}) => {
const { control, watch, setValue, clearErrors } = useFormContext();
const currPaymentMethod = watch("paymentMethod");
@@ -67,6 +71,10 @@ export const ChoosePaymentMethod = ({
handlePaymentMethodChange={handlePaymentMethodChange}
/>
))}
+
+ {showPayInPersonInfo ? (
+
+ ) : null}
)}
/>
diff --git a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx
index c4c9fbfc8..498be851a 100644
--- a/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx
+++ b/frontend/src/features/permits/pages/Application/components/review/PermitReview.tsx
@@ -17,6 +17,8 @@ import { PermitContactDetails } from "../../../../types/PermitContactDetails";
import { PermitVehicleDetails } from "../../../../types/PermitVehicleDetails";
import { Application } from "../../../../types/application";
import { PermitCondition } from "../../../../types/PermitCondition";
+import { ReviewPermitLOAs } from "./ReviewPermitLOAs";
+import { LOADetail } from "../../../../../settings/types/SpecialAuthorization";
import {
PERMIT_REVIEW_CONTEXTS,
PermitReviewContext,
@@ -56,6 +58,7 @@ interface PermitReviewProps {
oldFields?: Nullable>;
calculatedFee: string;
doingBusinessAs?: Nullable;
+ loas?: Nullable;
}
export const PermitReview = (props: PermitReviewProps) => {
@@ -83,6 +86,10 @@ export const PermitReview = (props: PermitReviewProps) => {
oldFields={props.oldFields?.permitData?.contactDetails}
/>
+
+
-
+
Description
-
+
+
Conditions
+
{reviewConditions.map((row: PermitCondition) => {
return (
@@ -42,7 +48,11 @@ export const ReviewConditionsTable = ({
key={row.condition}
data-testid="review-permit-condition"
>
-
+
-
+
;
+}) => {
+ return loas && loas.length > 0 ? (
+
+
+
+ Letter of Authorization (LOA)
+
+
+
+
+
+
+ Selected LOA(s)
+
+
+ ({
+ loa,
+ checked: true,
+ disabled: true,
+ }))}
+ />
+
+
+
+ ) : null;
+};
diff --git a/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx b/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx
index b3b5b3f76..63f36255d 100644
--- a/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx
+++ b/frontend/src/features/permits/pages/Application/tests/helpers/ApplicationReview/prepare.tsx
@@ -49,6 +49,7 @@ export const defaultApplicationData = {
startDate: getStartOfDate(toLocalDayjs(permitData.startDate)),
expiryDate: getEndOfDate(toLocalDayjs(permitData.expiryDate)),
},
+ permitStatus: PERMIT_STATUSES.IN_PROGRESS,
} as Application;
export const companyInfo = getDefaultCompanyInfo();
diff --git a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss
index 519f18cd9..b0d208663 100644
--- a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss
+++ b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.scss
@@ -20,7 +20,7 @@
& &__info {
border-bottom: 1px solid $bc-border-grey;
margin-bottom: 1.5rem;
- padding: 1.5rem 0;
+ padding: 0 0 1.5rem 0;
.info {
&__body {
diff --git a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx
index 229e2ee1d..2cc8aac35 100644
--- a/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx
+++ b/frontend/src/features/permits/pages/ShoppingCart/ShoppingCartPage.tsx
@@ -21,7 +21,6 @@ import { EditCartItemDialog } from "../../components/cart/EditCartItemDialog";
import { UpdateCartDialog } from "../../components/cart/UpdateCartDialog";
import { BCeID_USER_ROLE } from "../../../../common/authentication/types";
import { Loading } from "../../../../common/pages/Loading";
-import { CVPayInPersonInfo } from "../Application/components/pay/CVPayInPersonInfo";
import {
PAYMENT_METHOD_TYPE_CODE,
PaymentCardTypeCode,
@@ -391,6 +390,7 @@ export const ShoppingCartPage = () => {
{PPC_EMAIL}
+
{
-
- {!isStaffActingAsCompany && }
+ {!isFeeZero ? (
+
+ ) : null}
{paymentFailed ? : null}
diff --git a/frontend/src/features/permits/types/PermitData.ts b/frontend/src/features/permits/types/PermitData.ts
index 381d12e89..c33774c62 100644
--- a/frontend/src/features/permits/types/PermitData.ts
+++ b/frontend/src/features/permits/types/PermitData.ts
@@ -4,6 +4,7 @@ import { Nullable } from "../../../common/types/common";
import { PermitContactDetails } from "./PermitContactDetails";
import { PermitVehicleDetails } from "./PermitVehicleDetails";
import { PermitMailingAddress } from "./PermitMailingAddress";
+import { LOADetail } from "../../settings/types/SpecialAuthorization";
import { PermitCondition } from "./PermitCondition";
export interface PermitData {
@@ -18,4 +19,5 @@ export interface PermitData {
companyName?: Nullable;
doingBusinessAs?: Nullable;
clientNumber?: Nullable;
+ loas?: Nullable;
}
diff --git a/frontend/src/features/permits/types/PermitType.ts b/frontend/src/features/permits/types/PermitType.ts
index df4fc1046..1d2bb9341 100644
--- a/frontend/src/features/permits/types/PermitType.ts
+++ b/frontend/src/features/permits/types/PermitType.ts
@@ -116,3 +116,13 @@ export const permitTypeDisplayText = (permitType?: Nullable) => {
export const isPermitTypeValid = (permitType?: Nullable) => {
return permitType && (Object.values(PERMIT_TYPES) as string[]).includes(permitType.toUpperCase());
};
+
+/**
+ * Determine whether or not a permit type is considered a term permit.
+ * @param permitType Type of permit
+ * @returns Whether or not the permit of that type is considered a term permit
+ */
+export const isTermPermitType = (permitType: PermitType) => {
+ return permitType === PERMIT_TYPES.TROS
+ || permitType === PERMIT_TYPES.TROW;
+};
diff --git a/frontend/src/features/settings/apiManager/endpoints/endpoints.ts b/frontend/src/features/settings/apiManager/endpoints/endpoints.ts
index 5da008e7c..509fafb76 100644
--- a/frontend/src/features/settings/apiManager/endpoints/endpoints.ts
+++ b/frontend/src/features/settings/apiManager/endpoints/endpoints.ts
@@ -20,18 +20,18 @@ export const SPECIAL_AUTH_API_ROUTES = {
},
LOA: {
ALL: (companyId: number | string, expired: boolean) =>
- `${SPECIAL_AUTH_API_BASE}/${companyId}/loas${expired ? "?expired=true" : ""}`,
- DETAIL: (companyId: number | string, loaId: string) =>
+ `${SPECIAL_AUTH_API_BASE}/${companyId}/loas?expired=${expired}`,
+ DETAIL: (companyId: number | string, loaId: number) =>
`${SPECIAL_AUTH_API_BASE}/${companyId}/loas/${loaId}`,
CREATE: (companyId: number | string) =>
`${SPECIAL_AUTH_API_BASE}/${companyId}/loas`,
- UPDATE: (companyId: number | string, loaId: string) =>
+ UPDATE: (companyId: number | string, loaId: number) =>
`${SPECIAL_AUTH_API_BASE}/${companyId}/loas/${loaId}`,
- REMOVE: (companyId: number | string, loaId: string) =>
+ REMOVE: (companyId: number | string, loaId: number) =>
`${SPECIAL_AUTH_API_BASE}/${companyId}/loas/${loaId}`,
- DOWNLOAD: (companyId: number | string, loaId: string) =>
+ DOWNLOAD: (companyId: number | string, loaId: number) =>
`${SPECIAL_AUTH_API_BASE}/${companyId}/loas/${loaId}/documents?download=proxy`,
- REMOVE_DOCUMENT: (companyId: number | string, loaId: string) =>
+ REMOVE_DOCUMENT: (companyId: number | string, loaId: number) =>
`${SPECIAL_AUTH_API_BASE}/${companyId}/loas/${loaId}/documents`,
},
};
diff --git a/frontend/src/features/settings/apiManager/specialAuthorization.ts b/frontend/src/features/settings/apiManager/specialAuthorization.ts
index 689054947..7448be6ca 100644
--- a/frontend/src/features/settings/apiManager/specialAuthorization.ts
+++ b/frontend/src/features/settings/apiManager/specialAuthorization.ts
@@ -4,6 +4,7 @@ import { LOADetail, NoFeePermitType, SpecialAuthorizationData } from "../types/S
import { LOAFormData, serializeLOAFormData } from "../types/LOAFormData";
import { SPECIAL_AUTH_API_ROUTES } from "./endpoints/endpoints";
import { streamDownloadFile } from "../../../common/helpers/util";
+import { RequiredOrNull } from "../../../common/types/common";
import {
httpDELETERequest,
httpGETRequest,
@@ -12,7 +13,6 @@ import {
httpPUTRequest,
httpPUTRequestWithFile,
} from "../../../common/apiManager/httpRequestHandler";
-import { RequiredOrNull } from "../../../common/types/common";
/**
* Get the LOAs for a given company.
@@ -38,7 +38,7 @@ export const getLOAs = async (
*/
export const getLOADetail = async (
companyId: number | string,
- loaId: string,
+ loaId: number,
): Promise => {
const response = await httpGETRequest(
SPECIAL_AUTH_API_ROUTES.LOA.DETAIL(companyId, loaId),
@@ -72,7 +72,7 @@ export const createLOA = async (
export const updateLOA = async (
LOAData: {
companyId: number | string;
- loaId: string;
+ loaId: number;
data: LOAFormData;
},
): Promise> => {
@@ -91,7 +91,7 @@ export const updateLOA = async (
export const removeLOA = async (
LOAData: {
companyId: number | string;
- loaId: string;
+ loaId: number;
},
): Promise> => {
const { companyId, loaId } = LOAData;
@@ -107,7 +107,7 @@ export const removeLOA = async (
* @returns A Promise containing the dms reference string for the LOA download stream
*/
export const downloadLOA = async (
- loaId: string,
+ loaId: number,
companyId: string | number,
) => {
const url = SPECIAL_AUTH_API_ROUTES.LOA.DOWNLOAD(companyId, loaId);
@@ -123,7 +123,7 @@ export const downloadLOA = async (
export const removeLOADocument = async (
LOAData: {
companyId: number | string;
- loaId: string;
+ loaId: number;
},
): Promise> => {
const { companyId, loaId } = LOAData;
diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx
index 8d90836ea..06ec26564 100644
--- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx
+++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/expired/ExpiredLOAModal.tsx
@@ -17,8 +17,8 @@ export const ExpiredLOAModal = ({
showModal: boolean;
allowEditLOA: boolean;
handleCancel: () => void;
- handleEdit: (loaId: string) => void;
- handleDownload: (loaId: string) => void;
+ handleEdit: (loaId: number) => void;
+ handleDownload: (loaId: number) => void;
expiredLOAs: LOADetail[];
}) => {
return (
diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx
index 386b6215f..666f7a19c 100644
--- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx
+++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOADownloadCell.tsx
@@ -7,18 +7,17 @@ export const LOADownloadCell = ({
onDownload,
props: { row },
}: {
- onDownload: (loaId: string) => void;
+ onDownload: (loaId: number) => void;
props: {
row: MRT_Row;
};
}) => {
- const loaId = `${row.original.loaId}`;
const loaHasDocument = Boolean(row.original.documentId);
return loaHasDocument ? (
onDownload(loaId)}
+ onClick={() => onDownload(row.original.loaId)}
>
Download Letter
diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx
index 0383c1db8..69d3910e8 100644
--- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx
+++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAList.tsx
@@ -28,11 +28,11 @@ export const LOAList = ({
loas: LOADetail[];
isActive: boolean;
allowEditLOA: boolean;
- onEdit: (loaId: string) => void;
- onDelete?: (loaId: string) => void;
- onDownload: (loaId: string) => void;
+ onEdit: (loaId: number) => void;
+ onDelete?: (loaId: number) => void;
+ onDownload: (loaId: number) => void;
}) => {
- const handleEditLOA = (loaId: string) => {
+ const handleEditLOA = (loaId: number) => {
if (!allowEditLOA) return;
onEdit(loaId);
};
diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx
index a365964ad..bec21ad11 100644
--- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx
+++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOAListColumnDef.tsx
@@ -8,8 +8,8 @@ import { LOADownloadCell } from "./LOADownloadCell";
export const LOAListColumnDef = (
allowEditLOA: boolean,
- onEditLOA: (loaId: string) => void,
- onDownload: (loaId: string) => void,
+ onEditLOA: (loaId: number) => void,
+ onDownload: (loaId: number) => void,
): MRT_ColumnDef[] => [
{
Cell: (
diff --git a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx
index b8184d0e6..4301b5c81 100644
--- a/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx
+++ b/frontend/src/features/settings/components/SpecialAuthorizations/LOA/list/LOANumberCell.tsx
@@ -9,21 +9,19 @@ export const LOANumberCell = ({
props: { row },
}: {
allowEditLOA: boolean;
- onEditLOA: (loaId: string) => void;
+ onEditLOA: (loaId: number) => void;
props: {
row: MRT_Row;
};
}) => {
- const loaId = `${row.original.loaId}`;
- const loaNumber = `${row.original.loaNumber}`;
return allowEditLOA ? (
onEditLOA(loaId)}
+ onClick={() => onEditLOA(row.original.loaId)}
>
- {loaNumber}
+ {row.original.loaNumber}
) : (
- <>{loaNumber}>
+ <>{row.original.loaNumber}>
);
};
diff --git a/frontend/src/features/settings/hooks/LOA.ts b/frontend/src/features/settings/hooks/LOA.ts
index 75749bde1..bb3d8cb6e 100644
--- a/frontend/src/features/settings/hooks/LOA.ts
+++ b/frontend/src/features/settings/hooks/LOA.ts
@@ -12,7 +12,7 @@ import {
const QUERY_KEYS = {
LOAS: (expired: boolean) => ["loas", expired],
- LOA: (loaId?: Nullable) => ["loa", loaId],
+ LOA: (loaId?: Nullable) => ["loa", loaId],
};
/**
@@ -37,7 +37,7 @@ export const useFetchLOAs = (companyId: number | string, expired: boolean) => {
* @param loaId id of the LOA to fetch
* @returns Query result of the LOA details
*/
-export const useFetchLOADetail = (companyId: number, loaId?: Nullable) => {
+export const useFetchLOADetail = (companyId: number, loaId?: Nullable) => {
return useQuery({
queryKey: QUERY_KEYS.LOA(loaId),
queryFn: () => {
diff --git a/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx b/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx
index 13d721c62..b2205fd39 100644
--- a/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx
+++ b/frontend/src/features/settings/pages/SpecialAuthorizations/LOA/LOASteps.tsx
@@ -26,7 +26,7 @@ export const LOASteps = ({
companyId,
onExit,
}: {
- loaId?: Nullable;
+ loaId?: Nullable;
companyId: number;
onExit: () => void;
}) => {
diff --git a/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx b/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx
index b3b53a0b2..410938068 100644
--- a/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx
+++ b/frontend/src/features/settings/pages/SpecialAuthorizations/SpecialAuthorizations.tsx
@@ -46,9 +46,9 @@ export const SpecialAuthorizations = ({
const isLcvAllowed = getDefaultRequiredVal(false, specialAuthorizations?.isLcvAllowed);
const [showExpiredLOAs, setShowExpiredLOAs] = useState(false);
- const [loaToDelete, setLoaToDelete] = useState>(null);
+ const [loaToDelete, setLoaToDelete] = useState>(null);
const [showLOASteps, setShowLOASteps] = useState(false);
- const [loaToEdit, setLoaToEdit] = useState>(null);
+ const [loaToEdit, setLoaToEdit] = useState>(null);
const {
userClaims,
@@ -143,7 +143,7 @@ export const SpecialAuthorizations = ({
setLoaToEdit(null);
};
- const handleEditLOA = (loaId: string) => {
+ const handleEditLOA = (loaId: number) => {
if (!canWriteLOA) return;
setShowLOASteps(true);
setLoaToEdit(loaId);
@@ -156,7 +156,7 @@ export const SpecialAuthorizations = ({
expiredLOAsQuery.refetch();
};
- const handleOpenDeleteModal = (loaId: string) => {
+ const handleOpenDeleteModal = (loaId: number) => {
if (!canWriteLOA) return;
setLoaToDelete(loaId);
};
@@ -165,7 +165,7 @@ export const SpecialAuthorizations = ({
setLoaToDelete(null);
};
- const handleDeleteLOA = async (loaId: string) => {
+ const handleDeleteLOA = async (loaId: number) => {
try {
if (canWriteLOA) {
await removeLOAMutation.mutateAsync({
@@ -182,7 +182,7 @@ export const SpecialAuthorizations = ({
}
};
- const handleDownloadLOA = async (loaId: string) => {
+ const handleDownloadLOA = async (loaId: number) => {
if (loaId && canReadLOA) {
try {
const { blobObj: blobObjWithoutType } = await downloadLOA(
diff --git a/frontend/src/features/settings/types/SpecialAuthorization.ts b/frontend/src/features/settings/types/SpecialAuthorization.ts
index 7766de8e7..4392c906c 100644
--- a/frontend/src/features/settings/types/SpecialAuthorization.ts
+++ b/frontend/src/features/settings/types/SpecialAuthorization.ts
@@ -1,3 +1,4 @@
+import { areArraysEqual } from "../../../common/helpers/util";
import { Nullable, RequiredOrNull } from "../../../common/types/common";
import { PermitType } from "../../permits/types/PermitType";
@@ -29,8 +30,8 @@ export const noFeePermitTypeDescription = (noFeePermitType: NoFeePermitType) =>
};
export interface LOADetail {
- loaId: string;
- loaNumber: string;
+ loaId: number;
+ loaNumber: number;
companyId: number;
startDate: string;
expiryDate?: Nullable;
@@ -40,6 +41,8 @@ export interface LOADetail {
comment?: Nullable;
powerUnits: string[];
trailers: string[];
+ originalLoaId: number;
+ previousLoaId?: Nullable;
}
export interface CreateLOARequestData {
@@ -62,6 +65,33 @@ export interface UpdateLOARequestData {
trailers: string[];
}
+/**
+ * Determine whether or not two LOAs have the same details.
+ * @param loa1 First LOA
+ * @param loa2 Second LOA
+ * @returns Whether or not the two LOAs have the same details
+ */
+export const areLOADetailsEqual = (
+ loa1?: Nullable,
+ loa2?: Nullable,
+) => {
+ if (!loa1 && !loa2) return true;
+ if (!loa1 || !loa2) return false;
+
+ return loa1.loaId === loa2.loaId
+ && loa1.loaNumber === loa2.loaNumber
+ && loa1.companyId === loa2.companyId
+ && loa1.startDate === loa2.startDate
+ && loa1.expiryDate === loa2.expiryDate
+ && loa1.documentId === loa2.documentId
+ && loa1.fileName === loa2.fileName
+ && areArraysEqual(loa1.loaPermitType, loa2.loaPermitType)
+ && loa1.comment === loa2.comment
+ && areArraysEqual(loa1.powerUnits, loa2.powerUnits)
+ && areArraysEqual(loa1.trailers, loa2.trailers)
+ && loa1.originalLoaId === loa2.originalLoaId
+ && loa1.previousLoaId === loa2.previousLoaId;
+};
export interface SpecialAuthorizationData {
companyId: number;
specialAuthId: number;
diff --git a/frontend/src/features/wizard/subcomponents/ClientAndPermitReferenceInfoBox.tsx b/frontend/src/features/wizard/subcomponents/ClientAndPermitReferenceInfoBox.tsx
index 4044f9ae3..b65330939 100644
--- a/frontend/src/features/wizard/subcomponents/ClientAndPermitReferenceInfoBox.tsx
+++ b/frontend/src/features/wizard/subcomponents/ClientAndPermitReferenceInfoBox.tsx
@@ -2,6 +2,7 @@ import { faCircleInfo } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Card, CardContent, CardMedia, Stack } from "@mui/material";
import { BC_COLOURS } from "../../../themes/bcGovStyles";
+import { PPC_EMAIL, TOLL_FREE_NUMBER } from "../../../common/constants/constants";
/**
* React component to display an info box about how to locate
@@ -36,8 +37,8 @@ export const ClientAndPermitReferenceInfoBox = () => {
If you need further assistance, please contact the
Provincial Permit Centre at{" "}
- Toll-free: 1-800-559-9688 or
{" "}
- Email: ppcpermit@gov.bc.ca
+ Toll-free: {TOLL_FREE_NUMBER} or
{" "}
+ Email: {PPC_EMAIL}
,
+ @InjectRepository(PermitLoa)
+ private permitLoaRepository: Repository,
+ @InjectRepository(LoaDetail)
+ private loaDetail: Repository,
private dataSource: DataSource,
private readonly dopsService: DopsService,
private readonly paymentService: PaymentService,
@@ -1198,4 +1212,83 @@ export class ApplicationService {
return result;
}
+ @LogAsyncMethodExecution()
+ async createPermitLoa(
+ currentUser: IUserJWT,
+ permitId: string,
+ createPermitLoaDto: CreatePermitLoaDto,
+ ): Promise {
+ const { loaIds: inputLoaIds } = createPermitLoaDto;
+ const existingPermitLoa = await this.findAllPermitLoa(permitId);
+ const permit = await this.findOne(permitId);
+ const existingLoaIds = existingPermitLoa.map((x) => x.loaId);
+ const loaIdsToDelete = existingLoaIds.filter(
+ (value) => !inputLoaIds.includes(value),
+ );
+ const loaIdsToInsert = inputLoaIds.filter(
+ (value) => !existingLoaIds.includes(value),
+ );
+
+ if (loaIdsToInsert.length) {
+ const loaDetails = await this.loaDetail.find({
+ where: {
+ loaId: In(loaIdsToInsert),
+ company: { companyId: permit.company.companyId },
+ },
+ });
+ if(loaDetails.length != loaIdsToInsert.length)
+ throw new BadRequestException('One or more loa(s) does not exist')
+ // Transform the permit LOA IDs from an array of numbers into individual records.
+ const singlePermitLoa = loaIdsToInsert.map((loaId) => ({
+ permitId,
+ loaIds: [loaId],
+ }));
+
+ const permitLoas = await this.classMapper.mapArrayAsync(
+ singlePermitLoa,
+ CreatePermitLoaDto,
+ PermitLoa,
+ {
+ extraArgs: () => ({
+ permitId,
+ userName: currentUser.userName,
+ userGUID: currentUser.userGUID,
+ timestamp: new Date(),
+ directory: currentUser.orbcUserDirectory,
+ }),
+ },
+ );
+
+ // Save new PermitLoas in bulk
+ await this.permitLoaRepository.save(permitLoas);
+ }
+
+ // Delete old PermitLoas in a single query
+ if (loaIdsToDelete?.length)
+ await this.permitLoaRepository.delete({
+ permitId: permitId,
+ loa: { loaId: In(loaIdsToDelete) },
+ });
+ return await this.findAllPermitLoa(permitId);
+ }
+ @LogAsyncMethodExecution()
+ async findAllPermitLoa(permitId: string): Promise {
+ const savedPermitLoa = await this.permitLoaRepository
+ .createQueryBuilder('permitLoa')
+ .innerJoinAndSelect('permitLoa.loa', 'loa')
+ .innerJoinAndSelect('loa.company', 'company')
+ .innerJoinAndSelect('loa.loaVehicles', 'loaVehicles')
+ .innerJoinAndSelect('loa.loaPermitTypes', 'loaPermitTypes')
+ .where('permitLoa.permitId = :permitId', {
+ permitId: permitId,
+ })
+ .getMany();
+ const readPermitLoaDto: ReadPermitLoaDto[] =
+ await this.classMapper.mapArrayAsync(
+ savedPermitLoa,
+ PermitLoa,
+ ReadPermitLoaDto,
+ );
+ return readPermitLoaDto;
+ }
}
diff --git a/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts b/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts
index 0e674868b..56610a8f2 100644
--- a/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts
+++ b/vehicles/src/modules/permit-application-payment/application/company-application.controller.ts
@@ -52,6 +52,9 @@ import {
ApplicationQueueStatus,
convertApplicationQueueStatus,
} from '../../../common/enum/case-status-type.enum';
+import { ApplicationIdIdPathParamDto } from './dto/request/pathParam/applicationId.path-params.dto';
+import { CreatePermitLoaDto } from './dto/request/create-permit-loa.dto';
+import { ReadPermitLoaDto } from './dto/response/read-permit-loa.dto';
@ApiBearerAuth()
@ApiTags('Company Application')
@@ -367,4 +370,32 @@ export class CompanyApplicationController {
}
return deleteResult;
}
+ @ApiOperation({
+ summary: 'Designate LoA to permit.',
+ description:
+ 'Designate LoA to permit. Returns the created permit LoA object from the database.',
+ })
+ @ApiCreatedResponse({
+ description: 'Permit Loa Details',
+ type: ReadPermitLoaDto,
+ isArray: true,
+ })
+ @Permissions({
+ allowedBCeIDRoles: CLIENT_USER_ROLE_LIST,
+ allowedIdirRoles: IDIR_USER_ROLE_LIST,
+ })
+ @Post(':applicationId/loas')
+ async createPermitLoa(
+ @Req() request: Request,
+ @Param() { applicationId }: ApplicationIdIdPathParamDto,
+ @Body() createPermitLoaDto: CreatePermitLoaDto,
+ ): Promise {
+ const currentUser = request.user as IUserJWT;
+ const result = await this.applicationService.createPermitLoa(
+ currentUser,
+ applicationId,
+ createPermitLoaDto,
+ );
+ return result;
+ }
}
diff --git a/vehicles/src/modules/permit-application-payment/application/dto/request/create-permit-loa.dto.ts b/vehicles/src/modules/permit-application-payment/application/dto/request/create-permit-loa.dto.ts
new file mode 100644
index 000000000..fbecc9fdf
--- /dev/null
+++ b/vehicles/src/modules/permit-application-payment/application/dto/request/create-permit-loa.dto.ts
@@ -0,0 +1,14 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { ArrayMinSize, IsInt, IsPositive } from 'class-validator';
+
+export class CreatePermitLoaDto {
+ @ApiProperty({
+ description: 'Loa Ids to be assigned to the permit.',
+ isArray: true,
+ example: [1],
+ })
+ @IsInt({ each: true })
+ @IsPositive({ each: true })
+ @ArrayMinSize(1)
+ loaIds: number[];
+}
diff --git a/vehicles/src/modules/permit-application-payment/application/dto/response/read-permit-loa.dto.ts b/vehicles/src/modules/permit-application-payment/application/dto/response/read-permit-loa.dto.ts
new file mode 100644
index 000000000..c68ade7f8
--- /dev/null
+++ b/vehicles/src/modules/permit-application-payment/application/dto/response/read-permit-loa.dto.ts
@@ -0,0 +1,10 @@
+import { ApiProperty } from '@nestjs/swagger';
+import { ReadLoaDto } from 'src/modules/special-auth/dto/response/read-loa.dto';
+
+export class ReadPermitLoaDto extends ReadLoaDto {
+ @ApiProperty({
+ description: 'Permit Loa id',
+ example: 1,
+ })
+ permitLoaId: number;
+}
diff --git a/vehicles/src/modules/permit-application-payment/application/entities/permit-loa.entity.ts b/vehicles/src/modules/permit-application-payment/application/entities/permit-loa.entity.ts
new file mode 100644
index 000000000..b9c76c214
--- /dev/null
+++ b/vehicles/src/modules/permit-application-payment/application/entities/permit-loa.entity.ts
@@ -0,0 +1,29 @@
+import {
+ Column,
+ Entity,
+ JoinColumn,
+ OneToOne,
+ PrimaryGeneratedColumn,
+} from 'typeorm';
+import { AutoMap } from '@automapper/classes';
+import { Base } from 'src/modules/common/entities/base.entity';
+import { LoaDetail } from 'src/modules/special-auth/entities/loa-detail.entity';
+
+@Entity({ name: 'permit.ORBC_PERMIT_LOA' })
+export class PermitLoa extends Base {
+ @AutoMap()
+ @PrimaryGeneratedColumn({ type: 'int', name: 'PERMIT_LOA_ID' })
+ permitLoaId: number;
+
+ @AutoMap(() => LoaDetail)
+ @OneToOne(() => LoaDetail, (LoaDetail) => LoaDetail.loaId)
+ @JoinColumn({ name: 'LOA_ID' })
+ loa: LoaDetail;
+
+ @AutoMap()
+ @Column({
+ type: 'bigint',
+ name: 'PERMIT_ID',
+ })
+ permitId: string;
+}
diff --git a/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts b/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts
index 1f63de139..9569c7c09 100644
--- a/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts
+++ b/vehicles/src/modules/permit-application-payment/application/profile/application.profile.ts
@@ -27,6 +27,10 @@ import {
convertCaseStatus,
} from '../../../../common/enum/case-status-type.enum';
import { ReadCaseActivityDto } from '../../../case-management/dto/response/read-case-activity.dto';
+import { CreatePermitLoaDto } from '../dto/request/create-permit-loa.dto';
+import { PermitLoa } from '../entities/permit-loa.entity';
+import { ReadPermitLoaDto } from '../dto/response/read-permit-loa.dto';
+import * as dayjs from 'dayjs';
@Injectable()
export class ApplicationProfile extends AutomapperProfile {
@@ -444,6 +448,135 @@ export class ApplicationProfile extends AutomapperProfile {
}),
),
);
+ createMap(
+ mapper,
+ CreatePermitLoaDto,
+ PermitLoa,
+ forMember(
+ (d) => d.permitId,
+ mapWithArguments((_, { permitId }) => {
+ return permitId;
+ }),
+ ),
+ forMember(
+ (d) => d.loa.loaId,
+ mapFrom((s) => {
+ return s.loaIds[0];
+ }),
+ ),
+ forMember(
+ (d) => d.createdUserGuid,
+ mapWithArguments((_, { userGUID }) => {
+ return userGUID;
+ }),
+ ),
+ forMember(
+ (d) => d.createdUser,
+ mapWithArguments((_, { userName }) => {
+ return userName;
+ }),
+ ),
+ forMember(
+ (d) => d.createdUserDirectory,
+ mapWithArguments((_, { directory }) => {
+ return directory;
+ }),
+ ),
+
+ forMember(
+ (d) => d.createdDateTime,
+ mapWithArguments((_, { timestamp }) => {
+ return timestamp;
+ }),
+ ),
+
+ forMember(
+ (d) => d.updatedUserGuid,
+ mapWithArguments((_, { userGUID }) => {
+ return userGUID;
+ }),
+ ),
+ forMember(
+ (d) => d.updatedUser,
+ mapWithArguments((_, { userName }) => {
+ return userName;
+ }),
+ ),
+ forMember(
+ (d) => d.updatedUserDirectory,
+ mapWithArguments((_, { directory }) => {
+ return directory;
+ }),
+ ),
+
+ forMember(
+ (d) => d.updatedDateTime,
+ mapWithArguments((_, { timestamp }) => {
+ return timestamp;
+ }),
+ ),
+ );
+
+ createMap(
+ mapper,
+ PermitLoa,
+ ReadPermitLoaDto,
+ forMember(
+ (d) => d.permitLoaId,
+ mapFrom((s) => {
+ return s.permitLoaId;
+ }),
+ ),
+ forMember(
+ (d) => d.loaId,
+ mapFrom((s) => {
+ return s.loa.loaId;
+ }),
+ ),
+ forMember(
+ (d) => d.companyId,
+ mapFrom((s) => {
+ return s.loa.company.companyId;
+ }),
+ ),
+ forMember(
+ (d) => d.startDate,
+ mapFrom((s) => {
+ return dayjs(s.loa.startDate).format('YYYY-MM-DD');
+ }),
+ ),
+ forMember(
+ (d) => d.expiryDate,
+ mapFrom((s) => {
+ if (s.loa.expiryDate)
+ return dayjs(s.loa.expiryDate).format('YYYY-MM-DD');
+ }),
+ ),
+ forMember(
+ (d) => d.loaPermitType,
+ mapFrom((s) => {
+ return s.loa.loaPermitTypes.map((lpt) => lpt.permitType);
+ }),
+ ),
+ forMember(
+ (d) => d.powerUnits,
+ mapFrom((s) => {
+ if (s.loa.loaVehicles)
+ return s.loa.loaVehicles
+ .filter((lv) => lv.powerUnit)
+ .map((lv) => lv.powerUnit);
+ }),
+ ),
+ forMember(
+ (d) => d.trailers,
+ mapFrom((s) => {
+ if (s.loa.loaVehicles)
+ return s.loa.loaVehicles
+ .filter((lv) => lv.trailer)
+ .map((lv) => lv.trailer);
+ }),
+ ),
+ );
};
}
}
diff --git a/vehicles/src/modules/special-auth/loa.service.ts b/vehicles/src/modules/special-auth/loa.service.ts
index 8363942d5..68c81028f 100644
--- a/vehicles/src/modules/special-auth/loa.service.ts
+++ b/vehicles/src/modules/special-auth/loa.service.ts
@@ -94,6 +94,7 @@ export class LoaService {
companyId,
savedLoaDetail.loaId,
);
+
const readLoaDto = await this.classMapper.mapAsync(
refreshedLoaDetailsEntity,
LoaDetail,
@@ -173,7 +174,6 @@ export class LoaService {
where: {
loaId: loaId,
company: { companyId: companyId },
- isActive: true,
},
relations: ['company', 'loaVehicles', 'loaPermitTypes'],
});
diff --git a/vehicles/src/modules/special-auth/profile/loa.profile.ts b/vehicles/src/modules/special-auth/profile/loa.profile.ts
index a48e44ac0..0a6594429 100644
--- a/vehicles/src/modules/special-auth/profile/loa.profile.ts
+++ b/vehicles/src/modules/special-auth/profile/loa.profile.ts
@@ -9,11 +9,11 @@ import { AutomapperProfile, InjectMapper } from '@automapper/nestjs';
import { Injectable } from '@nestjs/common';
import { CreateLoaDto } from '../dto/request/create-loa.dto';
import { LoaDetail } from '../entities/loa-detail.entity';
-import { ReadLoaDto } from '../dto/response/read-loa.dto';
import { LoaPermitType } from '../entities/loa-permit-type-details.entity';
import { LoaVehicle } from '../entities/loa-vehicles.entity';
import * as dayjs from 'dayjs';
import { UpdateLoaDto } from '../dto/request/update-loa.dto';
+import { ReadLoaDto } from '../dto/response/read-loa.dto';
@Injectable()
export class LoaProfile extends AutomapperProfile {
@@ -385,7 +385,6 @@ export class LoaProfile extends AutomapperProfile {
),
),
);
-
createMap(
mapper,
LoaDetail,