diff --git a/src/pages/Search/SearchTransactionsChangeReport.tsx b/src/pages/Search/SearchTransactionsChangeReport.tsx index 3fe2e994ae2fb..aba7291786f2e 100644 --- a/src/pages/Search/SearchTransactionsChangeReport.tsx +++ b/src/pages/Search/SearchTransactionsChangeReport.tsx @@ -132,6 +132,22 @@ function SearchTransactionsChangeReport() { }); const createReport = () => { + if (!policyForMovingExpensesID && !shouldSelectPolicy && selectedTransactionsKeys.length > 0) { + const firstTransactionID = selectedTransactionsKeys.at(0); + if (firstTransactionID) { + Navigation.navigate( + ROUTES.MONEY_REQUEST_UPGRADE.getRoute({ + action: CONST.IOU.ACTION.EDIT, + iouType: CONST.IOU.TYPE.SUBMIT, + transactionID: firstTransactionID, + reportID: selectedTransactions[firstTransactionID]?.reportID ?? CONST.REPORT.UNREPORTED_REPORT_ID, + upgradePath: CONST.UPGRADE_PATHS.REPORTS, + }), + ); + } + return; + } + if (shouldSelectPolicy) { Navigation.navigate(ROUTES.NEW_REPORT_WORKSPACE_SELECTION.getRoute(true)); return; diff --git a/src/pages/iou/request/step/IOURequestStepUpgrade.tsx b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx index e8f6e935bdc7c..66c312e354cb9 100644 --- a/src/pages/iou/request/step/IOURequestStepUpgrade.tsx +++ b/src/pages/iou/request/step/IOURequestStepUpgrade.tsx @@ -1,25 +1,30 @@ import {hasSeenTourSelector} from '@selectors/Onboarding'; -import React, {useRef, useState} from 'react'; +import React, {useCallback, useMemo, useRef, useState} from 'react'; +import type {OnyxCollection} from 'react-native-onyx'; import ConfirmModal from '@components/ConfirmModal'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import {usePersonalDetails} from '@components/OnyxListItemProvider'; import ScreenWrapper from '@components/ScreenWrapper'; import ScrollView from '@components/ScrollView'; +import {useSearchActionsContext, useSearchStateContext} from '@components/Search/SearchContext'; import WorkspaceConfirmationForm from '@components/WorkspaceConfirmationForm'; import type {WorkspaceConfirmationSubmitFunctionParams} from '@components/WorkspaceConfirmationForm'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; +import usePermissions from '@hooks/usePermissions'; import usePreferredPolicy from '@hooks/usePreferredPolicy'; import useThemeStyles from '@hooks/useThemeStyles'; -import {setTransactionReport} from '@libs/actions/Transaction'; +import {createNewReport} from '@libs/actions/Report'; +import {changeTransactionsReport, setTransactionReport} from '@libs/actions/Transaction'; import type CreateWorkspaceParams from '@libs/API/parameters/CreateWorkspaceParams'; import getPlatform from '@libs/getPlatform'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {MoneyRequestNavigatorParamList} from '@libs/Navigation/types'; import {getParticipantsOption} from '@libs/OptionsListUtils'; +import {getPersonalDetailsForAccountID, hasViolations as hasViolationsReportUtils} from '@libs/ReportUtils'; import UpgradeConfirmation from '@pages/workspace/upgrade/UpgradeConfirmation'; import UpgradeIntro from '@pages/workspace/upgrade/UpgradeIntro'; import {setCustomUnitRateID, setMoneyRequestParticipants} from '@userActions/IOU'; @@ -29,6 +34,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; +import type {PersonalDetails, Transaction} from '@src/types/onyx'; type IOURequestStepUpgradeProps = PlatformStackScreenProps; @@ -39,12 +45,13 @@ function IOURequestStepUpgrade({ }: IOURequestStepUpgradeProps) { const styles = useThemeStyles(); - const {translate} = useLocalize(); + const {translate, toLocaleDigit} = useLocalize(); const {isOffline} = useNetwork(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const personalDetails = usePersonalDetails(); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`); + const [selectedReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); const [onboardingPurposeSelected] = useOnyx(ONYXKEYS.ONBOARDING_PURPOSE_SELECTED); const [isUpgraded, setIsUpgraded] = useState(false); @@ -62,6 +69,43 @@ function IOURequestStepUpgrade({ const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID); const [isSelfTourViewed] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); + // Hooks for bulk move functionality + const {selectedTransactions} = useSearchStateContext(); + const {clearSelectedTransactions} = useSearchActionsContext(); + const selectedTransactionsKeys = useMemo(() => Object.keys(selectedTransactions), [selectedTransactions]); + const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const [allPolicyCategories] = useOnyx(ONYXKEYS.COLLECTION.POLICY_CATEGORIES); + const [allReportNextSteps] = useOnyx(ONYXKEYS.COLLECTION.NEXT_STEP); + const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); + const [session] = useOnyx(ONYXKEYS.SESSION); + const [betas] = useOnyx(ONYXKEYS.BETAS); + + // Build transactions map from selectedTransactions (search results) instead of Onyx TRANSACTION collection + // This ensures that transactions selected from search are properly included in the map passed to changeTransactionsReport + const allTransactions = useMemo( + () => + Object.values(selectedTransactions).reduce( + (transactionsCollection, transactionItem) => { + if (transactionItem.transaction) { + // eslint-disable-next-line no-param-reassign + transactionsCollection[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionItem.transaction.transactionID}`] = transactionItem.transaction; + } + return transactionsCollection; + }, + {} as NonNullable>, + ), + [selectedTransactions], + ); + + const {isBetaEnabled} = usePermissions(); + const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); + const hasViolations = hasViolationsReportUtils(undefined, transactionViolations, session?.accountID ?? CONST.DEFAULT_NUMBER_ID, session?.email ?? ''); + + const ownerPersonalDetails = useMemo( + () => getPersonalDetailsForAccountID(selectedReport?.ownerAccountID, personalDetails) as PersonalDetails, + [personalDetails, selectedReport?.ownerAccountID], + ); + const feature = Object.values(CONST.UPGRADE_FEATURE_INTRO_MAPPING) .filter((value) => value.id !== CONST.UPGRADE_FEATURE_INTRO_MAPPING.policyPreventMemberChangingTitle.id) .find((f) => f.alias === upgradePath); @@ -74,9 +118,39 @@ function IOURequestStepUpgrade({ } }; - const afterUpgradeAcknowledged = () => { + const afterUpgradeAcknowledged = useCallback(() => { const expenseReportID = policyDataRef.current?.expenseChatReportID ?? reportID; const policyID = policyDataRef.current?.policyID; + + // Bulk move expenses + if (upgradePath === CONST.UPGRADE_PATHS.REPORTS && policyID && selectedTransactionsKeys.includes(transactionID)) { + const newPolicy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]; + + const optimisticReport = createNewReport(ownerPersonalDetails, hasViolations, isASAPSubmitBetaEnabled, newPolicy, betas); + + const reportNextStep = allReportNextSteps?.[`${ONYXKEYS.COLLECTION.NEXT_STEP}${optimisticReport.reportID}`]; + + // Move ALL selected transactions to the new report + changeTransactionsReport({ + transactionIDs: selectedTransactionsKeys, + isASAPSubmitBetaEnabled, + accountID: session?.accountID ?? CONST.DEFAULT_NUMBER_ID, + email: session?.email ?? '', + newReport: optimisticReport, + policy: newPolicy, + reportNextStep, + policyCategories: allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyID}`], + allTransactions, + translate, + toLocaleDigit, + }); + + clearSelectedTransactions(); + + Navigation.dismissModal(); + return; + } + if (shouldSubmitExpense) { setMoneyRequestParticipants(transactionID, [ { @@ -129,7 +203,29 @@ function IOURequestStepUpgrade({ default: Navigation.goBack(); } - }; + }, [ + action, + backTo, + navigateWithMicrotask, + reportID, + shouldSubmitExpense, + transactionID, + upgradePath, + selectedTransactionsKeys, + clearSelectedTransactions, + hasViolations, + isASAPSubmitBetaEnabled, + allPolicies, + allReportNextSteps, + allPolicyCategories, + session?.accountID, + session?.email, + ownerPersonalDetails, + allTransactions, + betas, + translate, + toLocaleDigit, + ]); const participant = transaction?.participants?.[0]; const adminParticipant = isDistanceRateUpgrade && participant?.accountID ? getParticipantsOption(participant, personalDetails) : undefined; @@ -170,8 +266,6 @@ function IOURequestStepUpgrade({ policyDataRef.current = policyData; }; - const [session] = useOnyx(ONYXKEYS.SESSION); - const handleConfirmUpgradeWarning = () => { setIsUpgradeWarningModalOpen(false); }; diff --git a/src/pages/workspace/upgrade/UpgradeConfirmation.tsx b/src/pages/workspace/upgrade/UpgradeConfirmation.tsx index 35563e7013534..821ad6ed2a49f 100644 --- a/src/pages/workspace/upgrade/UpgradeConfirmation.tsx +++ b/src/pages/workspace/upgrade/UpgradeConfirmation.tsx @@ -36,7 +36,7 @@ function UpgradeConfirmation({policyName, afterUpgradeAcknowledged, isReporting, }, [updateSubscriptionLink]); const description = useMemo(() => { - if (isCategorizing ?? isReporting) { + if (isCategorizing || isReporting) { return {translate('workspace.upgrade.completed.categorizeMessage')}; } @@ -56,7 +56,7 @@ function UpgradeConfirmation({policyName, afterUpgradeAcknowledged, isReporting, }, [isDistanceRateUpgrade, isCategorizing, isReporting, isTravelUpgrade, policyName, styles.renderHTML, styles.textAlignCenter, styles.w100, translate, subscriptionLink]); const heading = useMemo(() => { - if (isCategorizing ?? isReporting) { + if (isCategorizing || isReporting) { return translate('workspace.upgrade.completed.createdWorkspace'); } return translate('workspace.upgrade.completed.headline');