diff --git a/src/hooks/useSearchBulkActions.ts b/src/hooks/useSearchBulkActions.ts index 87b9dd722ee8..defa1ff531e4 100644 --- a/src/hooks/useSearchBulkActions.ts +++ b/src/hooks/useSearchBulkActions.ts @@ -269,7 +269,7 @@ function shouldShowBulkDuplicateOption({ } const reportID = selectedTransactions[id]?.reportID; - const submitterReport = reportID ? getReportOrDraftReport(reportID, searchReports) : undefined; + const submitterReport = reportID ? getReportOrDraftReport(reportID, searchReports, undefined, undefined, allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]) : undefined; if (submitterReport && !isCurrentUserSubmitter(submitterReport)) { return false; } @@ -1169,10 +1169,10 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { return ( selectedTransactionReportIDs.length > 0 && selectedTransactionReportIDs.every((id) => { - return isCurrentUserSubmitter(getReportOrDraftReport(id, reports)); + return isCurrentUserSubmitter(getReportOrDraftReport(id, reports, undefined, undefined, allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${id}`])); }) ); - }, [selectedTransactionReportIDs, currentUserPersonalDetails?.accountID, currentSearchResults?.data]); + }, [selectedTransactionReportIDs, currentUserPersonalDetails?.accountID, currentSearchResults?.data, allReports]); const duplicateHandlerRef = useRef<() => void>(() => {}); const setDuplicateHandler = useCallback((handler: () => void) => { @@ -1820,7 +1820,9 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { if (!transactionEntry) { continue; } - const ownerAccountID = transactionEntry.ownerAccountID ?? getReportOrDraftReport(transactionEntry.reportID)?.ownerAccountID; + const ownerAccountID = + transactionEntry.ownerAccountID ?? + getReportOrDraftReport(transactionEntry.reportID, undefined, undefined, undefined, allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionEntry.reportID}`])?.ownerAccountID; if (typeof ownerAccountID === 'number') { ownerAccountIDs.add(ownerAccountID); if (ownerAccountIDs.size > 1) { diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index a584d4d530ae..73ba73b70ac7 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -145,7 +145,7 @@ function useSelectedTransactionsActions({ continue; } - const parentReport = getReportOrDraftReport(reportID); + const parentReport = getReportOrDraftReport(reportID, undefined, undefined, undefined, allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]); const ownerAccountID = parentReport?.ownerAccountID; if (typeof ownerAccountID === 'number') { diff --git a/src/libs/ReportNameUtils.ts b/src/libs/ReportNameUtils.ts index 8c2d3bf048a5..434471d0b826 100644 --- a/src/libs/ReportNameUtils.ts +++ b/src/libs/ReportNameUtils.ts @@ -860,10 +860,12 @@ function computeChatThreadReportName( if (transactions) { linkedTransactions = linkedTransaction ? [linkedTransaction] : []; } + const linkedTransactionReport = linkedTransaction?.reportID ? reports?.[`${ONYXKEYS.COLLECTION.REPORT}${linkedTransaction.reportID}`] : undefined; let formattedName = getTransactionReportName({ translate, reportAction: parentReportAction, transactions: linkedTransactions, + report: linkedTransactionReport, }); if (isArchivedNonExpense) { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index ab1aca3a4e5e..ce23dd77fd4c 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -5298,12 +5298,12 @@ function getTransactionReportName({ translate, reportAction, transactions, - reports, + report, }: { translate: LocalizedTranslate; reportAction: OnyxEntry; transactions?: Transaction[]; - reports?: Report[]; + report: OnyxEntry; }): string { if (reportAction && isReversedTransaction(reportAction)) { return translate('parentReportAction.reversedTransaction'); @@ -5324,7 +5324,6 @@ function getTransactionReportName({ return translate('iou.receiptScanning', {count: 1}); } - const report = getReportOrDraftReport(transaction?.reportID, reports); if (hasMissingSmartscanFieldsTransactionUtils(transaction, report)) { return translate('iou.receiptMissingDetails'); } diff --git a/src/pages/TransactionMerge/MergeTransactionsListContent.tsx b/src/pages/TransactionMerge/MergeTransactionsListContent.tsx index 8e9831a406ae..5228c5522009 100644 --- a/src/pages/TransactionMerge/MergeTransactionsListContent.tsx +++ b/src/pages/TransactionMerge/MergeTransactionsListContent.tsx @@ -17,7 +17,7 @@ import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import {getTransactionsForMerging, setupMergeTransactionData, setupMergeTransactionDataAndNavigate} from '@libs/actions/MergeTransaction'; import {fillMissingReceiptSource} from '@libs/MergeTransactionUtils'; -import {getTransactionReportName, isIOUReport} from '@libs/ReportUtils'; +import {getReportOrDraftReport, getTransactionReportName, isIOUReport} from '@libs/ReportUtils'; import type {SkeletonSpanReasonAttributes} from '@libs/telemetry/useSkeletonSpan'; import tokenizedSearch from '@libs/tokenizedSearch'; import {getAmount, getCreated, getCurrency, getDescription, getMerchant, isExpenseUnreported} from '@libs/TransactionUtils'; @@ -131,7 +131,7 @@ function MergeTransactionsListContent({transactionID, mergeTransaction}: MergeTr translate, reportAction: undefined, transactions: [targetTransaction], - reports: targetTransactionReport ? [targetTransactionReport] : [], + report: getReportOrDraftReport(targetTransaction?.reportID, targetTransactionReport ? [targetTransactionReport] : [], undefined, undefined, targetTransactionReport), }) : ''; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index d05a62d8a9a1..0cd7699d0fd8 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -441,6 +441,7 @@ function IOURequestStepConfirmation({ const isMRReport = isMoneyRequestReport(report); const destinationReportID = backToReport ?? (isPerDiemRequest && isMRReport && Navigation.getTopmostReportId() !== report?.reportID ? report?.chatReportID : report?.reportID) ?? selfDMReportID; + const [destinationReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${destinationReportID}`); useEffect(() => { if (hasPreInsertFired.current || !isTransactionReady || !getIsNarrowLayout()) { @@ -470,7 +471,7 @@ function IOURequestStepConfirmation({ const hasValidDestination = !!destinationReportID && Navigation.getTopmostReportId() !== destinationReportID; // The report must be in Onyx so the pre-inserted screen can render immediately. - const isDestinationReportLoaded = !!destinationReportID && !!getReportOrDraftReport(destinationReportID)?.reportID; + const isDestinationReportLoaded = !!destinationReportID && !!getReportOrDraftReport(destinationReportID, undefined, undefined, undefined, destinationReport)?.reportID; const shouldPreInsertReport = canUseReportPreInsert && isOutsideRHP && hasValidDestination && isDestinationReportLoaded; diff --git a/src/pages/iou/request/step/confirmation/SubmitExpenseOrchestrator.tsx b/src/pages/iou/request/step/confirmation/SubmitExpenseOrchestrator.tsx index 70b225263689..db84fcb8bb4f 100644 --- a/src/pages/iou/request/step/confirmation/SubmitExpenseOrchestrator.tsx +++ b/src/pages/iou/request/step/confirmation/SubmitExpenseOrchestrator.tsx @@ -1,5 +1,6 @@ import React, {useEffect, useRef, useState} from 'react'; import LocationPermissionModal from '@components/LocationPermissionModal'; +import useOnyx from '@hooks/useOnyx'; import DateUtils from '@libs/DateUtils'; import {cancelDeferredWrite, flushDeferredWrite, reserveDeferredWriteChannel} from '@libs/deferredLayoutWrite'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; @@ -18,6 +19,7 @@ import {updateLastLocationPermissionPrompt} from '@userActions/IOU/MoneyRequest' import type {IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; +import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Receipt} from '@src/types/onyx/Transaction'; import {getSubmitHandler, SUBMIT_HANDLER} from './getSubmitHandler'; @@ -121,6 +123,7 @@ function SubmitExpenseOrchestrator({ isFromFloatingActionButtonOnTransaction, children, }: SubmitExpenseOrchestratorProps) { + const [destinationReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${destinationReportID}`); const [isConfirming, setIsConfirming] = useState(false); const [startLocationPermissionFlow, setStartLocationPermissionFlow] = useState(false); const confirmingSafetyTimeout = useRef | undefined>(undefined); @@ -178,7 +181,7 @@ function SubmitExpenseOrchestrator({ isReportInRHP: isReportOpenInRHP(rootState), isReportTopmostSplit: isReportTopmostSplitNavigator(), isSearchTopmostFullScreen: isSearchTopmostFullScreenRoute(), - isDestinationReportLoaded: !!destinationReportID && !!getReportOrDraftReport(destinationReportID)?.reportID, + isDestinationReportLoaded: !!destinationReportID && !!getReportOrDraftReport(destinationReportID, undefined, undefined, undefined, destinationReport)?.reportID, }; }; @@ -324,7 +327,7 @@ function SubmitExpenseOrchestrator({ setFastPath(CONST.TELEMETRY.FAST_PATH_HANDLER.REPORT_IN_RHP_DISMISS, CONST.TELEMETRY.SUBMIT_OPTIMIZATION.DISMISS_FIRST); const rootState = navigationRef.getRootState(); - const report = destinationReportID ? getReportOrDraftReport(destinationReportID) : undefined; + const report = destinationReportID ? getReportOrDraftReport(destinationReportID, undefined, undefined, undefined, destinationReport) : undefined; const isDestinationEmpty = !!report && isMoneyRequestReport(report) && !report.transactionCount; if (isDestinationEmpty) { reserveDeferredWriteChannel(CONST.DEFERRED_LAYOUT_WRITE_KEYS.DISMISS_MODAL, {destinationReportID}); diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 3e4a4ba15684..6c308af2ae2e 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -117,6 +117,7 @@ import { getTaskAssigneeChatOnyxData, getTitleFieldWithFallback, getTransactionDetails, + getTransactionReportName, getTransactionSortValue, getUnreportedTransactionMessage, getViolatingReportIDForRBRInLHN, @@ -12692,6 +12693,107 @@ describe('ReportUtils', () => { }); }); + describe('getTransactionReportName', () => { + const mockReportID = '100'; + const mockTransactionReport: Report = { + ...createRandomReport(100, undefined), + reportName: 'Transaction Report', + type: CONST.REPORT.TYPE.EXPENSE, + }; + + beforeEach(async () => { + await Onyx.clear(); + }); + + test('uses report param when provided', async () => { + const transaction: Transaction = { + ...createRandomTransaction(1), + reportID: mockReportID, + merchant: 'Coffee Shop', + amount: -1000, + currency: CONST.CURRENCY.USD, + }; + + const result = getTransactionReportName({ + translate: translateLocal, + reportAction: undefined, + transactions: [transaction], + report: mockTransactionReport, + }); + + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + }); + + test('returns report name with report param', () => { + const transaction: Transaction = { + ...createRandomTransaction(1), + reportID: mockReportID, + merchant: 'Coffee Shop', + amount: -1000, + currency: CONST.CURRENCY.USD, + }; + + const result = getTransactionReportName({ + translate: translateLocal, + reportAction: undefined, + transactions: [transaction], + report: mockTransactionReport, + }); + + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + }); + + test('returns reversed transaction message when action is reversed', () => { + const reportAction = createRandomReportAction(1); + + jest.spyOn(require('@libs/ReportActionsUtils'), 'isReversedTransaction').mockReturnValueOnce(true); + + const result = getTransactionReportName({ + translate: translateLocal, + reportAction, + report: mockTransactionReport, + }); + + expect(result).toBe(translateLocal('parentReportAction.reversedTransaction')); + }); + + test('returns deleted expense message when action is deleted', () => { + const reportAction = createRandomReportAction(1); + + jest.spyOn(require('@libs/ReportActionsUtils'), 'isDeletedAction').mockReturnValueOnce(true); + + const result = getTransactionReportName({ + translate: translateLocal, + reportAction, + report: mockTransactionReport, + }); + + expect(result).toBe(translateLocal('parentReportAction.deletedExpense')); + }); + + test('uses report param from caller', () => { + const transaction: Transaction = { + ...createRandomTransaction(1), + reportID: mockReportID, + merchant: 'Coffee Shop', + amount: -1000, + currency: CONST.CURRENCY.USD, + }; + + const result = getTransactionReportName({ + translate: translateLocal, + reportAction: undefined, + transactions: [transaction], + report: mockTransactionReport, + }); + + expect(result).toBeDefined(); + expect(typeof result).toBe('string'); + }); + }); + describe('buildOptimisticExpenseReport', () => { beforeEach(Onyx.clear); // Other describe blocks leave isPaidGroupPolicy stuck on mockReturnValue(true); restore the type-based default so the report-name fallback is driven by the real policy type