diff --git a/src/hooks/useSearchBulkActions.ts b/src/hooks/useSearchBulkActions.ts index 9f223d2ac07d5..dbec630cd31aa 100644 --- a/src/hooks/useSearchBulkActions.ts +++ b/src/hooks/useSearchBulkActions.ts @@ -1,5 +1,6 @@ import {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager} from 'react-native'; +import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import type {DropdownOption} from '@components/ButtonWithDropdownMenu/types'; import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider'; @@ -44,6 +45,7 @@ import { isIOUReport as isIOUReportUtil, } from '@libs/ReportUtils'; import {navigateToSearchRHP, shouldShowDeleteOption} from '@libs/SearchUIUtils'; +import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; import {hasTransactionBeenRejected} from '@libs/TransactionUtils'; import variables from '@styles/variables'; import {canIOUBePaid, dismissRejectUseExplanation} from '@userActions/IOU'; @@ -51,7 +53,7 @@ import {openOldDotLink} from '@userActions/Link'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {Report, SearchResults, Transaction, TransactionViolations} from '@src/types/onyx'; +import type {BillingGraceEndPeriod, Report, SearchResults, Transaction, TransactionViolations} from '@src/types/onyx'; import useAllTransactions from './useAllTransactions'; import useBulkPayOptions from './useBulkPayOptions'; import useConfirmModal from './useConfirmModal'; @@ -69,6 +71,10 @@ type UseSearchBulkActionsParams = { queryJSON: SearchQueryJSON | undefined; }; +function getRestrictedPolicyID(items: Array<{policyID?: string}>, billingGracePeriods: OnyxCollection): string | undefined { + return items.map((item) => item.policyID).find((policyID): policyID is string => !!policyID && shouldRestrictUserBillableActions(policyID, billingGracePeriods)); +} + function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { const {translate, localeCompare, formatPhoneNumber} = useLocalize(); const styles = useThemeStyles(); @@ -91,6 +97,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS); const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const [userBillingGraceEndPeriodCollection] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); // Cache the last search results that had data, so the merge option remains available // while results are temporarily unset (e.g. during sorting/loading). @@ -359,9 +366,15 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { return; } - const selectedPolicyIDList = selectedReports.length - ? selectedReports.map((report) => report.policyID) - : Object.values(selectedTransactions).map((transaction) => transaction.policyID); + const selectedItems = selectedReports.length ? selectedReports : Object.values(selectedTransactions); + + const restrictedPolicyID = getRestrictedPolicyID(selectedItems, userBillingGraceEndPeriodCollection); + if (restrictedPolicyID) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(restrictedPolicyID)); + return; + } + + const selectedPolicyIDList = selectedItems.map((item) => item.policyID); const hasDEWPolicy = selectedPolicyIDList.some((policyID) => { const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; return hasDynamicExternalWorkflow(policy); @@ -404,6 +417,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { translate, hash, clearSelectedTransactions, + userBillingGraceEndPeriodCollection, ]); const {expenseCount, uniqueReportCount} = useMemo(() => { @@ -521,9 +535,16 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { return; } - const activeRoute = Navigation.getActiveRoute(); const selectedOptions = selectedReports.length ? selectedReports : Object.values(selectedTransactions); + const restrictedPolicyID = getRestrictedPolicyID(selectedOptions, userBillingGraceEndPeriodCollection); + if (restrictedPolicyID) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(restrictedPolicyID)); + return; + } + + const activeRoute = Navigation.getActiveRoute(); + for (const item of selectedOptions) { const itemPolicyID = item.policyID; const itemReportID = item.reportID; @@ -634,6 +655,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { personalPolicyID, allTransactions, allReports, + userBillingGraceEndPeriodCollection, ], ); @@ -807,6 +829,12 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { const itemList = !selectedReports.length ? Object.values(selectedTransactions).map((transaction) => transaction) : (selectedReports?.filter((report) => !!report) ?? []); + const restrictedPolicyID = getRestrictedPolicyID(itemList, userBillingGraceEndPeriodCollection); + if (restrictedPolicyID) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(restrictedPolicyID)); + return; + } + for (const item of itemList) { const policy = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${item.policyID}`]; if (policy) { @@ -1047,6 +1075,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { styles.colorMuted, styles.fontWeightNormal, styles.textWrap, + userBillingGraceEndPeriodCollection, ]); const handleOfflineModalClose = useCallback(() => { diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 45d22cc126075..b0864d3fbd7bc 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -134,6 +134,10 @@ function handleActionButtonPress({ onDelegateAccessRestricted?.(); return; } + if (snapshotReport.policyID && shouldRestrictUserBillableActions(snapshotReport.policyID)) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(snapshotReport.policyID)); + return; + } getPayActionCallback(hash, item, goToItem, snapshotReport, snapshotPolicy, lastPaymentMethod, currentSearchKey, personalPolicyID); return; case CONST.SEARCH.ACTION_TYPES.APPROVE: @@ -141,6 +145,10 @@ function handleActionButtonPress({ onDelegateAccessRestricted?.(); return; } + if (snapshotReport.policyID && shouldRestrictUserBillableActions(snapshotReport.policyID)) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(snapshotReport.policyID)); + return; + } if (hasDynamicExternalWorkflow(snapshotPolicy) && !isDEWBetaEnabled) { onDEWModalOpen?.(); return; @@ -148,6 +156,10 @@ function handleActionButtonPress({ approveMoneyRequestOnSearch(hash, item.reportID ? [item.reportID] : [], currentSearchKey); return; case CONST.SEARCH.ACTION_TYPES.SUBMIT: { + if (snapshotReport.policyID && shouldRestrictUserBillableActions(snapshotReport.policyID)) { + Navigation.navigate(ROUTES.RESTRICTED_ACTION.getRoute(snapshotReport.policyID)); + return; + } if (hasDynamicExternalWorkflow(snapshotPolicy) && !isDEWBetaEnabled) { onDEWModalOpen?.(); return;