Skip to content

Commit

Permalink
ORV2-1504: Applying for no-fee permits (#1595)
Browse files Browse the repository at this point in the history
Co-authored-by: gchauhan-aot <[email protected]>
  • Loading branch information
zgong-gov and gchauhan-aot authored Sep 13, 2024
1 parent d60f720 commit 5517651
Show file tree
Hide file tree
Showing 19 changed files with 180 additions and 90 deletions.
6 changes: 6 additions & 0 deletions frontend/src/features/permits/helpers/feeSummary.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export const calculateAmountToRefund = (
currPermitType: PermitType,
) => {
const netPaid = calculateNetAmount(permitHistory);
if (isZeroAmount(netPaid)) return 0; // If total paid is $0 (eg. no-fee permits), then refund nothing

const feeForCurrDuration = calculateFeeByDuration(currPermitType, currDuration);
return netPaid - feeForCurrDuration;
};
Expand All @@ -128,12 +130,16 @@ export const isZeroAmount = (amount: number) => {
*/
export const calculateAmountForVoid = (
permit: Permit,
transactionHistory: PermitHistory[],
) => {
const permitState = getPermitState(permit);
if (permitState === PERMIT_STATES.EXPIRED) {
return 0;
}

const netAmountPaid = calculateNetAmount(transactionHistory);
if (isZeroAmount(netAmountPaid)) return 0; // If existing net paid is $0 (eg. no-fee permits), then refund nothing

const daysLeft = daysLeftBeforeExpiry(permit);
const intervalDays = getDurationIntervalDays(permit.permitType);
return calculateFeeByDuration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { DEFAULT_PERMIT_TYPE } from "../../../types/PermitType";
export const AmendPermitFinish = () => {
const navigate = useNavigate();
const { companyId } = useParams();

const {
permit,
amendmentApplication,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import { usePowerUnitSubTypesQuery } from "../../../manageVehicles/hooks/powerUn
import { useTrailerSubTypesQuery } from "../../../manageVehicles/hooks/trailers";
import { useFetchSpecialAuthorizations } from "../../../settings/hooks/specialAuthorizations";
import { applyLCVToApplicationData } from "../../helpers/getDefaultApplicationFormData";
import { calculateFeeByDuration } from "../../helpers/feeSummary";
import { DEFAULT_PERMIT_TYPE } from "../../types/PermitType";
import {
APPLICATIONS_ROUTES,
APPLICATION_STEPS,
Expand All @@ -38,11 +40,22 @@ export const ApplicationReview = () => {

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 applicationData = applyLCVToApplicationData(
applicationContextData,
isLcvDesignated,
);

const fee = isNoFeePermitType
? "0"
: `${calculateFeeByDuration(
getDefaultRequiredVal(DEFAULT_PERMIT_TYPE, applicationData?.permitType),
getDefaultRequiredVal(0, applicationData?.permitData?.permitDuration),
)}`;

const { setSnackBar } = useContext(SnackBarContext);
const { refetchCartCount } = useContext(CartContext);
Expand Down Expand Up @@ -166,6 +179,7 @@ export const ApplicationReview = () => {
applicationData?.permitData?.vehicleDetails?.saveVehicle
}
doingBusinessAs={doingBusinessAs}
calculatedFee={fee}
/>
</FormProvider>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import { LCV_CONDITION } from "../../../../constants/constants";
import { sortConditions } from "../../../../helpers/conditions";
import { getStartOfDate } from "../../../../../../common/helpers/formatDate";
import { getExpiryDate } from "../../../../helpers/permitState";
import { calculateFeeByDuration } from "../../../../helpers/feeSummary";
import {
PowerUnit,
Trailer,
Expand Down Expand Up @@ -91,10 +90,6 @@ export const PermitForm = (props: PermitFormProps) => {
setValue("permitData.expiryDate", dayjs(expiry));
};

const handleSetFee = (fee: string) => {
setValue("permitData.feeSummary", fee);
};

const isLcvDesignated = props.isLcvDesignated;
const ineligiblePowerUnitSubtypes = getIneligiblePowerUnitSubtypes(permitType)
.filter(subtype => !isLcvDesignated || !isVehicleSubtypeLCV(subtype.typeCode));
Expand All @@ -105,10 +100,7 @@ export const PermitForm = (props: PermitFormProps) => {
handleSetExpiryDate(expiryDate);
}, [expiryDate]);

// Update fee summary whenever duration or permit type changes
useEffect(() => {
handleSetFee(`${calculateFeeByDuration(permitType, permitDuration)}`);
}, [permitDuration, permitType]);
const isAmendAction = props.isAmendAction;

const vehicleSubtype = vehicleFormData.vehicleSubType;
useEffect(() => {
Expand All @@ -117,7 +109,7 @@ export const PermitForm = (props: PermitFormProps) => {
&& 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
// then remove that LCV condition from the form
handleSetConditions(permitConditions.filter(
({ condition }: PermitCondition) => condition !== LCV_CONDITION.condition,
));
Expand All @@ -137,13 +129,13 @@ export const PermitForm = (props: PermitFormProps) => {
<ApplicationDetails
permitType={permitType}
infoNumber={
props.isAmendAction ? props.permitNumber : applicationNumber
isAmendAction ? props.permitNumber : applicationNumber
}
infoNumberType={props.isAmendAction ? "permit" : "application"}
infoNumberType={isAmendAction ? "permit" : "application"}
createdDateTime={props.createdDateTime}
updatedDateTime={props.updatedDateTime}
companyInfo={props.companyInfo}
isAmendAction={props.isAmendAction}
isAmendAction={isAmendAction}
doingBusinessAs={props.doingBusinessAs}
/>

Expand All @@ -154,7 +146,7 @@ export const PermitForm = (props: PermitFormProps) => {
expiryDate={expiryDate}
conditionsInPermit={permitConditions}
durationOptions={props.durationOptions}
disableStartDate={props.isAmendAction}
disableStartDate={isAmendAction}
permitType={permitType}
pastStartDateStatus={props.pastStartDateStatus}
includeLcvCondition={isLcvDesignated && isVehicleSubtypeLCV(vehicleFormData.vehicleSubType)}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
.choose-payment-method {
& &__title {
padding-top: 0;
padding-bottom: 1.5rem;
font-size: 1.5rem;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@ import { ReviewFeeSummary } from "./ReviewFeeSummary";
import { ReviewActions } from "./ReviewActions";
import { CompanyProfile } from "../../../../../manageProfile/types/manageProfile";
import { VehicleSubType } from "../../../../../manageVehicles/types/Vehicle";
import { DEFAULT_PERMIT_TYPE, PermitType } from "../../../../types/PermitType";
import { calculateFeeByDuration } from "../../../../helpers/feeSummary";
import { getDefaultRequiredVal } from "../../../../../../common/helpers/util";
import { PermitType } from "../../../../types/PermitType";
import { Nullable } from "../../../../../../common/types/common";
import { PermitContactDetails } from "../../../../types/PermitContactDetails";
import { PermitVehicleDetails } from "../../../../types/PermitVehicleDetails";
Expand Down Expand Up @@ -48,18 +46,11 @@ interface PermitReviewProps {
onAddToCart?: () => Promise<void>;
showChangedFields?: boolean;
oldFields?: Nullable<Partial<Application>>;
calculatedFee?: Nullable<string>;
calculatedFee: string;
doingBusinessAs?: Nullable<string>;
}

export const PermitReview = (props: PermitReviewProps) => {
const feeSummary = props.calculatedFee
? props.calculatedFee
: `${calculateFeeByDuration(
getDefaultRequiredVal(DEFAULT_PERMIT_TYPE, props.permitType),
getDefaultRequiredVal(0, props.permitDuration),
)}`;

return (
<Box className="permit-review layout-box">
<Box className="permit-review__container">
Expand Down Expand Up @@ -108,7 +99,7 @@ export const PermitReview = (props: PermitReviewProps) => {
isChecked={props.allChecked}
setIsChecked={props.setAllChecked}
permitType={props.permitType}
fee={feeSummary}
fee={props.calculatedFee}
/>

{props.children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,24 +408,14 @@ describe("Review and Confirm Application Details", () => {

it("should display proper fee summary", async () => {
// Arrange and Act
const applicationData = {
...defaultApplicationData,
permitData: {
...defaultApplicationData.permitData,
feeSummary: `${calculateFeeByDuration(
defaultApplicationData.permitType,
defaultApplicationData.permitData.permitDuration,
)}`,
},
};
renderTestComponent(applicationData);
renderTestComponent(defaultApplicationData);

// Assert
const {
permitType,
permitData: { feeSummary },
} = applicationData;
const permitTypeStr = permitTypeDisplayText(permitType);
const feeSummary = `${calculateFeeByDuration(
defaultApplicationData.permitType,
defaultApplicationData.permitData.permitDuration,
)}`;
const permitTypeStr = permitTypeDisplayText(defaultApplicationData.permitType);
expect(await feeSummaryPermitType()).toHaveTextContent(permitTypeStr);
expect(await feeSummaryPrice()).toHaveTextContent(`$${feeSummary}.00`);
expect(await feeSummaryTotal()).toHaveTextContent(`$${feeSummary}.00`);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
background-color: $bc-white;
display: flex;
flex-direction: column;
padding-top: 1.5rem;

.choose-payment-method {
margin-bottom: 2.5rem;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -136,28 +136,32 @@ export const ShoppingCartPage = () => {
}, []);

useEffect(() => {
// transaction is undefined when payment endpoint has not been requested
// ie. "Pay Now" button has not been pressed
if (typeof transaction !== "undefined") {
if (!isStaffActingAsCompany) {
// CV Client
if (!transaction?.url) {
// Failed to generate transaction url
navigate(SHOPPING_CART_ROUTES.DETAILS(true));
} else {
window.open(transaction.url, "_self");
}
} else if (!transaction) {
// Staff payment failed
if (!transaction) {
// Payment failed - ie. transaction object is null
navigate(SHOPPING_CART_ROUTES.DETAILS(true));
} else {
// Staff payment transaction created successfully, proceed to issue permit
} else if (isFeeZero || isStaffActingAsCompany) {
// If purchase was for no-fee permits, or if staff payment transaction was created successfully,
// simply proceed to issue permits
issuePermitMutation.mutate([...selectedIds]);

// also update the cart and cart count
cartQuery.refetch();
refetchCartCount();
} else {
// CV Client payment, anticipate PayBC transaction url
if (!transaction?.url) {
// Failed to generate transaction url
navigate(SHOPPING_CART_ROUTES.DETAILS(true));
} else {
// Redirect to PayBC transaction url to continue payment
window.open(transaction.url, "_self");
}
}
}
}, [transaction, isStaffActingAsCompany]);
}, [transaction, isStaffActingAsCompany, isFeeZero]);

useEffect(() => {
const issueFailed = hasPermitsActionFailed(issueResults);
Expand Down Expand Up @@ -248,11 +252,30 @@ export const ShoppingCartPage = () => {
});
};

// Paying for no-fee permits
const handlePayForNoFee = () => {
startTransactionMutation.mutate({
transactionTypeId: TRANSACTION_TYPES.P,
paymentMethodTypeCode: PAYMENT_METHOD_TYPE_CODE.NP,
applicationDetails: [
...selectedApplications.map((application) => ({
applicationId: application.applicationId,
transactionAmount: 0,
})),
],
});
};

const handlePay = (paymentMethodData: PaymentMethodData) => {
if (startTransactionMutation.isPending) return;

const { paymentMethod, additionalPaymentData } = paymentMethodData;

if (isFeeZero) {
handlePayForNoFee();
return;
}

if (paymentMethod === PAYMENT_METHOD_TYPE_CODE.ICEPAY) {
const { cardType, icepayTransactionId } =
additionalPaymentData as IcepayPaymentData;
Expand Down Expand Up @@ -365,9 +388,11 @@ export const ShoppingCartPage = () => {

<Box className="shopping-cart-page__right-container">
<FormProvider {...formMethods}>
<ChoosePaymentMethod
availablePaymentMethods={availablePaymentMethods}
/>
{!isFeeZero ? (
<ChoosePaymentMethod
availablePaymentMethods={availablePaymentMethods}
/>
) : null}

{paymentFailed ? <PaymentFailedBanner /> : null}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Checkbox } from "@mui/material";

import "./ShoppingCartItem.scss";
import { CartItem } from "../../../types/CartItem";
import { SelectableCartItem } from "../../../types/CartItem";
import { DATE_FORMATS, toLocal } from "../../../../../common/helpers/formatDate";
import { CustomActionLink } from "../../../../../common/components/links/CustomActionLink";
import { feeSummaryDisplayText } from "../../../helpers/feeSummary";
Expand All @@ -14,7 +14,7 @@ export const ShoppingCartItem = ({
onDeselect,
onEditCartItem,
}: {
cartItemData: CartItem;
cartItemData: SelectableCartItem;
isSelected: boolean;
isDisabled?: boolean;
onSelect: (id: string) => void;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export const useCheckOutdatedCart = (
// Reset old cart items whenever radio button filter is changed
setOldCartItems([]);
}, [
//showAllApplications
cartFilterChanged
]);

Expand All @@ -40,7 +39,7 @@ export const useCheckOutdatedCart = (

const outdatedApplicationNumbers = getOutdatedCartItems(
oldCartItems,
getDefaultRequiredVal([], fetchedCartItems),//cartItems),
getDefaultRequiredVal([], fetchedCartItems),
).map(cartItem => cartItem.applicationNumber);

return {
Expand Down
Loading

0 comments on commit 5517651

Please sign in to comment.