Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1a370ae
migrate to useReceiptScan
Guccio163 Mar 2, 2026
9420a7b
Merge branch 'main' of github.com:software-mansion-labs/expensify-app…
Guccio163 Mar 3, 2026
ccecdf5
tests fix, merge main
Guccio163 Mar 3, 2026
a334f24
Merge branch 'main' of github.com:software-mansion-labs/expensify-app…
Guccio163 Mar 3, 2026
4738080
Merge branch 'Guccio163/onyx-connect/policy_tags/startSplitBill2' of …
Guccio163 Mar 4, 2026
6bad412
calculate participants and participantsPolicyTags before handleMoneyR…
Guccio163 Mar 4, 2026
efc663a
Merge branch 'Guccio163/onyx-connect/policy_tags/startSplitBill2' of …
Guccio163 Mar 4, 2026
bc1a04c
Merge branch 'Guccio163/onyx-connect/policy_tags/startSplitBill2' of …
Guccio163 Mar 4, 2026
d6dcf0a
add mockGetMoneyRequestParticipantOptions mocks
Guccio163 Mar 4, 2026
75dd269
Merge branch 'Guccio163/onyx-connect/policy_tags/startSplitBill2' of …
Guccio163 Mar 6, 2026
06f00cf
Merge branch 'Guccio163/onyx-connect/policy_tags/startSplitBill2' of …
Guccio163 Mar 6, 2026
e3f5420
add participantsPolicyTags selector
Guccio163 Mar 7, 2026
36030f7
extract logic to useParticipantsPolicyTags
Guccio163 Mar 7, 2026
45dc205
Merge branch 'main' of github.com:software-mansion-labs/expensify-app…
Guccio163 Mar 8, 2026
3fde27f
prettier fix
Guccio163 Mar 8, 2026
4f81b38
Merge branch 'main' of github.com:software-mansion-labs/expensify-app…
Guccio163 Mar 9, 2026
de43a84
selector uses participants array
Guccio163 Mar 9, 2026
4bfcee8
Merge branch 'main' of github.com:software-mansion-labs/expensify-app…
Guccio163 Mar 10, 2026
2057454
add participants to useOnyx deps
Guccio163 Mar 10, 2026
af29e58
Merge branch 'main' of github.com:software-mansion-labs/expensify-app…
Guccio163 Mar 10, 2026
6b7d203
revert deps addition
Guccio163 Mar 10, 2026
51b073b
use memoized participants
Guccio163 Mar 11, 2026
e8f6cc1
Merge branch 'main' of github.com:software-mansion-labs/expensify-app…
Guccio163 Mar 11, 2026
314293a
Merge branch 'main' of github.com:software-mansion-labs/expensify-app…
Guccio163 Mar 12, 2026
0a2086a
mobile exp
Guccio163 Mar 12, 2026
f579b26
Merge branch 'main' of github.com:software-mansion-labs/expensify-app…
Guccio163 Mar 12, 2026
560eb7c
Merge branch 'main' of github.com:software-mansion-labs/expensify-app…
Guccio163 Mar 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions src/hooks/useParticipantsPolicyTags.ts
Original file line number Diff line number Diff line change
@@ -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<PolicyTagLists>) => Record<string, PolicyTagLists> {
return (allTags: OnyxCollection<PolicyTagLists>) => {
if (!participants) {
return {};
}
return participants.reduce<Record<string, PolicyTagLists>>((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<string, PolicyTagLists> {
const [participantsPolicyTags = getEmptyObject<Record<string, PolicyTagLists>>()] = useOnyx(ONYXKEYS.COLLECTION.POLICY_TAGS, {selector: getPolicyTagsSelector(participants)}, [
participants,
]);

return participantsPolicyTags;
}

export default useParticipantsPolicyTags;
22 changes: 6 additions & 16 deletions src/libs/actions/IOU/MoneyRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -43,7 +42,6 @@ import type {GpsPoint} from './index';
import {
createDistanceRequest,
getMoneyRequestParticipantsFromReport,
getPolicyTags,
requestMoney,
setCustomUnitRateID,
setMoneyRequestDistance,
Expand Down Expand Up @@ -98,7 +96,6 @@ type MoneyRequestStepScanParticipantsFlowParams = {
policy: OnyxEntry<Policy>;
report: OnyxEntry<Report>;
reportID: string;
reportAttributesDerived?: Record<string, ReportAttributes>;
transactions: Transaction[];
initialTransaction: InitialTransactionParams;
policyForMovingExpenses?: OnyxEntry<Policy>;
Expand All @@ -117,7 +114,6 @@ type MoneyRequestStepScanParticipantsFlowParams = {
policyRecentlyUsedCurrencies?: string[];
introSelected?: IntroSelected;
activePolicyID?: string;
privateIsArchived?: string;
files: ReceiptFile[];
isTestTransaction?: boolean;
locationPermissionGranted?: boolean;
Expand All @@ -127,6 +123,8 @@ type MoneyRequestStepScanParticipantsFlowParams = {
allTransactionDrafts: OnyxCollection<Transaction>;
betas: OnyxEntry<Beta[]>;
recentWaypoints: OnyxEntry<RecentWaypoint[]>;
participants: Participant[];
participantsPolicyTags: Record<string, PolicyTagLists>;
amountOwed: OnyxEntry<number>;
};

Expand Down Expand Up @@ -304,7 +302,6 @@ function handleMoneyRequestStepScanParticipants({
policy,
report,
reportID,
reportAttributesDerived,
transactions,
initialTransaction,
policyForMovingExpenses,
Expand All @@ -324,7 +321,6 @@ function handleMoneyRequestStepScanParticipants({
policyRecentlyUsedCurrencies,
introSelected,
activePolicyID,
privateIsArchived,
files,
isTestTransaction = false,
locationPermissionGranted = false,
Expand All @@ -333,6 +329,8 @@ function handleMoneyRequestStepScanParticipants({
allTransactionDrafts,
betas,
recentWaypoints,
participants,
participantsPolicyTags,
amountOwed,
}: MoneyRequestStepScanParticipantsFlowParams) {
if (backTo) {
Expand Down Expand Up @@ -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);
Expand All @@ -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<PolicyTagLists> = getPolicyTags();
const participantsPolicyTags = participants.reduce<Record<string, PolicyTagLists>>((acc, participant) => {
if (participant.policyID) {
acc[participant.policyID] = allPolicyTags?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`] ?? {};
}
return acc;
}, {});

startSplitBill({
participants,
currentUserLogin: currentUserLogin ?? '',
Expand Down Expand Up @@ -784,5 +774,5 @@ function handleMoneyRequestStepDistanceNavigation({
}
}

export {createTransaction, handleMoneyRequestStepScanParticipants, handleMoneyRequestStepDistanceNavigation};
export {createTransaction, handleMoneyRequestStepScanParticipants, handleMoneyRequestStepDistanceNavigation, getMoneyRequestParticipantOptions};
export type {MoneyRequestStepScanParticipantsFlowParams, MoneyRequestStepDistanceNavigationParams};
14 changes: 4 additions & 10 deletions src/pages/iou/request/step/IOURequestStepConfirmation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
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';
Expand Down Expand Up @@ -105,7 +106,7 @@
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';
Expand Down Expand Up @@ -208,7 +209,6 @@

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);
Expand Down Expand Up @@ -319,6 +319,7 @@
}) ?? [],
[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);
Expand Down Expand Up @@ -699,7 +700,7 @@
existingIOUReport = iouReport;
}
},
[

Check warning on line 703 in src/pages/iou/request/step/IOURequestStepConfirmation.tsx

View workflow job for this annotation

GitHub Actions / Changed files ESLint check

React Hook useCallback has an unnecessary dependency: 'transactionIDs'. Either exclude it or remove the dependency array
transactionIDs,
transactions,
receiptFiles,
Expand Down Expand Up @@ -1104,13 +1105,6 @@
}
const itemTrimmedComment = item?.comment?.comment?.trim() ?? '';

const participantsPolicyTags = selectedParticipants.reduce<Record<string, PolicyTagLists>>((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,
Expand Down Expand Up @@ -1357,7 +1351,7 @@
reportID,
requestType,
betas,
allPolicyTags,
participantsPolicyTags,
personalDetails,
],
);
Expand Down
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -96,19 +97,25 @@ 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,
op: CONST.TELEMETRY.SPAN_SCAN_PROCESS_AND_NAVIGATE,
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,
Expand All @@ -135,7 +142,6 @@ function useReceiptScan({
policyRecentlyUsedCurrencies,
introSelected,
activePolicyID,
privateIsArchived: reportNameValuePairs?.private_isArchived,
files,
isTestTransaction,
locationPermissionGranted,
Expand All @@ -145,6 +151,8 @@ function useReceiptScan({
betas,
recentWaypoints,
allTransactionDrafts,
participants,
participantsPolicyTags,
amountOwed,
});
}
Expand Down
45 changes: 38 additions & 7 deletions tests/actions/IOU/MoneyRequestTest.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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';
Expand All @@ -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', () => {
Expand Down Expand Up @@ -401,7 +403,6 @@ describe('MoneyRequest', () => {
policy: fakePolicy,
report: fakeReport,
reportID: '1',
reportAttributesDerived: {},
transactions: [fakeTransaction],
initialTransaction: {
transactionID: '1',
Expand Down Expand Up @@ -431,6 +432,8 @@ describe('MoneyRequest', () => {
betas: [],
recentWaypoints: [] as RecentWaypoint[],
allTransactionDrafts: {},
participants: [] as Participant[],
participantsPolicyTags: {} as Record<string, PolicyTagLists>,
amountOwed: 0,
};

Expand All @@ -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<Record<string, PolicyTagLists>>((acc, participant) => {
if (participant.policyID) {
acc[participant.policyID] = value?.[`${ONYXKEYS.COLLECTION.POLICY_TAGS}${participant.policyID}`] ?? {};
}
return acc;
}, {});
},
});
});

afterEach(async () => {
Expand Down Expand Up @@ -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<Record<string, PolicyTagLists>>((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,
Expand Down
2 changes: 2 additions & 0 deletions tests/unit/hooks/useReceiptScan.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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', () => ({
Expand Down
Loading