From 84044b59e57c45a57065c82b78982f52c50f1fb0 Mon Sep 17 00:00:00 2001 From: parasharrajat Date: Fri, 7 Aug 2026 11:32:35 +0530 Subject: [PATCH 01/27] Refactor getCleanUpTransactionThreadReportOnyxData to accept optional args --- src/libs/actions/IOU/DeleteMoneyRequest.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/IOU/DeleteMoneyRequest.ts b/src/libs/actions/IOU/DeleteMoneyRequest.ts index de0d6dd6c801..24ff13ed76e5 100644 --- a/src/libs/actions/IOU/DeleteMoneyRequest.ts +++ b/src/libs/actions/IOU/DeleteMoneyRequest.ts @@ -43,6 +43,8 @@ import Onyx from 'react-native-onyx'; import {getAllReportActionsFromIOU, getAllReportNameValuePairs, getAllReports, getAllTransactions, getAllTransactionViolations} from '.'; import {getReportPreviewAction, maybeUpdateReportNameForFormulaTitle} from './MoneyRequestBuilder'; +type ReportEntry = OnyxEntry; + type PrepareToCleanUpMoneyRequestResult = { shouldDeleteTransactionThread: boolean; shouldDeleteIOUReport: boolean; @@ -570,6 +572,9 @@ function getCleanUpTransactionThreadReportOnyxData({ updatedReportPreviewAction, shouldAddUpdatedReportPreviewActionToOnyxData = true, currentUserAccountID, + transactionThread: transactionThreadParam, + iouReport: iouReportParam, + chatReport: chatReportParam, }: { transactionThreadID?: string; shouldDeleteTransactionThread: boolean; @@ -578,6 +583,9 @@ function getCleanUpTransactionThreadReportOnyxData({ updatedReportPreviewAction?: ReportAction; shouldAddUpdatedReportPreviewActionToOnyxData?: boolean; currentUserAccountID: number; + transactionThread?: ReportEntry; + iouReport?: ReportEntry; + chatReport?: ReportEntry; }) { const allReports = getAllReports(); const allReportActions = getAllReportActionsFromIOU(); @@ -591,7 +599,7 @@ function getCleanUpTransactionThreadReportOnyxData({ let transactionThread = null; let transactionThreadReportActions = null; if (transactionThreadID) { - transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`] ?? null; + transactionThread = transactionThreadParam ?? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`] ?? null; transactionThreadReportActions = allReportActions?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionThreadID}`] ?? null; } @@ -641,8 +649,8 @@ function getCleanUpTransactionThreadReportOnyxData({ // Update the child comment visible count for reportPreviewAction. const iouReportID = isMoneyRequestAction(reportAction) ? reportAction?.reportID : undefined; - const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]; - const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`]; + const iouReport = iouReportParam ?? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]; + const chatReport = chatReportParam ?? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`]; const originalReportPreviewAction = getReportPreviewAction(chatReport?.reportID, iouReport?.reportID) ?? undefined; let reportPreviewAction = updatedReportPreviewAction ?? originalReportPreviewAction; if ( From 64eedc5fe226b6945523c607aea57b9619fd6f6e Mon Sep 17 00:00:00 2001 From: parasharrajat Date: Fri, 7 Aug 2026 12:11:29 +0530 Subject: [PATCH 02/27] Pass transactionThread to getCleanUpTransactionThreadReportOnyxData --- src/libs/actions/MergeTransaction.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index 1ac438b5867d..c33f844c16a0 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -54,6 +54,7 @@ import Onyx from 'react-native-onyx'; import type {UpdateMoneyRequestData, UpdateMoneyRequestDataKeys} from './IOU/UpdateMoneyRequest'; +import {getAllReports} from './IOU'; import {getCleanUpTransactionThreadReportOnyxData} from './IOU/DeleteMoneyRequest'; import {getDeleteTrackExpenseInformation} from './IOU/TrackExpense'; import {getUpdateMoneyRequestParams, getUpdateTrackExpenseParams} from './IOU/UpdateMoneyRequest'; @@ -589,11 +590,15 @@ function mergeTransactionRequest({ const sourceIouAction = getIOUActionForReportID(sourceTransaction.reportID, sourceTransaction.transactionID); const sourceTransactionThreadReportID = sourceIouAction?.childReportID; const shouldDeleteTransactionThread = !!sourceTransactionThreadReportID; + const allReports = getAllReports(); + const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${sourceTransactionThreadReportID}`]; + const cleanUpSourceTransactionThreadReportOnyxData = getCleanUpTransactionThreadReportOnyxData({ transactionThreadID: sourceTransactionThreadReportID, shouldDeleteTransactionThread, reportAction: sourceIouAction, currentUserAccountID: currentUserAccountIDParam, + transactionThread, }); optimisticSourceReportActionData.push(...cleanUpSourceTransactionThreadReportOnyxData.optimisticData); successSourceReportActionData.push(...cleanUpSourceTransactionThreadReportOnyxData.successData); From d5e56823f563c18bdbdbd457aa895143e93aca37 Mon Sep 17 00:00:00 2001 From: parasharrajat Date: Fri, 7 Aug 2026 12:32:15 +0530 Subject: [PATCH 03/27] Pass reports to getCleanUpTransactionThreadReportOnyxData in duplicate merge --- src/libs/actions/IOU/Duplicate.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index 8fc4f5b9e09b..ab2aadadbdb2 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -297,6 +297,10 @@ function mergeDuplicates({ let updatedReportPreviewAction; for (const [index, iouAction] of actions.entries()) { const transactionThreadID = iouAction.childReportID; + const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`]; + const iouReportID = isMoneyRequestAction(iouAction) ? iouAction?.reportID : undefined; + const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]; + const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`]; const cleanUp = getCleanUpTransactionThreadReportOnyxData({ transactionThreadID, shouldDeleteTransactionThread: !!transactionThreadID, @@ -304,6 +308,9 @@ function mergeDuplicates({ updatedReportPreviewAction, shouldAddUpdatedReportPreviewActionToOnyxData: index === actions.length - 1, currentUserAccountID, + transactionThread, + iouReport, + chatReport, }); cleanUpTransactionThreadReportsOptimisticData.push(...cleanUp.optimisticData); cleanUpTransactionThreadReportsSuccessData.push(...cleanUp.successData); From b284170a516fcec83f24f355dcd9f8409db0c740 Mon Sep 17 00:00:00 2001 From: parasharrajat Date: Fri, 7 Aug 2026 13:06:45 +0530 Subject: [PATCH 04/27] refactor pass chatReport to cleanup transaction thread data builder --- src/libs/actions/IOU/TrackExpense.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU/TrackExpense.ts b/src/libs/actions/IOU/TrackExpense.ts index b0378ebf0c5f..8643dd0aff9b 100644 --- a/src/libs/actions/IOU/TrackExpense.ts +++ b/src/libs/actions/IOU/TrackExpense.ts @@ -724,6 +724,7 @@ function getDeleteTrackExpenseInformation( const cleanUpTransactionThreadReportOnyxData = getCleanUpTransactionThreadReportOnyxData({ transactionThreadID, shouldDeleteTransactionThread, + chatReport, currentUserAccountID, }); optimisticData.push(...cleanUpTransactionThreadReportOnyxData.optimisticData); From 5b558d5bb21a5f838b79cd78830da32f7c5925d5 Mon Sep 17 00:00:00 2001 From: parasharrajat Date: Fri, 7 Aug 2026 20:45:14 +0530 Subject: [PATCH 05/27] Pass source transaction thread report via hooks to cleanup transaction thread data builder --- src/libs/actions/IOU/TrackExpense.ts | 4 ++++ src/libs/actions/MergeTransaction.ts | 4 ++++ src/pages/TransactionMerge/DynamicConfirmationPage.tsx | 3 +++ 3 files changed, 11 insertions(+) diff --git a/src/libs/actions/IOU/TrackExpense.ts b/src/libs/actions/IOU/TrackExpense.ts index 8643dd0aff9b..423f27a9ba47 100644 --- a/src/libs/actions/IOU/TrackExpense.ts +++ b/src/libs/actions/IOU/TrackExpense.ts @@ -650,6 +650,8 @@ function getDeleteTrackExpenseInformation( actionableWhisperReportActionID = '', resolution = '', shouldRemoveIOUTransaction = true, + transactionThread?: OnyxEntry, + iouReport?: OnyxEntry, ) { // STEP 1: Get all collections we're updating const transaction = getAllTransactions()?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; @@ -725,6 +727,8 @@ function getDeleteTrackExpenseInformation( transactionThreadID, shouldDeleteTransactionThread, chatReport, + transactionThread, + iouReport, currentUserAccountID, }); optimisticData.push(...cleanUpTransactionThreadReportOnyxData.optimisticData); diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index c33f844c16a0..5f46d0146a0b 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -379,6 +379,7 @@ type MergeTransactionRequestParams = { allTransactionViolations: OnyxCollection; sourceTransaction: Transaction; targetTransactionThreadReport: OnyxEntry; + sourceTransactionThreadReport: OnyxEntry; targetTransactionThreadParentReport: OnyxEntry; targetTransactionThreadParentReportNextStep: OnyxEntry; iouReportOwnerLogin: string | undefined; @@ -409,6 +410,7 @@ function mergeTransactionRequest({ targetTransaction, sourceTransaction, targetTransactionThreadReport, + sourceTransactionThreadReport, targetTransactionThreadParentReport, targetTransactionThreadParentReportNextStep, iouReportOwnerLogin, @@ -630,6 +632,8 @@ function mergeTransactionRequest({ actionableWhisperReportActionID, CONST.REPORT.ACTIONABLE_TRACK_EXPENSE_WHISPER_RESOLUTION.NOTHING, false, + sourceTransactionThreadReport, + selfDMReport, ); sourceTransactionOptimisticData.push(...optimisticData); diff --git a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx index 351133260746..b67efebafeeb 100644 --- a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx +++ b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx @@ -65,6 +65,8 @@ function DynamicConfirmationPage({route}: DynamicConfirmationPageProps) { const targetTransactionThreadReportID = getTransactionThreadReportID(targetTransaction); const [targetTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${targetTransactionThreadReportID}`); + const sourceTransactionThreadReportID = getTransactionThreadReportID(sourceTransaction); + const [sourceTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceTransactionThreadReportID)}`); const [targetTransactionThreadParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(targetTransactionThreadReport?.parentReportID)}`); const [targetTransactionThreadParentReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${getNonEmptyStringOnyxID(targetTransactionThreadReport?.parentReportID)}`); const [iouReportOwnerLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, { @@ -95,6 +97,7 @@ function DynamicConfirmationPage({route}: DynamicConfirmationPageProps) { targetTransaction, sourceTransaction, targetTransactionThreadReport, + sourceTransactionThreadReport, targetTransactionThreadParentReport, targetTransactionThreadParentReportNextStep, iouReportOwnerLogin, From c2d565e796587e8c6b92f5ea49bbaf365d18623c Mon Sep 17 00:00:00 2001 From: parasharrajat Date: Sat, 8 Aug 2026 09:51:27 +0530 Subject: [PATCH 06/27] Source transaction thread and duplicate merge reports via hooks instead of getAllReports --- src/libs/actions/IOU/Duplicate.ts | 15 ++++++++------- src/libs/actions/MergeTransaction.ts | 6 +----- .../DynamicConfirmationPage.tsx | 11 ++++++++++- tests/actions/IOUTest/DuplicateTest.ts | 6 ++++++ 4 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index ab2aadadbdb2..ee5441cf1476 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -59,7 +59,7 @@ import type {PerDiemExpenseInformation} from './PerDiem'; import type {CreateDistanceRequestInformation} from './Split'; import type {CreateTrackExpenseParams} from './TrackExpense'; -import {buildParticipantsPolicyTags, getAllReports, getAllTransactions} from '.'; +import {buildParticipantsPolicyTags, getAllTransactions} from '.'; import {getCleanUpTransactionThreadReportOnyxData} from './DeleteMoneyRequest'; import {getMoneyRequestParticipantsFromReport} from './MoneyRequest'; import {submitPerDiemExpense} from './PerDiem'; @@ -175,6 +175,7 @@ type MergeDuplicatesFuncParams = MergeDuplicatesParams & { taxValue?: string; allTransactionViolations: OnyxCollection; allReportActionsList: OnyxCollection; + allReportsList: OnyxCollection; }; /** Merge several transactions into one by updating the fields of the one we want to keep and deleting the rest */ @@ -186,11 +187,11 @@ function mergeDuplicates({ taxValue, allTransactionViolations, allReportActionsList, + allReportsList, ...params }: MergeDuplicatesFuncParams) { const allParams: MergeDuplicatesParams = {...params}; const allTransactions = getAllTransactions(); - const allReports = getAllReports(); const originalSelectedTransaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${params.transactionID}`]; const optimisticTransactionData = buildOptimisticTransactionData({ @@ -242,7 +243,7 @@ function mergeDuplicates({ }; }); - const expenseReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${params.reportID}`]; + const expenseReport = allReportsList?.[`${ONYXKEYS.COLLECTION.REPORT}${params.reportID}`]; // Group each discarded duplicate's IOU action and amount by its own source report so the // soft-delete MERGE and total decrement target the correct keys when duplicates span reports. @@ -269,7 +270,7 @@ function mergeDuplicates({ const cleanUpTransactionThreadReportsSuccessData = []; const cleanUpTransactionThreadReportsFailureData = []; for (const [sourceReportID, {amount, reimbursableAmount, actions}] of sources) { - const sourceReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${sourceReportID}`]; + const sourceReport = allReportsList?.[`${ONYXKEYS.COLLECTION.REPORT}${sourceReportID}`]; const sourceReimbursableTotal = getReimbursableTotal(sourceReport); expenseReportOptimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -297,10 +298,10 @@ function mergeDuplicates({ let updatedReportPreviewAction; for (const [index, iouAction] of actions.entries()) { const transactionThreadID = iouAction.childReportID; - const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`]; + const transactionThread = allReportsList?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`]; const iouReportID = isMoneyRequestAction(iouAction) ? iouAction?.reportID : undefined; - const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]; - const chatReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`]; + const iouReport = allReportsList?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]; + const chatReport = allReportsList?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`]; const cleanUp = getCleanUpTransactionThreadReportOnyxData({ transactionThreadID, shouldDeleteTransactionThread: !!transactionThreadID, diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index 5f46d0146a0b..870aa80967e8 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -54,7 +54,6 @@ import Onyx from 'react-native-onyx'; import type {UpdateMoneyRequestData, UpdateMoneyRequestDataKeys} from './IOU/UpdateMoneyRequest'; -import {getAllReports} from './IOU'; import {getCleanUpTransactionThreadReportOnyxData} from './IOU/DeleteMoneyRequest'; import {getDeleteTrackExpenseInformation} from './IOU/TrackExpense'; import {getUpdateMoneyRequestParams, getUpdateTrackExpenseParams} from './IOU/UpdateMoneyRequest'; @@ -592,15 +591,12 @@ function mergeTransactionRequest({ const sourceIouAction = getIOUActionForReportID(sourceTransaction.reportID, sourceTransaction.transactionID); const sourceTransactionThreadReportID = sourceIouAction?.childReportID; const shouldDeleteTransactionThread = !!sourceTransactionThreadReportID; - const allReports = getAllReports(); - const transactionThread = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${sourceTransactionThreadReportID}`]; - const cleanUpSourceTransactionThreadReportOnyxData = getCleanUpTransactionThreadReportOnyxData({ transactionThreadID: sourceTransactionThreadReportID, shouldDeleteTransactionThread, reportAction: sourceIouAction, currentUserAccountID: currentUserAccountIDParam, - transactionThread, + transactionThread: sourceTransactionThreadReport, }); optimisticSourceReportActionData.push(...cleanUpSourceTransactionThreadReportOnyxData.optimisticData); successSourceReportActionData.push(...cleanUpSourceTransactionThreadReportOnyxData.successData); diff --git a/src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx b/src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx index a478f76c922b..bb500c9ca549 100644 --- a/src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx +++ b/src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx @@ -66,6 +66,7 @@ function DynamicConfirmationPage() { const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${newTransaction?.reportID}`); const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newTransaction?.reportID}`); const [allReportActions] = useOnyx(ONYXKEYS.COLLECTION.REPORT_ACTIONS); + const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); const reportAction = Object.values(reportActions ?? {}).find( (action) => ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID === reviewDuplicates?.transactionID, ); @@ -108,7 +109,15 @@ function DynamicConfirmationPage() { // Suppress the NotFound guard for the discarded thread the server tears down on merge. const keptReportRoute = ROUTES.REPORT_WITH_ID.getRoute(mergeParams.reportID); setDeleteTransactionNavigateBackUrl(keptReportRoute); - mergeDuplicates({...mergeParams, ...taxData, currentUserAccountID, currentUserLogin: currentUserLogin ?? '', allTransactionViolations, allReportActionsList: allReportActions}); + mergeDuplicates({ + ...mergeParams, + ...taxData, + currentUserAccountID, + currentUserLogin: currentUserLogin ?? '', + allTransactionViolations, + allReportActionsList: allReportActions, + allReportsList: allReports, + }); if (isSuperWideRHPDisplayed) { Navigation.dismissToSuperWideRHP(); return; diff --git a/tests/actions/IOUTest/DuplicateTest.ts b/tests/actions/IOUTest/DuplicateTest.ts index bfedb717d6cc..15069deb1582 100644 --- a/tests/actions/IOUTest/DuplicateTest.ts +++ b/tests/actions/IOUTest/DuplicateTest.ts @@ -229,6 +229,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate2ID}`]: duplicate2Violations, }, + allReportsList: {}, allReportActionsList: { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {action456: iouAction1, action789: iouAction2}, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${childReportID}`]: {}, @@ -319,6 +320,7 @@ describe('actions/Duplicate', () => { currentUserLogin: RORY_EMAIL, currentUserAccountID: RORY_ACCOUNT_ID, allTransactionViolations: {[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: []}, + allReportsList: {}, allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {}}, }); await waitForBatchedUpdates(); @@ -379,6 +381,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: [], [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: [], }, + allReportsList: {}, allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {}}, }); await waitForBatchedUpdates(); @@ -598,6 +601,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate2ID}`]: duplicate2Violations, }, + allReportsList: {}, allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {[iouAction1ID]: iouAction1, [iouAction2ID]: iouAction2}}, }); await waitForBatchedUpdates(); @@ -705,6 +709,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, }, + allReportsList: {}, allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {mainAction123: mainIouAction, action456: dupIouAction}}, }); await waitForBatchedUpdates(); @@ -793,6 +798,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${crossReportDuplicateID}`]: crossDuplicateViolations, }, + allReportsList: {}, allReportActionsList: { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${keptReportID}`]: {actionMain: mainIouAction}, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${crossReportID}`]: {actionCross: crossIouAction}, From 030f9592ca7025e7fe4485b086a0f5fa26fc53c8 Mon Sep 17 00:00:00 2001 From: parasharrajat Date: Mon, 10 Aug 2026 18:30:50 +0530 Subject: [PATCH 07/27] Source transaction thread and duplicate merge reports via hooks, remove getAllReports from getCleanUp callers --- src/libs/actions/IOU/TrackExpense.ts | 1 + .../DynamicConfirmationPage.tsx | 2 +- tests/actions/IOUTest/DuplicateTest.ts | 13 +++++++------ tests/actions/MergeTransactionTest.ts | 9 +++++++++ 4 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/libs/actions/IOU/TrackExpense.ts b/src/libs/actions/IOU/TrackExpense.ts index 423f27a9ba47..b4806aca55bf 100644 --- a/src/libs/actions/IOU/TrackExpense.ts +++ b/src/libs/actions/IOU/TrackExpense.ts @@ -639,6 +639,7 @@ function buildOnyxDataForTrackExpense({ return onyxData; } +// eslint-disable-next-line @typescript-eslint/max-params function getDeleteTrackExpenseInformation( chatReport: OnyxEntry, transactionID: string | undefined, diff --git a/src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx b/src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx index bb500c9ca549..ce076e4d2203 100644 --- a/src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx +++ b/src/pages/TransactionDuplicate/DynamicConfirmationPage.tsx @@ -131,7 +131,7 @@ function DynamicConfirmationPage() { Navigation.dismissModal({ afterTransition: () => Navigation.navigate(keptReportRoute, {forceReplace: true}), }); - }, [childReportID, transactionsMergeParams, taxData, currentUserAccountID, currentUserLogin, isSuperWideRHPDisplayed, allTransactionViolations, allReportActions]); + }, [childReportID, transactionsMergeParams, taxData, currentUserAccountID, currentUserLogin, isSuperWideRHPDisplayed, allTransactionViolations, allReportActions, allReports]); const handleResolveDuplicates = useCallback(() => { resolveDuplicates({...transactionsMergeParams, ...taxData, transactionThreadReportIDMap, allTransactionViolations, allReportActionsList: allReportActions}); diff --git a/tests/actions/IOUTest/DuplicateTest.ts b/tests/actions/IOUTest/DuplicateTest.ts index 15069deb1582..6baddc93531e 100644 --- a/tests/actions/IOUTest/DuplicateTest.ts +++ b/tests/actions/IOUTest/DuplicateTest.ts @@ -1,6 +1,7 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import type {RenderAPI} from '@testing-library/react-native'; +import {getAllReports} from '@libs/actions/IOU'; import {bulkDuplicateExpenses, bulkDuplicateReports, duplicateExpenseTransaction, duplicateReport, mergeDuplicates, resolveDuplicates} from '@libs/actions/IOU/Duplicate'; import type {BulkDuplicateReportsParams, DuplicateReportParams} from '@libs/actions/IOU/Duplicate'; import {getReportPreviewAction} from '@libs/actions/IOU/MoneyRequestBuilder'; @@ -229,7 +230,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate2ID}`]: duplicate2Violations, }, - allReportsList: {}, + allReportsList: getAllReports(), allReportActionsList: { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {action456: iouAction1, action789: iouAction2}, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${childReportID}`]: {}, @@ -320,7 +321,7 @@ describe('actions/Duplicate', () => { currentUserLogin: RORY_EMAIL, currentUserAccountID: RORY_ACCOUNT_ID, allTransactionViolations: {[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: []}, - allReportsList: {}, + allReportsList: getAllReports(), allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {}}, }); await waitForBatchedUpdates(); @@ -381,7 +382,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: [], [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: [], }, - allReportsList: {}, + allReportsList: getAllReports(), allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {}}, }); await waitForBatchedUpdates(); @@ -601,7 +602,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate2ID}`]: duplicate2Violations, }, - allReportsList: {}, + allReportsList: getAllReports(), allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {[iouAction1ID]: iouAction1, [iouAction2ID]: iouAction2}}, }); await waitForBatchedUpdates(); @@ -709,7 +710,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, }, - allReportsList: {}, + allReportsList: getAllReports(), allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {mainAction123: mainIouAction, action456: dupIouAction}}, }); await waitForBatchedUpdates(); @@ -798,7 +799,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${crossReportDuplicateID}`]: crossDuplicateViolations, }, - allReportsList: {}, + allReportsList: getAllReports(), allReportActionsList: { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${keptReportID}`]: {actionMain: mainIouAction}, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${crossReportID}`]: {actionCross: crossIouAction}, diff --git a/tests/actions/MergeTransactionTest.ts b/tests/actions/MergeTransactionTest.ts index 4a45e6402cff..b6bfba54a6f8 100644 --- a/tests/actions/MergeTransactionTest.ts +++ b/tests/actions/MergeTransactionTest.ts @@ -195,6 +195,7 @@ function runCrossReportMergeToSourceReportRequest(fixtures: CrossReportMergeToSo policyTags: undefined, policyCategories: undefined, allTransactionViolations: createAllTransactionViolations(targetTransaction.transactionID, sourceTransaction.transactionID, mockViolations, mockViolations), + sourceTransactionThreadReport: undefined, targetTransactionThreadReport: {reportID: targetReport.reportID}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -309,6 +310,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, + sourceTransactionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target-report-456'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -428,6 +430,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, + sourceTransactionThreadReport: undefined, targetTransactionThreadReport: {reportID: targetReportID}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -532,6 +535,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, + sourceTransactionThreadReport: undefined, targetTransactionThreadReport: {reportID: targetExpenseReport.reportID}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -698,6 +702,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, + sourceTransactionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target-report-456'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -805,6 +810,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, + sourceTransactionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target123'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -1037,6 +1043,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, + sourceTransactionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target-report-456'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -1240,6 +1247,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, + sourceTransactionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target-report-456'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -1392,6 +1400,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, + sourceTransactionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target-report-456'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, From 69abde347a441853685f7699be773b7be66fcce5 Mon Sep 17 00:00:00 2001 From: parasharrajat Date: Mon, 10 Aug 2026 19:21:45 +0530 Subject: [PATCH 08/27] Thread reports via hooks to getCleanUpTransactionThreadReportOnyxData callers --- tests/actions/IOUTest/DuplicateTest.ts | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/tests/actions/IOUTest/DuplicateTest.ts b/tests/actions/IOUTest/DuplicateTest.ts index 6baddc93531e..bceaa6baadf9 100644 --- a/tests/actions/IOUTest/DuplicateTest.ts +++ b/tests/actions/IOUTest/DuplicateTest.ts @@ -1,7 +1,6 @@ /* eslint-disable @typescript-eslint/no-unsafe-assignment */ import type {RenderAPI} from '@testing-library/react-native'; -import {getAllReports} from '@libs/actions/IOU'; import {bulkDuplicateExpenses, bulkDuplicateReports, duplicateExpenseTransaction, duplicateReport, mergeDuplicates, resolveDuplicates} from '@libs/actions/IOU/Duplicate'; import type {BulkDuplicateReportsParams, DuplicateReportParams} from '@libs/actions/IOU/Duplicate'; import {getReportPreviewAction} from '@libs/actions/IOU/MoneyRequestBuilder'; @@ -230,7 +229,9 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate2ID}`]: duplicate2Violations, }, - allReportsList: getAllReports(), + allReportsList: { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: expenseReport, + }, allReportActionsList: { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {action456: iouAction1, action789: iouAction2}, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${childReportID}`]: {}, @@ -321,7 +322,7 @@ describe('actions/Duplicate', () => { currentUserLogin: RORY_EMAIL, currentUserAccountID: RORY_ACCOUNT_ID, allTransactionViolations: {[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: []}, - allReportsList: getAllReports(), + allReportsList: {}, allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {}}, }); await waitForBatchedUpdates(); @@ -382,7 +383,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: [], [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: [], }, - allReportsList: getAllReports(), + allReportsList: {}, allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {}}, }); await waitForBatchedUpdates(); @@ -602,7 +603,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate2ID}`]: duplicate2Violations, }, - allReportsList: getAllReports(), + allReportsList: {}, allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {[iouAction1ID]: iouAction1, [iouAction2ID]: iouAction2}}, }); await waitForBatchedUpdates(); @@ -710,7 +711,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, }, - allReportsList: getAllReports(), + allReportsList: {}, allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {mainAction123: mainIouAction, action456: dupIouAction}}, }); await waitForBatchedUpdates(); @@ -799,7 +800,10 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${crossReportDuplicateID}`]: crossDuplicateViolations, }, - allReportsList: getAllReports(), + allReportsList: { + [`${ONYXKEYS.COLLECTION.REPORT}${keptReportID}`]: keptReport, + [`${ONYXKEYS.COLLECTION.REPORT}${crossReportID}`]: crossReport, + }, allReportActionsList: { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${keptReportID}`]: {actionMain: mainIouAction}, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${crossReportID}`]: {actionCross: crossIouAction}, From 967176ff24b154dfbb3bbdc5ec4d8b7bbaf7ce75 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Tue, 1 Sep 2026 17:36:35 +0530 Subject: [PATCH 09/27] changes --- src/libs/actions/IOU/DeleteMoneyRequest.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/libs/actions/IOU/DeleteMoneyRequest.ts b/src/libs/actions/IOU/DeleteMoneyRequest.ts index 3b2e3136c187..f757d3dadc89 100644 --- a/src/libs/actions/IOU/DeleteMoneyRequest.ts +++ b/src/libs/actions/IOU/DeleteMoneyRequest.ts @@ -45,8 +45,6 @@ import Onyx from 'react-native-onyx'; import {getAllReportActionsFromIOU, getAllReportNameValuePairs, getAllReports, getAllTransactions, getAllTransactionViolations} from '.'; import {getReportPreviewReportAction, maybeUpdateReportNameForFormulaTitle} from './MoneyRequestBuilder'; -type ReportEntry = OnyxEntry; - type PrepareToCleanUpMoneyRequestResult = { shouldDeleteTransactionThread: boolean; shouldDeleteIOUReport: boolean; @@ -633,9 +631,9 @@ function getCleanUpTransactionThreadReportOnyxData({ updatedReportPreviewAction?: ReportAction; shouldAddUpdatedReportPreviewActionToOnyxData?: boolean; currentUserAccountID: number; - transactionThread?: ReportEntry; - iouReport?: ReportEntry; - chatReport?: ReportEntry; + transactionThread?: OnyxEntry; + iouReport?: OnyxEntry; + chatReport?: OnyxEntry; transactionThreadReportActionsParam?: OnyxEntry; }) { const allReports = getAllReports(); From 74feda453f63712ce17b5ee8d5afd0a643d493a2 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 2 Sep 2026 19:37:15 +0530 Subject: [PATCH 10/27] Remove unused --- src/libs/actions/IOU/TrackExpense.ts | 3 --- src/libs/actions/MergeTransaction.ts | 1 - 2 files changed, 4 deletions(-) diff --git a/src/libs/actions/IOU/TrackExpense.ts b/src/libs/actions/IOU/TrackExpense.ts index 6809ebd58aa3..bef0c892f928 100644 --- a/src/libs/actions/IOU/TrackExpense.ts +++ b/src/libs/actions/IOU/TrackExpense.ts @@ -665,7 +665,6 @@ function getDeleteTrackExpenseInformation( resolution = '', shouldRemoveIOUTransaction = true, transactionThread?: OnyxEntry, - iouReport?: OnyxEntry, ) { // STEP 1: Get all collections we're updating const transaction = getAllTransactions()?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; @@ -740,9 +739,7 @@ function getDeleteTrackExpenseInformation( const cleanUpTransactionThreadReportOnyxData = getCleanUpTransactionThreadReportOnyxData({ transactionThreadID, shouldDeleteTransactionThread, - chatReport, transactionThread, - iouReport, currentUserAccountID, }); optimisticData.push(...cleanUpTransactionThreadReportOnyxData.optimisticData); diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index 3e33236ab7f5..7d410ec601df 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -660,7 +660,6 @@ function mergeTransactionRequest({ CONST.REPORT.ACTIONABLE_TRACK_EXPENSE_WHISPER_RESOLUTION.NOTHING, false, sourceTransactionThreadReport, - selfDMReport, ); sourceTransactionOptimisticData.push(...optimisticData); From 2b300cbbec6f618e239e82deebb7453432d2fc1a Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 2 Sep 2026 19:40:17 +0530 Subject: [PATCH 11/27] refactor params --- .../actions/IOU/SplitTransactionUpdate.ts | 24 ++++---- src/libs/actions/IOU/TrackExpense.ts | 57 +++++++++++-------- src/libs/actions/MergeTransaction.ts | 24 ++++---- tests/actions/IOUTest/TrackExpenseTest.ts | 36 ++++++------ 4 files changed, 76 insertions(+), 65 deletions(-) diff --git a/src/libs/actions/IOU/SplitTransactionUpdate.ts b/src/libs/actions/IOU/SplitTransactionUpdate.ts index 3d024da4c316..268ee1e1cb22 100644 --- a/src/libs/actions/IOU/SplitTransactionUpdate.ts +++ b/src/libs/actions/IOU/SplitTransactionUpdate.ts @@ -1407,18 +1407,18 @@ function updateSplitTransactions({ optimisticData: deleteExpenseOptimisticData, failureData: deleteExpenseFailureData, successData: deleteExpenseSuccessData, - } = getDeleteTrackExpenseInformation( - splitTransactionReport, - undeletedTransaction?.transactionID, - currentReportAction, - undefined, - currentUserPersonalDetails.accountID, - undefined, - undefined, - undefined, - undefined, - isReportArchived || undeletedTransaction?.transactionID === forceDeleteSplitTransactionID, - ); + } = getDeleteTrackExpenseInformation({ + chatReport: splitTransactionReport, + transactionID: undeletedTransaction?.transactionID, + reportAction: currentReportAction, + isChatReportArchived: undefined, + currentUserAccountID: currentUserPersonalDetails.accountID, + shouldDeleteTransactionFromOnyx: undefined, + isMovingTransactionFromTrackExpense: undefined, + actionableWhisperReportActionID: undefined, + resolution: undefined, + shouldRemoveIOUTransaction: isReportArchived || undeletedTransaction?.transactionID === forceDeleteSplitTransactionID, + }); // getDeleteTrackExpenseInformation only handles deleting the transaction report thread, so we need to update the report preview action here if (originalReportPreviewAction) { diff --git a/src/libs/actions/IOU/TrackExpense.ts b/src/libs/actions/IOU/TrackExpense.ts index bef0c892f928..893526aff0ac 100644 --- a/src/libs/actions/IOU/TrackExpense.ts +++ b/src/libs/actions/IOU/TrackExpense.ts @@ -652,20 +652,31 @@ function buildOnyxDataForTrackExpense({ return onyxData; } -// eslint-disable-next-line @typescript-eslint/max-params -function getDeleteTrackExpenseInformation( - chatReport: OnyxEntry, - transactionID: string | undefined, - reportAction: OnyxTypes.ReportAction, - isChatReportArchived: boolean | undefined, - currentUserAccountID: number, +function getDeleteTrackExpenseInformation({ + chatReport, + transactionID, + reportAction, + isChatReportArchived, + currentUserAccountID, shouldDeleteTransactionFromOnyx = true, isMovingTransactionFromTrackExpense = false, actionableWhisperReportActionID = '', resolution = '', shouldRemoveIOUTransaction = true, - transactionThread?: OnyxEntry, -) { + transactionThread, +}: { + chatReport: OnyxEntry; + transactionID: string | undefined; + reportAction: OnyxTypes.ReportAction; + isChatReportArchived: boolean | undefined; + currentUserAccountID: number; + shouldDeleteTransactionFromOnyx?: boolean; + isMovingTransactionFromTrackExpense?: boolean; + actionableWhisperReportActionID?: string; + resolution?: string; + shouldRemoveIOUTransaction?: boolean; + transactionThread?: OnyxEntry; +}) { // STEP 1: Get all collections we're updating const transaction = getAllTransactions()?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; // TODO: https://github.com/Expensify/App/issues/66512 @@ -1243,18 +1254,18 @@ const getConvertTrackedExpenseInformation = ( optimisticData: deleteOptimisticData, successData: deleteSuccessData, failureData: deleteFailureData, - } = getDeleteTrackExpenseInformation( - getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${linkedTrackedExpenseReportID}`], + } = getDeleteTrackExpenseInformation({ + chatReport: getAllReports()?.[`${ONYXKEYS.COLLECTION.REPORT}${linkedTrackedExpenseReportID}`], transactionID, - linkedTrackedExpenseReportAction, - isLinkedTrackedExpenseReportArchived, + reportAction: linkedTrackedExpenseReportAction, + isChatReportArchived: isLinkedTrackedExpenseReportArchived, currentUserAccountID, - false, - true, + shouldDeleteTransactionFromOnyx: false, + isMovingTransactionFromTrackExpense: true, actionableWhisperReportActionID, resolution, - true, - ); + shouldRemoveIOUTransaction: true, + }); optimisticData?.push(...deleteOptimisticData); successData?.push(...deleteSuccessData); @@ -3027,18 +3038,18 @@ function deleteTrackExpense({ const whisperAction = getTrackExpenseActionableWhisper(transactionID, chatReportID, chatReportActions); const actionableWhisperReportActionID = whisperAction?.reportActionID; - const {parameters, optimisticData, successData, failureData} = getDeleteTrackExpenseInformation( + const {parameters, optimisticData, successData, failureData} = getDeleteTrackExpenseInformation({ chatReport, transactionID, reportAction, isChatReportArchived, currentUserAccountID, - undefined, - undefined, + shouldDeleteTransactionFromOnyx: undefined, + isMovingTransactionFromTrackExpense: undefined, actionableWhisperReportActionID, - CONST.REPORT.ACTIONABLE_TRACK_EXPENSE_WHISPER_RESOLUTION.NOTHING, - false, - ); + resolution: CONST.REPORT.ACTIONABLE_TRACK_EXPENSE_WHISPER_RESOLUTION.NOTHING, + shouldRemoveIOUTransaction: false, + }); // STEP 6: Make the API request API.write(WRITE_COMMANDS.DELETE_MONEY_REQUEST, parameters, {optimisticData, successData, failureData}); diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index 7d410ec601df..8b20e84d9189 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -648,19 +648,19 @@ function mergeTransactionRequest({ if (!sourceIouAction) { Log.warn("Can't find the iouAction for the transaction in the selfDM report."); } else { - const {optimisticData, successData, failureData} = getDeleteTrackExpenseInformation( - selfDMReport, - sourceTransaction.transactionID, - sourceIouAction, - false, - currentUserAccountIDParam, - undefined, - undefined, + const {optimisticData, successData, failureData} = getDeleteTrackExpenseInformation({ + chatReport: selfDMReport, + transactionID: sourceTransaction.transactionID, + reportAction: sourceIouAction, + isChatReportArchived: false, + currentUserAccountID: currentUserAccountIDParam, + shouldDeleteTransactionFromOnyx: undefined, + isMovingTransactionFromTrackExpense: undefined, actionableWhisperReportActionID, - CONST.REPORT.ACTIONABLE_TRACK_EXPENSE_WHISPER_RESOLUTION.NOTHING, - false, - sourceTransactionThreadReport, - ); + resolution: CONST.REPORT.ACTIONABLE_TRACK_EXPENSE_WHISPER_RESOLUTION.NOTHING, + shouldRemoveIOUTransaction: false, + transactionThread: sourceTransactionThreadReport, + }); sourceTransactionOptimisticData.push(...optimisticData); sourceTransactionSuccessData.push(...successData); diff --git a/tests/actions/IOUTest/TrackExpenseTest.ts b/tests/actions/IOUTest/TrackExpenseTest.ts index bc859a6afd44..4239a75bc4d4 100644 --- a/tests/actions/IOUTest/TrackExpenseTest.ts +++ b/tests/actions/IOUTest/TrackExpenseTest.ts @@ -2520,16 +2520,16 @@ describe('actions/IOU/TrackExpense', () => { expect(createIOUAction).toBeTruthy(); // When deleting expense - const {optimisticData, successData, shouldDeleteTransactionThread} = getDeleteTrackExpenseInformation( - selfDMReport, - transaction?.transactionID, + const {optimisticData, successData, shouldDeleteTransactionThread} = getDeleteTrackExpenseInformation({ + chatReport: selfDMReport, + transactionID: transaction?.transactionID, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - createIOUAction!, - false, - RORY_ACCOUNT_ID, - undefined, - undefined, - ); + reportAction: createIOUAction!, + isChatReportArchived: false, + currentUserAccountID: RORY_ACCOUNT_ID, + shouldDeleteTransactionFromOnyx: undefined, + isMovingTransactionFromTrackExpense: undefined, + }); await waitForBatchedUpdates(); // Then the transaction thread report should be ready to be deleted @@ -2589,16 +2589,16 @@ describe('actions/IOU/TrackExpense', () => { }); // When deleting expense - const {optimisticData, successData, shouldDeleteTransactionThread} = getDeleteTrackExpenseInformation( - selfDMReport, - transaction?.transactionID, + const {optimisticData, successData, shouldDeleteTransactionThread} = getDeleteTrackExpenseInformation({ + chatReport: selfDMReport, + transactionID: transaction?.transactionID, // eslint-disable-next-line @typescript-eslint/no-non-null-assertion - createIOUAction!, - false, - RORY_ACCOUNT_ID, - undefined, - true, - ); + reportAction: createIOUAction!, + isChatReportArchived: false, + currentUserAccountID: RORY_ACCOUNT_ID, + shouldDeleteTransactionFromOnyx: undefined, + isMovingTransactionFromTrackExpense: true, + }); await waitForBatchedUpdates(); // Then the transaction thread report should be ready to be deleted From d7cf7ce68ad2301eecd979988225bba74470e0c5 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 2 Sep 2026 19:46:37 +0530 Subject: [PATCH 12/27] fixes --- src/libs/actions/MergeTransaction.ts | 4 +++- src/pages/TransactionMerge/DynamicConfirmationPage.tsx | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index 8b20e84d9189..488b0ba941b1 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -420,6 +420,7 @@ type MergeTransactionRequestParams = { sourceIOUAction: OnyxEntry; getCurrencyDecimals: CurrencyListActionsContextType['getCurrencyDecimals']; getCurrencySymbol: CurrencyListActionsContextType['getCurrencySymbol']; + sourceIOUActionThreadReport: OnyxEntry; }; /** * Merges two transactions by updating the target transaction with selected fields and deleting the source transaction. @@ -455,6 +456,7 @@ function mergeTransactionRequest({ sourceIOUAction, getCurrencyDecimals, getCurrencySymbol, + sourceIOUActionThreadReport, }: MergeTransactionRequestParams) { // For both unreported expenses and expense reports, negate the display amount when storing // This preserves the user's chosen sign while following the storage convention @@ -626,7 +628,7 @@ function mergeTransactionRequest({ shouldDeleteTransactionThread, reportAction: sourceIOUAction, currentUserAccountID: currentUserAccountIDParam, - transactionThread: sourceTransactionThreadReport, + transactionThread: sourceIOUActionThreadReport, transactionThreadReportActionsParam: sourceTransactionThreadReportActions, }); optimisticSourceReportActionData.push(...cleanUpSourceTransactionThreadReportOnyxData.optimisticData); diff --git a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx index 1adaa02dd4bf..99079a7a2fad 100644 --- a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx +++ b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx @@ -94,6 +94,7 @@ function DynamicConfirmationPage({route}: DynamicConfirmationPageProps) { const [sourceReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(sourceTransaction?.reportID)}`); const sourceIOUAction = sourceTransaction ? getIOUActionForTransactionID(Object.values(sourceReportActions ?? {}), sourceTransaction.transactionID) : undefined; const [sourceTransactionThreadReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(sourceIOUAction?.childReportID)}`); + const [sourceIOUActionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceIOUAction?.childReportID)}`); // Build the merged transaction data for display const mergedTransactionData = buildMergedTransactionData(targetTransaction, mergeTransaction); @@ -137,6 +138,7 @@ function DynamicConfirmationPage({route}: DynamicConfirmationPageProps) { reportPolicyTags, sourceTransactionThreadReportActions, sourceIOUAction, + sourceIOUActionThreadReport, }); const reportIDToDismiss = reportID !== CONST.REPORT.UNREPORTED_REPORT_ID ? reportID : undefined; From 484f91a9dd79314f7c50c87889106bb2356061ef Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 2 Sep 2026 19:47:58 +0530 Subject: [PATCH 13/27] More cleanup --- src/libs/actions/MergeTransaction.ts | 2 -- src/pages/TransactionMerge/DynamicConfirmationPage.tsx | 3 --- 2 files changed, 5 deletions(-) diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index 488b0ba941b1..ea75ca4bdb3c 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -402,7 +402,6 @@ type MergeTransactionRequestParams = { allTransactionViolations: OnyxCollection; sourceTransaction: Transaction; targetTransactionThreadReport: OnyxEntry; - sourceTransactionThreadReport: OnyxEntry; targetTransactionThreadParentReport: OnyxEntry; iouReportOwnerLogin: string | undefined; policy: OnyxEntry; @@ -437,7 +436,6 @@ function mergeTransactionRequest({ targetTransaction, sourceTransaction, targetTransactionThreadReport, - sourceTransactionThreadReport, targetTransactionThreadParentReport, iouReportOwnerLogin, allTransactionViolations, diff --git a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx index 99079a7a2fad..b6e2f79f92ef 100644 --- a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx +++ b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx @@ -77,8 +77,6 @@ function DynamicConfirmationPage({route}: DynamicConfirmationPageProps) { // Reports opened from Search may not be in Onyx yet, so we also read the expenses from the Search snapshot. const {currentSearchResults} = useSearchResultsContext(); const [targetTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${targetTransactionThreadReportID}`); - const sourceTransactionThreadReportID = getTransactionThreadReportID(sourceTransaction); - const [sourceTransactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceTransactionThreadReportID)}`); const [targetTransactionThreadParentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(targetTransactionThreadReport?.parentReportID)}`); const [iouReportOwnerLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, { selector: personalDetailsLoginSelector(targetTransactionThreadParentReport?.ownerAccountID), @@ -121,7 +119,6 @@ function DynamicConfirmationPage({route}: DynamicConfirmationPageProps) { targetTransaction, sourceTransaction, targetTransactionThreadReport, - sourceTransactionThreadReport, targetTransactionThreadParentReport, iouReportOwnerLogin, allTransactionViolations, From e3f63b2fe2c1776ac6a74d89fefce00ff4f406e4 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 2 Sep 2026 19:51:49 +0530 Subject: [PATCH 14/27] fix tests --- src/libs/actions/MergeTransaction.ts | 2 +- tests/actions/IOUTest/DuplicateTest.ts | 1 + tests/actions/MergeTransactionTest.ts | 18 +++++++++--------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index ea75ca4bdb3c..f67fae690c8a 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -659,7 +659,7 @@ function mergeTransactionRequest({ actionableWhisperReportActionID, resolution: CONST.REPORT.ACTIONABLE_TRACK_EXPENSE_WHISPER_RESOLUTION.NOTHING, shouldRemoveIOUTransaction: false, - transactionThread: sourceTransactionThreadReport, + transactionThread: sourceIOUActionThreadReport, }); sourceTransactionOptimisticData.push(...optimisticData); diff --git a/tests/actions/IOUTest/DuplicateTest.ts b/tests/actions/IOUTest/DuplicateTest.ts index cd356e828f93..82342fc7a1f0 100644 --- a/tests/actions/IOUTest/DuplicateTest.ts +++ b/tests/actions/IOUTest/DuplicateTest.ts @@ -344,6 +344,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {action789: iouAction1}, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${childReportID}`]: passedInChildReportActions, }, + allReportsList: {}, }); await waitForBatchedUpdates(); diff --git a/tests/actions/MergeTransactionTest.ts b/tests/actions/MergeTransactionTest.ts index 0731dbf59e1b..d3db56cd927b 100644 --- a/tests/actions/MergeTransactionTest.ts +++ b/tests/actions/MergeTransactionTest.ts @@ -202,7 +202,7 @@ function runCrossReportMergeToSourceReportRequest(fixtures: CrossReportMergeToSo policyTags: undefined, policyCategories: undefined, allTransactionViolations: createAllTransactionViolations(targetTransaction.transactionID, sourceTransaction.transactionID, mockViolations, mockViolations), - sourceTransactionThreadReport: undefined, + sourceIOUActionThreadReport: undefined, targetTransactionThreadReport: {reportID: targetReport.reportID}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -320,7 +320,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, - sourceTransactionThreadReport: undefined, + sourceIOUActionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target-report-456'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -443,7 +443,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, - sourceTransactionThreadReport: undefined, + sourceIOUActionThreadReport: undefined, targetTransactionThreadReport: {reportID: targetReportID}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -549,7 +549,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, - sourceTransactionThreadReport: undefined, + sourceIOUActionThreadReport: undefined, targetTransactionThreadReport: {reportID: targetExpenseReport.reportID}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -705,7 +705,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, - sourceTransactionThreadReport: undefined, + sourceIOUActionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target-report-456'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -816,7 +816,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, - sourceTransactionThreadReport: undefined, + sourceIOUActionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target123'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -1050,7 +1050,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, - sourceTransactionThreadReport: undefined, + sourceIOUActionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target-report-456'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -1258,7 +1258,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, - sourceTransactionThreadReport: undefined, + sourceIOUActionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target-report-456'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, @@ -1415,7 +1415,7 @@ describe('mergeTransactionRequest', () => { mergeTransaction, targetTransaction, sourceTransaction, - sourceTransactionThreadReport: undefined, + sourceIOUActionThreadReport: undefined, targetTransactionThreadReport: {reportID: 'target-report-456'}, targetTransactionThreadParentReport: undefined, reportPolicyTags: undefined, From 051022bec77f77b4df5ee09282e3a9e4ccea7b76 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 20:14:27 +0530 Subject: [PATCH 15/27] fixes --- src/pages/TransactionMerge/DynamicConfirmationPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx index cde6722c03cf..40bf05ad3056 100644 --- a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx +++ b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx @@ -91,8 +91,11 @@ function DynamicConfirmationPage({route}: DynamicConfirmationPageProps) { const [sourceReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(sourceTransaction?.reportID)}`); const sourceIOUAction = sourceTransaction ? getIOUActionForTransactionID(Object.values(sourceReportActions ?? {}), sourceTransaction.transactionID) : undefined; + const selfDMSourceIOUAction = + selfDMReport?.reportID && sourceTransaction ? getIOUActionForTransactionID(Object.values(selfDMReportActions ?? {}), sourceTransaction.transactionID) : undefined; const [sourceTransactionThreadReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(sourceIOUAction?.childReportID)}`); - const [sourceIOUActionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceIOUAction?.childReportID)}`); + const sourceThreadReportID = sourceIOUAction?.childReportID ?? selfDMSourceIOUAction?.childReportID; + const [sourceIOUActionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceThreadReportID)}`); // Build the merged transaction data for display const mergedTransactionData = buildMergedTransactionData(targetTransaction, mergeTransaction); From 9416703de753131c9b701bc9c081dfd0601ccc37 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 20:24:19 +0530 Subject: [PATCH 16/27] fix --- src/libs/actions/IOU/TrackExpense.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/actions/IOU/TrackExpense.ts b/src/libs/actions/IOU/TrackExpense.ts index 7dc9df71f294..b34d9d31c80e 100644 --- a/src/libs/actions/IOU/TrackExpense.ts +++ b/src/libs/actions/IOU/TrackExpense.ts @@ -680,6 +680,7 @@ function getDeleteTrackExpenseInformation({ actionableWhisperReportActionID = '', resolution = '', shouldRemoveIOUTransaction = true, + transactionThread, }: GetDeleteTrackExpenseInformationParams) { // STEP 1: Get all collections we're updating const transaction = getAllTransactions()?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; From 19c5766863011f96c57496edf827ca764bcc4fa6 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 20:40:58 +0530 Subject: [PATCH 17/27] fix tests --- tests/actions/IOUTest/DuplicateTest.ts | 117 ++++++++++++++++++++++++- 1 file changed, 113 insertions(+), 4 deletions(-) diff --git a/tests/actions/IOUTest/DuplicateTest.ts b/tests/actions/IOUTest/DuplicateTest.ts index 34eee9e98647..273187cea7b4 100644 --- a/tests/actions/IOUTest/DuplicateTest.ts +++ b/tests/actions/IOUTest/DuplicateTest.ts @@ -344,7 +344,9 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {action789: iouAction1}, [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${childReportID}`]: passedInChildReportActions, }, - allReportsList: {}, + allReportsList: { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: expenseReport, + }, }); await waitForBatchedUpdates(); @@ -364,6 +366,100 @@ describe('actions/Duplicate', () => { ); }); + it('threads allReportsList into the transaction-thread cleanup instead of relying on the deprecated reports cache', async () => { + // Given: A duplicate whose expense report and transaction thread are already cached in Onyx + const reportID = 'reportReports'; + const mainTransactionID = 'mainReports'; + const duplicate1ID = 'dupReports'; + const childReportID = 'childReports'; + + const mainTransaction = createMockTransaction(mainTransactionID, reportID); + const duplicateTransaction1 = createMockTransaction(duplicate1ID, reportID, 100); + const mainViolations = createMockViolations(); + const duplicate1Violations = createMockViolations(); + + const iouAction1 = createMockIouAction(duplicate1ID, 'action789', childReportID); + + // The values already cached via the deprecated Onyx.connect-backed reports accessor — + // deliberately different from what's passed via allReportsList below. + const cachedExpenseReport = createMockReport(reportID, 500); + const cachedThreadReport = createMockReport(childReportID, 999); + + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${mainTransactionID}`, mainTransaction); + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${duplicate1ID}`, duplicateTransaction1); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, cachedExpenseReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${childReportID}`, cachedThreadReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`, mainViolations); + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`, duplicate1Violations); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {action789: iouAction1}); + await waitForBatchedUpdates(); + + // The reports explicitly passed in, distinct from the cached ones above. + const passedExpenseReport = createMockReport(reportID, 200); + const passedThreadReport = createMockReport(childReportID, 42); + + // When: Call mergeDuplicates, passing allReportsList with DIFFERENT values for the source and child reports + mergeDuplicates({ + transactionID: mainTransactionID, + transactionIDList: [duplicate1ID], + created: '2024-01-01 12:00:00', + merchant: 'Updated Merchant', + amount: 200, + currency: CONST.CURRENCY.EUR, + category: 'Travel', + comment: 'Updated comment', + billable: true, + reimbursable: false, + tag: 'UpdatedProject', + taxCode: '', + receiptID: 123, + reportID, + currentUserLogin: RORY_EMAIL, + currentUserAccountID: RORY_ACCOUNT_ID, + allTransactionViolations: { + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations, + [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, + }, + allReportsList: { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: passedExpenseReport, + [`${ONYXKEYS.COLLECTION.REPORT}${childReportID}`]: passedThreadReport, + }, + allReportActionsList: { + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {action789: iouAction1}, + [`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${childReportID}`]: {}, + }, + }); + await waitForBatchedUpdates(); + + // Then: The source-report total math and the transaction-thread rollback data use the PASSED-IN reports, + // not the stale cached ones — proving mergeDuplicates used the explicit allReportsList slice instead of + // the deprecated reports cache. + expect(writeSpy).toHaveBeenCalledWith( + WRITE_COMMANDS.MERGE_DUPLICATES, + expect.anything(), + expect.objectContaining({ + optimisticData: expect.arrayContaining([ + expect.objectContaining({ + key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, + value: expect.objectContaining({total: passedExpenseReport.total - duplicateTransaction1.amount}), + }), + ]), + }), + ); + expect(writeSpy).toHaveBeenCalledWith( + WRITE_COMMANDS.MERGE_DUPLICATES, + expect.anything(), + expect.objectContaining({ + failureData: expect.arrayContaining([ + expect.objectContaining({ + key: `${ONYXKEYS.COLLECTION.REPORT}${childReportID}`, + value: passedThreadReport, + }), + ]), + }), + ); + }); + it('should handle empty duplicate transaction list', async () => { // Given: Set up test data with only main transaction const reportID = 'report123'; @@ -399,7 +495,9 @@ describe('actions/Duplicate', () => { currentUserLogin: RORY_EMAIL, currentUserAccountID: RORY_ACCOUNT_ID, allTransactionViolations: {[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: []}, - allReportsList: {}, + allReportsList: { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: expenseReport, + }, allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {}}, }); await waitForBatchedUpdates(); @@ -674,6 +772,15 @@ describe('actions/Duplicate', () => { reportID, }; + // Mirror what the page passes: the full REPORT collection from useOnyx(ONYXKEYS.COLLECTION.REPORT), + // including the transaction thread reports created optimistically above. + const allReportsList = { + [`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`]: chatReport, + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: expenseReport, + [`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport1.reportID}`]: transactionThreadReport1, + [`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport2.reportID}`]: transactionThreadReport2, + }; + // When: Call mergeDuplicates mergeDuplicates({ ...mergeParams, @@ -684,7 +791,7 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate2ID}`]: duplicate2Violations, }, - allReportsList: {}, + allReportsList, allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {[iouAction1ID]: iouAction1, [iouAction2ID]: iouAction2}}, }); await waitForBatchedUpdates(); @@ -792,7 +899,9 @@ describe('actions/Duplicate', () => { [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${mainTransactionID}`]: mainViolations, [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicate1ID}`]: duplicate1Violations, }, - allReportsList: {}, + allReportsList: { + [`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]: expenseReport, + }, allReportActionsList: {[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`]: {mainAction123: mainIouAction, action456: dupIouAction}}, }); await waitForBatchedUpdates(); From ac766ba5ccb633861185085aba7b0203bd787e2a Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 20:55:15 +0530 Subject: [PATCH 18/27] fix tests --- tests/actions/IOUTest/DuplicateTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/actions/IOUTest/DuplicateTest.ts b/tests/actions/IOUTest/DuplicateTest.ts index 273187cea7b4..b8eb175d945e 100644 --- a/tests/actions/IOUTest/DuplicateTest.ts +++ b/tests/actions/IOUTest/DuplicateTest.ts @@ -441,7 +441,7 @@ describe('actions/Duplicate', () => { optimisticData: expect.arrayContaining([ expect.objectContaining({ key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: expect.objectContaining({total: passedExpenseReport.total - duplicateTransaction1.amount}), + value: expect.objectContaining({total: passedExpenseReport?.total ?? 0 - duplicateTransaction1.amount}), }), ]), }), From d8f7a9362ad7eb7759c84f5182724c48273797e1 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 20:58:31 +0530 Subject: [PATCH 19/27] fix tests --- tests/actions/IOUTest/DuplicateTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/actions/IOUTest/DuplicateTest.ts b/tests/actions/IOUTest/DuplicateTest.ts index b8eb175d945e..4cbc930796aa 100644 --- a/tests/actions/IOUTest/DuplicateTest.ts +++ b/tests/actions/IOUTest/DuplicateTest.ts @@ -441,7 +441,7 @@ describe('actions/Duplicate', () => { optimisticData: expect.arrayContaining([ expect.objectContaining({ key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: expect.objectContaining({total: passedExpenseReport?.total ?? 0 - duplicateTransaction1.amount}), + value: passedExpenseReport, }), ]), }), From 718e0f498178b85fad9c0bfd3d8cbedff81e3dee Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 21:59:10 +0530 Subject: [PATCH 20/27] fix tests --- tests/actions/IOUTest/DuplicateTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/actions/IOUTest/DuplicateTest.ts b/tests/actions/IOUTest/DuplicateTest.ts index 4cbc930796aa..d068fd6a1a8c 100644 --- a/tests/actions/IOUTest/DuplicateTest.ts +++ b/tests/actions/IOUTest/DuplicateTest.ts @@ -441,7 +441,7 @@ describe('actions/Duplicate', () => { optimisticData: expect.arrayContaining([ expect.objectContaining({ key: `${ONYXKEYS.COLLECTION.REPORT}${reportID}`, - value: passedExpenseReport, + value: expect.objectContaining({total: (passedExpenseReport.total ?? 0) - duplicateTransaction1.amount}), }), ]), }), From a966dcf627f4089843220920a6061346a27ecd50 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 22:11:38 +0530 Subject: [PATCH 21/27] Refactor deleteMoneyRequest function to pass param --- src/libs/actions/IOU/DeleteMoneyRequest.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/actions/IOU/DeleteMoneyRequest.ts b/src/libs/actions/IOU/DeleteMoneyRequest.ts index e0e06a264ec1..cc2355c28e02 100644 --- a/src/libs/actions/IOU/DeleteMoneyRequest.ts +++ b/src/libs/actions/IOU/DeleteMoneyRequest.ts @@ -949,6 +949,9 @@ function deleteMoneyRequest({ reportAction, isChatIOUReportArchived, currentUserAccountID, + transactionThread: transactionThreadReport, + iouReport, + chatReport, transactionThreadReportActionsParam: transactionThreadReportActions, }); optimisticData.push(...cleanUpTransactionThreadReportOnyxData.optimisticData); From c43a81314d1b73f1d2052d3082f0a4003f77c9dc Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 22:22:53 +0530 Subject: [PATCH 22/27] pass iou and chat report to mergeTransactionRequest for source action --- src/libs/actions/MergeTransaction.ts | 6 ++++++ src/pages/TransactionMerge/DynamicConfirmationPage.tsx | 8 ++++++-- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/MergeTransaction.ts b/src/libs/actions/MergeTransaction.ts index db5180b821ea..0fc08034e808 100644 --- a/src/libs/actions/MergeTransaction.ts +++ b/src/libs/actions/MergeTransaction.ts @@ -421,6 +421,8 @@ type MergeTransactionRequestParams = { getCurrencyDecimals: CurrencyListActionsContextType['getCurrencyDecimals']; getCurrencySymbol: CurrencyListActionsContextType['getCurrencySymbol']; sourceIOUActionThreadReport: OnyxEntry; + sourceActionIOUReport: OnyxEntry; + sourceActionChatReport: OnyxEntry; }; /** * Merges two transactions by updating the target transaction with selected fields and deleting the source transaction. @@ -456,6 +458,8 @@ function mergeTransactionRequest({ getCurrencyDecimals, getCurrencySymbol, sourceIOUActionThreadReport, + sourceActionIOUReport, + sourceActionChatReport, }: MergeTransactionRequestParams) { // For both unreported expenses and expense reports, negate the display amount when storing // This preserves the user's chosen sign while following the storage convention @@ -627,6 +631,8 @@ function mergeTransactionRequest({ shouldDeleteTransactionThread, reportAction: sourceIOUAction, currentUserAccountID: currentUserAccountIDParam, + iouReport: sourceActionIOUReport, + chatReport: sourceActionChatReport, transactionThread: sourceIOUActionThreadReport, transactionThreadReportActionsParam: sourceTransactionThreadReportActions, }); diff --git a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx index 40bf05ad3056..9265bbbcbf11 100644 --- a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx +++ b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx @@ -28,7 +28,7 @@ import isSearchTopmostFullScreenRoute from '@libs/Navigation/helpers/isSearchTop import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {MergeTransactionNavigatorParamList} from '@libs/Navigation/types'; -import {getFilteredReportActionsForReportView, getIOUActionForTransactionID} from '@libs/ReportActionsUtils'; +import {getFilteredReportActionsForReportView, getIOUActionForTransactionID, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {findSelfDMReportID} from '@libs/ReportUtils'; import CONST from '@src/CONST'; @@ -96,7 +96,9 @@ function DynamicConfirmationPage({route}: DynamicConfirmationPageProps) { const [sourceTransactionThreadReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${getNonEmptyStringOnyxID(sourceIOUAction?.childReportID)}`); const sourceThreadReportID = sourceIOUAction?.childReportID ?? selfDMSourceIOUAction?.childReportID; const [sourceIOUActionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceThreadReportID)}`); - + const sourceIOUReportID = isMoneyRequestAction(sourceIOUAction) ? sourceIOUAction?.reportID : undefined; + const sourceActionIOUReport = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceIOUReportID)}`); + const sourceActionChatReport = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceActionIOUReport?.chatReportID)}`); // Build the merged transaction data for display const mergedTransactionData = buildMergedTransactionData(targetTransaction, mergeTransaction); @@ -139,6 +141,8 @@ function DynamicConfirmationPage({route}: DynamicConfirmationPageProps) { sourceTransactionThreadReportActions, sourceIOUAction, sourceIOUActionThreadReport, + sourceActionIOUReport, + sourceActionChatReport, }); const reportIDToDismiss = reportID !== CONST.REPORT.UNREPORTED_REPORT_ID ? reportID : undefined; From 87a6c7fa6f91968617d0e2ec94df6eb9e1c47149 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 22:25:39 +0530 Subject: [PATCH 23/27] fix type --- src/pages/TransactionMerge/DynamicConfirmationPage.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx index 9265bbbcbf11..f6e7628e53ea 100644 --- a/src/pages/TransactionMerge/DynamicConfirmationPage.tsx +++ b/src/pages/TransactionMerge/DynamicConfirmationPage.tsx @@ -97,8 +97,8 @@ function DynamicConfirmationPage({route}: DynamicConfirmationPageProps) { const sourceThreadReportID = sourceIOUAction?.childReportID ?? selfDMSourceIOUAction?.childReportID; const [sourceIOUActionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceThreadReportID)}`); const sourceIOUReportID = isMoneyRequestAction(sourceIOUAction) ? sourceIOUAction?.reportID : undefined; - const sourceActionIOUReport = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceIOUReportID)}`); - const sourceActionChatReport = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceActionIOUReport?.chatReportID)}`); + const [sourceActionIOUReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceIOUReportID)}`); + const [sourceActionChatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(sourceActionIOUReport?.chatReportID)}`); // Build the merged transaction data for display const mergedTransactionData = buildMergedTransactionData(targetTransaction, mergeTransaction); From 74f05594b0538a9118506a5b947c4d5db1bd5414 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 22:44:17 +0530 Subject: [PATCH 24/27] Pass iOUReport and ChatReport to `getCleanUpTransactionThreadReportOnyxData` --- src/libs/actions/IOU/DeleteMoneyRequest.ts | 25 +++++++++++-------- .../actions/IOU/SplitTransactionUpdate.ts | 8 ++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/IOU/DeleteMoneyRequest.ts b/src/libs/actions/IOU/DeleteMoneyRequest.ts index cc2355c28e02..df968340b9bc 100644 --- a/src/libs/actions/IOU/DeleteMoneyRequest.ts +++ b/src/libs/actions/IOU/DeleteMoneyRequest.ts @@ -621,23 +621,30 @@ function getCleanUpTransactionThreadReportOnyxData({ shouldAddUpdatedReportPreviewActionToOnyxData = true, currentUserAccountID, transactionThread: transactionThreadParam, - iouReport: iouReportParam, - chatReport: chatReportParam, + iouReport, + chatReport, transactionThreadReportActionsParam, }: { transactionThreadID?: string; shouldDeleteTransactionThread: boolean; - reportAction?: ReportAction; isChatIOUReportArchived?: boolean; updatedReportPreviewAction?: ReportAction; shouldAddUpdatedReportPreviewActionToOnyxData?: boolean; currentUserAccountID: number; transactionThread?: OnyxEntry; - iouReport?: OnyxEntry; - chatReport?: OnyxEntry; transactionThreadReportActionsParam?: OnyxEntry; -}) { - const allReports = getAllReports(); +} & ( + | { + reportAction: ReportAction; + iouReport: OnyxEntry; + chatReport: OnyxEntry; + } + | { + reportAction?: undefined; + iouReport?: OnyxEntry; + chatReport?: OnyxEntry; + } +)) { const allReportNameValuePairs = getAllReportNameValuePairs(); const optimisticData: Array> = []; @@ -648,7 +655,7 @@ function getCleanUpTransactionThreadReportOnyxData({ let transactionThread = null; let transactionThreadReportActions = null; if (transactionThreadID) { - transactionThread = transactionThreadParam ?? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadID}`] ?? null; + transactionThread = transactionThreadParam ?? null; transactionThreadReportActions = transactionThreadReportActionsParam ?? null; } @@ -698,8 +705,6 @@ function getCleanUpTransactionThreadReportOnyxData({ // Update the child comment visible count for reportPreviewAction. const iouReportID = isMoneyRequestAction(reportAction) ? reportAction?.reportID : undefined; - const iouReport = iouReportParam ?? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`]; - const chatReport = chatReportParam ?? allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${iouReport?.chatReportID}`]; const originalReportPreviewAction = getReportPreviewReportAction(chatReport?.reportID, iouReport?.reportID) ?? undefined; let reportPreviewAction = updatedReportPreviewAction ?? originalReportPreviewAction; if ( diff --git a/src/libs/actions/IOU/SplitTransactionUpdate.ts b/src/libs/actions/IOU/SplitTransactionUpdate.ts index 6c01d851deca..448261da7329 100644 --- a/src/libs/actions/IOU/SplitTransactionUpdate.ts +++ b/src/libs/actions/IOU/SplitTransactionUpdate.ts @@ -1419,12 +1419,15 @@ function updateSplitTransactions({ // getDeleteTrackExpenseInformation only handles deleting the transaction report thread, so we need to update the report preview action here if (originalReportPreviewAction) { + const currentActionIOUReport = allReportsList?.[`${ONYXKEYS.COLLECTION.REPORT}${currentReportAction?.reportID}`]; const cleanUpTransactionThreadReportOnyxData = getCleanUpTransactionThreadReportOnyxData({ shouldDeleteTransactionThread: false, reportAction: currentReportAction, updatedReportPreviewAction: (updatedReportPreviewAction ?? originalReportPreviewAction) as OnyxTypes.ReportAction, shouldAddUpdatedReportPreviewActionToOnyxData: false, currentUserAccountID: currentUserPersonalDetails.accountID, + iouReport: currentActionIOUReport, + chatReport: allReportsList?.[`${ONYXKEYS.COLLECTION.REPORT}${currentActionIOUReport?.chatReportID}`], // shouldDeleteTransactionThread is false, so the transaction-thread report actions are never read here. transactionThreadReportActionsParam: undefined, }); @@ -1670,6 +1673,8 @@ function updateSplitTransactions({ }, }), }; + const iouActionIOUReportID = isMoneyRequestAction(iouActionToCleanUp) ? iouActionToCleanUp.reportID : undefined; + const iouActionIOUReport = allReportsList?.[`${ONYXKEYS.COLLECTION.REPORT}${iouActionIOUReportID}`]; const {optimisticData, successData, failureData} = getCleanUpTransactionThreadReportOnyxData({ transactionThreadID: iouActionToCleanUp.childReportID, @@ -1677,6 +1682,9 @@ function updateSplitTransactions({ reportAction: iouActionToCleanUp, updatedReportPreviewAction: updatedReportPreviewAction as OnyxTypes.ReportAction, currentUserAccountID: currentUserPersonalDetails.accountID, + transactionThread: allReportsList?.[`${ONYXKEYS.COLLECTION.REPORT}${iouActionToCleanUp.childReportID}`], + iouReport: iouActionIOUReport, + chatReport: allReportsList?.[`${ONYXKEYS.COLLECTION.REPORT}${iouActionIOUReport?.chatReportID}`], transactionThreadReportActionsParam: allReportActionsList?.[`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouActionToCleanUp.childReportID}`], }); From 75c7f50a15244951b17d45e1a5dfa6dad280e914 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 22:47:57 +0530 Subject: [PATCH 25/27] remove unused --- src/libs/actions/IOU/DeleteMoneyRequest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU/DeleteMoneyRequest.ts b/src/libs/actions/IOU/DeleteMoneyRequest.ts index df968340b9bc..4a081ae2f81a 100644 --- a/src/libs/actions/IOU/DeleteMoneyRequest.ts +++ b/src/libs/actions/IOU/DeleteMoneyRequest.ts @@ -42,7 +42,7 @@ import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxInputValue, OnyxUpdate} import cloneDeep from 'lodash/cloneDeep'; import Onyx from 'react-native-onyx'; -import {getAllReportActionsFromIOU, getAllReportNameValuePairs, getAllReports, getAllTransactions, getAllTransactionViolations} from '.'; +import {getAllReportActionsFromIOU, getAllReportNameValuePairs, getAllTransactions, getAllTransactionViolations} from '.'; import {getReportPreviewReportAction, maybeUpdateReportNameForFormulaTitle} from './MoneyRequestBuilder'; type PrepareToCleanUpMoneyRequestResult = { From c29e2ee458da50d0f86777b313cce1d8b68b7843 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 23:58:28 +0530 Subject: [PATCH 26/27] Refactor PutonHold functions --- src/libs/actions/IOU/Hold.ts | 24 ++++++++++++++++++---- src/pages/Search/SearchHoldReasonPage.tsx | 25 +++++++++++++++++++++++ src/pages/iou/DynamicHoldReasonPage.tsx | 3 +++ 3 files changed, 48 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU/Hold.ts b/src/libs/actions/IOU/Hold.ts index d2d1378cf4ab..c870e87794e5 100644 --- a/src/libs/actions/IOU/Hold.ts +++ b/src/libs/actions/IOU/Hold.ts @@ -53,6 +53,8 @@ function putOnHold( transactionID: string, comment: string, initialReportID: string | undefined, + initialReport: OnyxEntry, + transactionReport: OnyxEntry, isOffline: boolean, currentUserLogin: string, currentUserAccountID: number, @@ -62,7 +64,6 @@ function putOnHold( ancestors: Ancestor[] = [], ) { const allTransactions = getAllTransactions(); - const allReports = getAllReports(); const currentTime = DateUtils.getDBTime(); const reportID = initialReportID ?? generateReportID(); @@ -71,14 +72,14 @@ function putOnHold( const newViolation = {name: CONST.VIOLATIONS.HOLD, type: CONST.VIOLATION_TYPES.VIOLATION, showInReview: true}; const updatedViolations = [...(transactionViolations ?? []), newViolation]; const transaction = allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; - const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`]; + const iouReport = transactionReport; const iouAction = getIOUActionForReportID(transaction?.reportID, transactionID); let transactionThreadReport: OnyxTypes.Report; // If there is no existing transaction thread report, we should create one // This way we ensure every held request has a dedicated thread for comments if (initialReportID) { - transactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${initialReportID}`] ?? ({} as OnyxTypes.Report); + transactionThreadReport = initialReport ?? ({} as OnyxTypes.Report); } else { const moneyRequestReport = getReportOrDraftReport(transaction?.reportID); transactionThreadReport = buildTransactionThread(iouAction, moneyRequestReport, currentUserAccountID, undefined, reportID); @@ -343,6 +344,8 @@ function putOnHold( function putTransactionsOnHold( transactionsID: string[], + allReports: OnyxCollection, + transactionReports: Record>, comment: string, reportID: string, isOffline: boolean, @@ -356,7 +359,20 @@ function putTransactionsOnHold( for (const transactionID of transactionsID) { const {childReportID} = getIOUActionForReportID(reportID, transactionID) ?? {}; const transactionViolations = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; - putOnHold(transactionID, comment, childReportID, isOffline, currentUserLogin, currentUserAccountID, transactionViolations, isTrackIntentUser, delegateAccountID, ancestors); + putOnHold( + transactionID, + comment, + childReportID, + allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${childReportID}`], + transactionReports[transactionID], + isOffline, + currentUserLogin, + currentUserAccountID, + transactionViolations, + isTrackIntentUser, + delegateAccountID, + ancestors, + ); } } diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx index 1303de3f79b0..da8af3e3074d 100644 --- a/src/pages/Search/SearchHoldReasonPage.tsx +++ b/src/pages/Search/SearchHoldReasonPage.tsx @@ -9,6 +9,7 @@ import useDynamicBackPath from '@hooks/useDynamicBackPath'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useOnyx from '@hooks/useOnyx'; +import useTransactionsByID from '@hooks/useTransactionsByID'; import {clearErrorFields, clearErrors} from '@libs/actions/FormActions'; import {putOnHold, putTransactionsOnHold} from '@libs/actions/IOU/Hold'; @@ -24,6 +25,9 @@ import ONYXKEYS from '@src/ONYXKEYS'; import {DYNAMIC_ROUTES} from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import INPUT_IDS from '@src/types/form/MoneyRequestHoldReasonForm'; +import type {Report} from '@src/types/onyx'; + +import type {OnyxEntry} from 'react-native-onyx'; import {isTrackIntentUserSelector} from '@selectors/Onboarding'; import {transactionViolationsByIDsSelector} from '@selectors/TransactionViolations'; @@ -44,9 +48,23 @@ function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) { const {accountID: currentUserAccountID, login: currentUserLogin} = useCurrentUserPersonalDetails(); const delegateAccountID = useDelegateAccountID(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`); + const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); const relevantTransactionIDs = useMemo(() => (isBulkHold ? selectedTransactionIDs : Object.keys(selectedTransactions)), [isBulkHold, selectedTransactionIDs, selectedTransactions]); const [selectedTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {selector: transactionViolationsByIDsSelector(relevantTransactionIDs)}); + // selectedTransactionIDs can be different from selectedtransactions so we need to use data from onyx + const [selectedTransactionsOnyx] = useTransactionsByID(selectedTransactionIDs); + const selectedTransactionReports = selectedTransactionsOnyx?.reduce( + (reportCollection, selectedTransaction) => { + if (!selectedTransaction.transactionID) { + return reportCollection; + } + // eslint-disable-next-line no-param-reassign + reportCollection[selectedTransaction.transactionID] = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${selectedTransaction?.reportID}`]; + return reportCollection; + }, + {} as Record>, + ); const {isOffline} = useNetwork(); const [isTrackIntentUser] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, { selector: isTrackIntentUserSelector, @@ -67,6 +85,8 @@ function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) { if (isBulkHold) { putTransactionsOnHold( selectedTransactionIDs, + allReports, + selectedTransactionReports, comment, reportID, isOffline, @@ -82,11 +102,14 @@ function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) { const transactionIDs = Object.keys(selectedTransactions); for (const transactionID of transactionIDs) { const transactionThreadReportID = selectedTransactions[transactionID].reportAction?.childReportID; + const transactionReportID = selectedTransactions[transactionID].transaction?.reportID; const transactionViolations = selectedTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; putOnHold( transactionID, comment, transactionThreadReportID, + allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`], + allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionReportID}`], isOffline, currentUserLogin ?? '', currentUserAccountID, @@ -116,6 +139,8 @@ function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) { selectedTransactionViolations, isTrackIntentUser, delegateAccountID, + allReports, + selectedTransactionReports, ], ); diff --git a/src/pages/iou/DynamicHoldReasonPage.tsx b/src/pages/iou/DynamicHoldReasonPage.tsx index 95a803eae7cc..0865b088e390 100644 --- a/src/pages/iou/DynamicHoldReasonPage.tsx +++ b/src/pages/iou/DynamicHoldReasonPage.tsx @@ -45,6 +45,7 @@ function DynamicHoldReasonPage({route}: DynamicHoldReasonPageProps) { const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${holdReportID}`); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); + const [transactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); const {isOffline} = useNetwork(); const ancestors = useAncestors(report); @@ -81,6 +82,8 @@ function DynamicHoldReasonPage({route}: DynamicHoldReasonPageProps) { transactionID, values.comment, holdReportID, + report, + transactionReport, isOffline, currentUserLogin ?? '', currentUserAccountID, From 7de7b102ee25082b5d3246958f3db0a9686cbf1c Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 6 Sep 2026 23:59:15 +0530 Subject: [PATCH 27/27] Refactor function to object type --- src/libs/actions/IOU/Hold.ts | 92 +++++++++++++++-------- src/pages/Search/SearchHoldReasonPage.tsx | 24 +++--- src/pages/iou/DynamicHoldReasonPage.tsx | 12 +-- 3 files changed, 77 insertions(+), 51 deletions(-) diff --git a/src/libs/actions/IOU/Hold.ts b/src/libs/actions/IOU/Hold.ts index c870e87794e5..3a8fc72dd0e0 100644 --- a/src/libs/actions/IOU/Hold.ts +++ b/src/libs/actions/IOU/Hold.ts @@ -49,20 +49,33 @@ import {getAllReports, getAllTransactions} from '.'; /** * Put expense on HOLD */ -function putOnHold( - transactionID: string, - comment: string, - initialReportID: string | undefined, - initialReport: OnyxEntry, - transactionReport: OnyxEntry, - isOffline: boolean, - currentUserLogin: string, - currentUserAccountID: number, - transactionViolations: OnyxEntry, - isTrackIntentUser: boolean | undefined, - delegateAccountID: number | undefined, - ancestors: Ancestor[] = [], -) { +function putOnHold({ + transactionID, + comment, + initialReportID, + initialReport, + transactionReport, + isOffline, + currentUserLogin, + currentUserAccountID, + transactionViolations, + isTrackIntentUser, + delegateAccountID, + ancestors = [], +}: { + transactionID: string; + comment: string; + initialReportID: string | undefined; + initialReport: OnyxEntry; + transactionReport: OnyxEntry; + isOffline: boolean; + currentUserLogin: string; + currentUserAccountID: number; + transactionViolations: OnyxEntry; + isTrackIntentUser: boolean | undefined; + delegateAccountID: number | undefined; + ancestors?: Ancestor[]; +}) { const allTransactions = getAllTransactions(); const currentTime = DateUtils.getDBTime(); @@ -342,29 +355,42 @@ function putOnHold( Navigation.setNavigationActionToMicrotaskQueue(() => notifyNewAction(currentReportID, undefined, true)); } -function putTransactionsOnHold( - transactionsID: string[], - allReports: OnyxCollection, - transactionReports: Record>, - comment: string, - reportID: string, - isOffline: boolean, - currentUserLogin: string, - currentUserAccountID: number, - allTransactionViolations: OnyxCollection, - isTrackIntentUser: boolean | undefined, - delegateAccountID: number | undefined, - ancestors: Ancestor[] = [], -) { +function putTransactionsOnHold({ + transactionsID, + allReports, + transactionReports, + comment, + reportID, + isOffline, + currentUserLogin, + currentUserAccountID, + allTransactionViolations, + isTrackIntentUser, + delegateAccountID, + ancestors = [], +}: { + transactionsID: string[]; + allReports: OnyxCollection; + transactionReports: Record>; + comment: string; + reportID: string; + isOffline: boolean; + currentUserLogin: string; + currentUserAccountID: number; + allTransactionViolations: OnyxCollection; + isTrackIntentUser: boolean | undefined; + delegateAccountID: number | undefined; + ancestors?: Ancestor[]; +}) { for (const transactionID of transactionsID) { const {childReportID} = getIOUActionForReportID(reportID, transactionID) ?? {}; const transactionViolations = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; - putOnHold( + putOnHold({ transactionID, comment, - childReportID, - allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${childReportID}`], - transactionReports[transactionID], + initialReportID: childReportID, + initialReport: allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${childReportID}`], + transactionReport: transactionReports[transactionID], isOffline, currentUserLogin, currentUserAccountID, @@ -372,7 +398,7 @@ function putTransactionsOnHold( isTrackIntentUser, delegateAccountID, ancestors, - ); + }); } } diff --git a/src/pages/Search/SearchHoldReasonPage.tsx b/src/pages/Search/SearchHoldReasonPage.tsx index da8af3e3074d..fd41d82a5ab8 100644 --- a/src/pages/Search/SearchHoldReasonPage.tsx +++ b/src/pages/Search/SearchHoldReasonPage.tsx @@ -83,20 +83,20 @@ function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) { return; } if (isBulkHold) { - putTransactionsOnHold( - selectedTransactionIDs, + putTransactionsOnHold({ + transactionsID: selectedTransactionIDs, allReports, - selectedTransactionReports, + transactionReports: selectedTransactionReports, comment, reportID, isOffline, - currentUserLogin ?? '', + currentUserLogin: currentUserLogin ?? '', currentUserAccountID, - selectedTransactionViolations, + allTransactionViolations: selectedTransactionViolations, isTrackIntentUser, delegateAccountID, ancestors, - ); + }); clearSelectedTransactions(true); } else { const transactionIDs = Object.keys(selectedTransactions); @@ -104,20 +104,20 @@ function SearchHoldReasonPage({route}: SearchHoldReasonPageProps) { const transactionThreadReportID = selectedTransactions[transactionID].reportAction?.childReportID; const transactionReportID = selectedTransactions[transactionID].transaction?.reportID; const transactionViolations = selectedTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]; - putOnHold( + putOnHold({ transactionID, comment, - transactionThreadReportID, - allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`], - allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionReportID}`], + initialReportID: transactionThreadReportID, + initialReport: allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`], + transactionReport: allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionReportID}`], isOffline, - currentUserLogin ?? '', + currentUserLogin: currentUserLogin ?? '', currentUserAccountID, transactionViolations, isTrackIntentUser, delegateAccountID, ancestors, - ); + }); } clearSelectedTransactions(); } diff --git a/src/pages/iou/DynamicHoldReasonPage.tsx b/src/pages/iou/DynamicHoldReasonPage.tsx index 0865b088e390..1b70e2a84e1b 100644 --- a/src/pages/iou/DynamicHoldReasonPage.tsx +++ b/src/pages/iou/DynamicHoldReasonPage.tsx @@ -78,20 +78,20 @@ function DynamicHoldReasonPage({route}: DynamicHoldReasonPageProps) { return; } - putOnHold( + putOnHold({ transactionID, - values.comment, - holdReportID, - report, + comment: values.comment, + initialReportID: holdReportID, + initialReport: report, transactionReport, isOffline, - currentUserLogin ?? '', + currentUserLogin: currentUserLogin ?? '', currentUserAccountID, transactionViolations, isTrackIntentUser, delegateAccountID, ancestors, - ); + }); Navigation.goBack(backPath); };