diff --git a/src/hooks/useParticipantsPolicyTags.ts b/src/hooks/useParticipantsPolicyTags.ts new file mode 100644 index 000000000000..e7012e6c2384 --- /dev/null +++ b/src/hooks/useParticipantsPolicyTags.ts @@ -0,0 +1,40 @@ +import type {OnyxCollection} from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {PolicyTagLists} from '@src/types/onyx'; +import {getEmptyObject} from '@src/types/utils/EmptyObject'; +import useOnyx from './useOnyx'; + +type ParticipantWithPolicyID = { + policyID?: string; +}; + +function getPolicyTagsSelector(participants: ParticipantWithPolicyID[]): (allTags: OnyxCollection) => Record { + return (allTags: OnyxCollection) => { + if (!participants) { + return {}; + } + return participants.reduce>((acc, participant) => { + const key = `${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`; + if (allTags?.[key] && participant.policyID) { + acc[participant.policyID] = allTags[key]; + } + return acc; + }, {}); + }; +} + +/** + * Hook that extracts policy tags only for participants' policies. + * + * @param participants - Array of participants with optional policyID + * @returns Record mapping policyID to PolicyTagLists + */ +function useParticipantsPolicyTags(participants: ParticipantWithPolicyID[]): Record { + const [participantsPolicyTags = getEmptyObject>()] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, {selector: getPolicyTagsSelector(participants)}, [ + participants, + ]); + + return participantsPolicyTags; +} + +export default useParticipantsPolicyTags; diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index ff4beeef4048..3ee72fe80b56 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -19,7 +19,6 @@ import type {IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; import type {TranslationParameters, TranslationPaths} from '@src/languages/types'; -import ONYXKEYS from '@src/ONYXKEYS'; import type {Route} from '@src/ROUTES'; import ROUTES from '@src/ROUTES'; import type { @@ -43,7 +42,6 @@ import type {GpsPoint} from './index'; import { createDistanceRequest, getMoneyRequestParticipantsFromReport, - getPolicyTags, requestMoney, setCustomUnitRateID, setMoneyRequestDistance, @@ -98,7 +96,6 @@ type MoneyRequestStepScanParticipantsFlowParams = { policy: OnyxEntry; report: OnyxEntry; reportID: string; - reportAttributesDerived?: Record; transactions: Transaction[]; initialTransaction: InitialTransactionParams; policyForMovingExpenses?: OnyxEntry; @@ -117,7 +114,6 @@ type MoneyRequestStepScanParticipantsFlowParams = { policyRecentlyUsedCurrencies?: string[]; introSelected?: IntroSelected; activePolicyID?: string; - privateIsArchived?: string; files: ReceiptFile[]; isTestTransaction?: boolean; locationPermissionGranted?: boolean; @@ -127,6 +123,8 @@ type MoneyRequestStepScanParticipantsFlowParams = { allTransactionDrafts: OnyxCollection; betas: OnyxEntry; recentWaypoints: OnyxEntry; + participants: Participant[]; + participantsPolicyTags: Record; amountOwed: OnyxEntry; }; @@ -304,7 +302,6 @@ function handleMoneyRequestStepScanParticipants({ policy, report, reportID, - reportAttributesDerived, transactions, initialTransaction, policyForMovingExpenses, @@ -324,7 +321,6 @@ function handleMoneyRequestStepScanParticipants({ policyRecentlyUsedCurrencies, introSelected, activePolicyID, - privateIsArchived, files, isTestTransaction = false, locationPermissionGranted = false, @@ -333,6 +329,8 @@ function handleMoneyRequestStepScanParticipants({ allTransactionDrafts, betas, recentWaypoints, + participants, + participantsPolicyTags, amountOwed, }: MoneyRequestStepScanParticipantsFlowParams) { if (backTo) { @@ -367,8 +365,6 @@ function handleMoneyRequestStepScanParticipants({ // to the confirmation step. // If the user is started this flow using the Create expense option (combined submit/track flow), they should be redirected to the participants page. if (!initialTransaction?.isFromGlobalCreate && !isArchivedExpenseReport && iouType !== CONST.IOU.TYPE.CREATE) { - const participants = getMoneyRequestParticipantOptions(currentUserAccountID, report, policy, personalDetails, privateIsArchived, reportAttributesDerived); - if (shouldSkipConfirmation) { cancelSpan(CONST.TELEMETRY.SPAN_SCAN_PROCESS_AND_NAVIGATE); cancelSpan(CONST.TELEMETRY.SPAN_CONFIRMATION_MOUNT); @@ -380,13 +376,7 @@ function handleMoneyRequestStepScanParticipants({ const splitReceipt: Receipt = firstReceiptFile.file ?? {}; splitReceipt.source = firstReceiptFile.source; splitReceipt.state = CONST.IOU.RECEIPT_STATE.SCAN_READY; - const allPolicyTags: OnyxCollection = getPolicyTags(); - const participantsPolicyTags = participants.reduce>((acc, participant) => { - if (participant.policyID) { - acc[participant.policyID] = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`] ?? {}; - } - return acc; - }, {}); + startSplitBill({ participants, currentUserLogin: currentUserLogin ?? '', @@ -784,5 +774,5 @@ function handleMoneyRequestStepDistanceNavigation({ } } -export {createTransaction, handleMoneyRequestStepScanParticipants, handleMoneyRequestStepDistanceNavigation}; +export {createTransaction, handleMoneyRequestStepScanParticipants, handleMoneyRequestStepDistanceNavigation, getMoneyRequestParticipantOptions}; export type {MoneyRequestStepScanParticipantsFlowParams, MoneyRequestStepDistanceNavigationParams}; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index e365203ac468..49a5736e20e5 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -25,6 +25,7 @@ import useOnyx from '@hooks/useOnyx'; import useOptimisticDraftTransactions from '@hooks/useOptimisticDraftTransactions'; import useParentReportAction from '@hooks/useParentReportAction'; import useParticipantsInvoiceReport from '@hooks/useParticipantsInvoiceReport'; +import useParticipantsPolicyTags from '@hooks/useParticipantsPolicyTags'; import usePermissions from '@hooks/usePermissions'; import usePolicyForTransaction from '@hooks/usePolicyForTransaction'; import usePrivateIsArchivedMap from '@hooks/usePrivateIsArchivedMap'; @@ -105,7 +106,7 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type SCREENS from '@src/SCREENS'; -import type {PolicyTagLists, RecentlyUsedCategories, Report} from '@src/types/onyx'; +import type {RecentlyUsedCategories, Report} from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type {InvoiceReceiver} from '@src/types/onyx/Report'; @@ -208,7 +209,6 @@ function IOURequestStepConfirmation({ const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${draftPolicyID}`); const [policyRecentlyUsedCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES}${policyID}`); - const [allPolicyTags] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policyID}`); const [policyRecentlyUsedTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${policyID}`); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES); @@ -319,6 +319,7 @@ function IOURequestStepConfirmation({ }) ?? [], [transaction?.participants, iouType, personalDetails, reportAttributesDerived, reportDrafts, privateIsArchivedMap, policy, currentUserPersonalDetails.accountID], ); + const participantsPolicyTags = useParticipantsPolicyTags(participants ?? []); const isPolicyExpenseChat = useMemo(() => participants?.some((participant) => participant.isPolicyExpenseChat), [participants]); const shouldGenerateTransactionThreadReport = !isBetaEnabled(CONST.BETAS.NO_OPTIMISTIC_TRANSACTION_THREADS); const formHasBeenSubmitted = useRef(false); @@ -1104,13 +1105,6 @@ function IOURequestStepConfirmation({ } const itemTrimmedComment = item?.comment?.comment?.trim() ?? ''; - const participantsPolicyTags = selectedParticipants.reduce>((acc, participant) => { - if (participant.policyID) { - acc[participant.policyID] = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`] ?? {}; - } - return acc; - }, {}); - // If we have a receipt let's start the split expense by creating only the action, the transaction, and the group DM if needed startSplitBill({ participants: selectedParticipants, @@ -1357,7 +1351,7 @@ function IOURequestStepConfirmation({ reportID, requestType, betas, - allPolicyTags, + participantsPolicyTags, personalDetails, ], ); diff --git a/src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptScan.ts b/src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptScan.ts index ae67e2a60ead..7f374973c900 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptScan.ts +++ b/src/pages/iou/request/step/IOURequestStepScan/hooks/useReceiptScan.ts @@ -1,18 +1,19 @@ import shouldStartLocationPermissionFlowSelector from '@selectors/LocationPermission'; import {hasSeenTourSelector} from '@selectors/Onboarding'; -import {useEffect, useState} from 'react'; +import {useEffect, useMemo, useState} from 'react'; import TestReceipt from '@assets/images/fake-receipt.png'; import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy'; import useFilesValidation from '@hooks/useFilesValidation'; import useOnyx from '@hooks/useOnyx'; import useOptimisticDraftTransactions from '@hooks/useOptimisticDraftTransactions'; +import useParticipantsPolicyTags from '@hooks/useParticipantsPolicyTags'; import usePermissions from '@hooks/usePermissions'; import usePersonalPolicy from '@hooks/usePersonalPolicy'; import usePolicy from '@hooks/usePolicy'; import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses'; import useReportAttributes from '@hooks/useReportAttributes'; import useSelfDMReport from '@hooks/useSelfDMReport'; -import {handleMoneyRequestStepScanParticipants} from '@libs/actions/IOU/MoneyRequest'; +import {getMoneyRequestParticipantOptions, handleMoneyRequestStepScanParticipants} from '@libs/actions/IOU/MoneyRequest'; import setTestReceipt from '@libs/actions/setTestReceipt'; import {isArchivedReport, isPolicyExpenseChat} from '@libs/ReportUtils'; import {getSpan, startSpan} from '@libs/telemetry/activeSpans'; @@ -96,6 +97,12 @@ function useReceiptScan({ const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS); + const participants = useMemo( + () => getMoneyRequestParticipantOptions(currentUserPersonalDetails.accountID, report, policy, personalDetails, reportNameValuePairs?.private_isArchived, reportAttributesDerived), + [currentUserPersonalDetails.accountID, report, policy, personalDetails, reportNameValuePairs?.private_isArchived, reportAttributesDerived], + ); + + const participantsPolicyTags = useParticipantsPolicyTags(participants); function navigateToConfirmationStep(files: ReceiptFile[], locationPermissionGranted = false, isTestTransaction = false) { startSpan(CONST.TELEMETRY.SPAN_SCAN_PROCESS_AND_NAVIGATE, { name: CONST.TELEMETRY.SPAN_SCAN_PROCESS_AND_NAVIGATE, @@ -103,12 +110,12 @@ function useReceiptScan({ parentSpan: getSpan(CONST.TELEMETRY.SPAN_SHUTTER_TO_CONFIRMATION), attributes: {[CONST.TELEMETRY.ATTRIBUTE_IS_MULTI_SCAN]: isMultiScanEnabled}, }); + handleMoneyRequestStepScanParticipants({ iouType, policy, report, reportID, - reportAttributesDerived, transactions, initialTransaction: { transactionID: initialTransactionID, @@ -135,7 +142,6 @@ function useReceiptScan({ policyRecentlyUsedCurrencies, introSelected, activePolicyID, - privateIsArchived: reportNameValuePairs?.private_isArchived, files, isTestTransaction, locationPermissionGranted, @@ -145,6 +151,8 @@ function useReceiptScan({ betas, recentWaypoints, allTransactionDrafts, + participants, + participantsPolicyTags, amountOwed, }); } diff --git a/tests/actions/IOU/MoneyRequestTest.ts b/tests/actions/IOU/MoneyRequestTest.ts index 4820916ad172..8cb8d20947c3 100644 --- a/tests/actions/IOU/MoneyRequestTest.ts +++ b/tests/actions/IOU/MoneyRequestTest.ts @@ -1,7 +1,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {MoneyRequestStepScanParticipantsFlowParams} from '@libs/actions/IOU/MoneyRequest'; -import {createTransaction, handleMoneyRequestStepDistanceNavigation, handleMoneyRequestStepScanParticipants} from '@libs/actions/IOU/MoneyRequest'; +import {createTransaction, getMoneyRequestParticipantOptions, handleMoneyRequestStepDistanceNavigation, handleMoneyRequestStepScanParticipants} from '@libs/actions/IOU/MoneyRequest'; import getCurrentPosition from '@libs/getCurrentPosition'; import {GeolocationErrorCode} from '@libs/getCurrentPosition/getCurrentPosition.types'; import Navigation from '@libs/Navigation/Navigation'; @@ -10,7 +10,8 @@ import type {ReceiptFile} from '@pages/iou/request/step/IOURequestStepScan/types import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {QuickAction, RecentWaypoint} from '@src/types/onyx'; +import type {PolicyTagLists, QuickAction, RecentWaypoint} from '@src/types/onyx'; +import type {Participant} from '@src/types/onyx/IOU'; import type {SplitShares} from '@src/types/onyx/Transaction'; import * as IOU from '../../../src/libs/actions/IOU'; import * as Split from '../../../src/libs/actions/IOU/Split'; @@ -19,6 +20,7 @@ import createRandomPolicy from '../../utils/collections/policies'; import {createRandomReport, createSelfDM} from '../../utils/collections/reports'; import createRandomTransaction from '../../utils/collections/transaction'; import getOnyxValue from '../../utils/getOnyxValue'; +import {getOnyxData} from '../../utils/TestHelper'; import waitForBatchedUpdates from '../../utils/waitForBatchedUpdates'; jest.mock('@libs/actions/IOU', () => { @@ -401,7 +403,6 @@ describe('MoneyRequest', () => { policy: fakePolicy, report: fakeReport, reportID: '1', - reportAttributesDerived: {}, transactions: [fakeTransaction], initialTransaction: { transactionID: '1', @@ -431,6 +432,8 @@ describe('MoneyRequest', () => { betas: [], recentWaypoints: [] as RecentWaypoint[], allTransactionDrafts: {}, + participants: [] as Participant[], + participantsPolicyTags: {} as Record, amountOwed: 0, }; @@ -445,6 +448,19 @@ describe('MoneyRequest', () => { }, }); baseParams.recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; + baseParams.participants = getMoneyRequestParticipantOptions(baseParams.currentUserAccountID, baseParams.report, baseParams.policy, baseParams.personalDetails, undefined, {}); + await getOnyxData({ + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}`, + waitForCollectionCallback: true, + callback: (value) => { + baseParams.participantsPolicyTags = baseParams.participants.reduce>((acc, participant) => { + if (participant.policyID) { + acc[participant.policyID] = value?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`] ?? {}; + } + return acc; + }, {}); + }, + }); }); afterEach(async () => { @@ -547,13 +563,28 @@ describe('MoneyRequest', () => { }); it('should return if no participants found for non-SPLIT iouType when not from global create menu and skipping confirmation', async () => { + const report = { + ...fakeReport, + chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, + }; + baseParams.participants = getMoneyRequestParticipantOptions(baseParams.currentUserAccountID, report, baseParams.policy, baseParams.personalDetails, undefined, {}); + + await getOnyxData({ + key: `${ONYXKEYS.COLLECTION.POLICY_TAGS}`, + waitForCollectionCallback: true, + callback: (value) => { + baseParams.participantsPolicyTags = baseParams.participants.reduce>((acc, participant) => { + if (participant.policyID) { + acc[participant.policyID] = value?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`] ?? {}; + } + return acc; + }, {}); + }, + }); handleMoneyRequestStepScanParticipants({ ...baseParams, iouType: CONST.IOU.TYPE.TRACK, - report: { - ...fakeReport, - chatType: CONST.REPORT.CHAT_TYPE.POLICY_ROOM, - }, + report, shouldSkipConfirmation: true, initialTransaction: { ...baseParams.initialTransaction, diff --git a/tests/unit/hooks/useReceiptScan.test.ts b/tests/unit/hooks/useReceiptScan.test.ts index a866b784ec5e..4f38e4b6dea0 100644 --- a/tests/unit/hooks/useReceiptScan.test.ts +++ b/tests/unit/hooks/useReceiptScan.test.ts @@ -9,6 +9,7 @@ import type {Report, Transaction} from '@src/types/onyx'; import waitForBatchedUpdatesWithAct from '../../utils/waitForBatchedUpdatesWithAct'; const mockHandleMoneyRequestStepScanParticipants = jest.fn(); +const mockGetMoneyRequestParticipantOptions = jest.fn().mockReturnValue([]); const mockRemoveDraftTransactions = jest.fn(); const mockRemoveTransactionReceipt = jest.fn(); const mockSetMoneyRequestReceipt = jest.fn(); @@ -32,6 +33,7 @@ jest.mock('@hooks/useFilesValidation', () => ({ jest.mock('@libs/actions/IOU/MoneyRequest', () => ({ handleMoneyRequestStepScanParticipants: (...args: unknown[]) => mockHandleMoneyRequestStepScanParticipants(...args), + getMoneyRequestParticipantOptions: (...args: unknown[]) => mockGetMoneyRequestParticipantOptions(...args), })); jest.mock('@userActions/TransactionEdit', () => ({