diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 73841603e9111..a4fd8f2e4190f 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -33,7 +33,7 @@ import {getDecodedCategoryName, isCategoryMissing} from '@libs/CategoryUtils'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; -import {getRateFromMerchant, getReportIDForExpense} from '@libs/MergeTransactionUtils'; +import {getRateFromMerchant} from '@libs/MergeTransactionUtils'; import {hasEnabledOptions} from '@libs/OptionsListUtils'; import Parser from '@libs/Parser'; import {getLengthOfTag, getTagLists, hasDependentTags as hasDependentTagsPolicyUtils, isTaxTrackingEnabled} from '@libs/PolicyUtils'; @@ -45,7 +45,6 @@ import { canEditMoneyRequest, canUserPerformWriteAction as canUserPerformWriteActionReportUtils, getReportName, - getReportOrDraftReport, getTransactionDetails, getTripIDFromTransactionParentReportID, isInvoiceReport, @@ -626,9 +625,9 @@ function MoneyRequestView({ ); }); - const actualParentReport = isFromMergeTransaction ? getReportOrDraftReport(getReportIDForExpense(updatedTransaction)) : parentReport; - const shouldShowReport = !!parentReportID || !!actualParentReport; - const reportCopyValue = !canEditReport ? getReportName(actualParentReport) || actualParentReport?.reportName : undefined; + const reportNameToDisplay = isFromMergeTransaction ? updatedTransaction?.reportName : getReportName(parentReport) || parentReport?.reportName; + const shouldShowReport = !!parentReportID || (isFromMergeTransaction && !!reportNameToDisplay); + const reportCopyValue = !canEditReport ? reportNameToDisplay : undefined; // In this case we want to use this value. The shouldUseNarrowLayout will always be true as this case is handled when we display ReportScreen in RHP. // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth @@ -926,7 +925,7 @@ function MoneyRequestView({ , m created: mergeTransaction.created, modifiedCreated: mergeTransaction.created, reportID: mergeTransaction.reportID, + reportName: mergeTransaction.reportName, routes: mergeTransaction.routes, }; } @@ -390,7 +392,11 @@ function getDisplayValue(field: MergeFieldKey, transaction: Transaction, transla return getCommaSeparatedTagNameWithSanitizedColons(SafeString(fieldValue)); } if (field === 'reportID') { - return fieldValue === CONST.REPORT.UNREPORTED_REPORT_ID ? translate('common.none') : getReportName(getReportOrDraftReport(SafeString(fieldValue))); + if (fieldValue === CONST.REPORT.UNREPORTED_REPORT_ID) { + return translate('common.none'); + } + + return transaction?.reportName ?? getReportName(getReportOrDraftReport(SafeString(fieldValue))); } if (field === 'attendees') { return Array.isArray(fieldValue) ? getAttendeesListDisplayString(fieldValue) : ''; @@ -457,6 +463,10 @@ function getMergeFieldUpdatedValues(transaction: OnyxEn updatedValues.currency = getCurrency(transaction); } + if (field === 'reportID') { + updatedValues.reportName = transaction?.reportName ?? getReportName(getReportOrDraftReport(getReportIDForExpense(transaction))); + } + if (field === 'merchant' && isDistanceRequest(transaction)) { const transactionDetails = getTransactionDetails(transaction); updatedValues.amount = getMergeFieldValue(transactionDetails, transaction, 'amount') as number; diff --git a/src/pages/TransactionMerge/MergeTransactionsListContent.tsx b/src/pages/TransactionMerge/MergeTransactionsListContent.tsx index 5a5f51098bec3..b207c363a6716 100644 --- a/src/pages/TransactionMerge/MergeTransactionsListContent.tsx +++ b/src/pages/TransactionMerge/MergeTransactionsListContent.tsx @@ -22,9 +22,8 @@ import { shouldNavigateToReceiptReview, } from '@libs/MergeTransactionUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {getReportName, getReportOrDraftReport} from '@libs/ReportUtils'; +import {getReportName} from '@libs/ReportUtils'; import {getCreated} from '@libs/TransactionUtils'; -import {openReport} from '@userActions/Report'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -115,13 +114,6 @@ function MergeTransactionsListContent({transactionID, mergeTransaction}: MergeTr return; } - // It's a temporary solution to ensure the source report is loaded, so we can display reportName in the merge transaction details page - // We plan to remove this in next phase of merge expenses project - const sourceReport = getReportOrDraftReport(sourceTransaction.reportID); - if (!sourceReport) { - openReport(sourceTransaction.reportID); - } - const {targetTransaction: newTargetTransaction, sourceTransaction: newSourceTransaction} = selectTargetAndSourceTransactionsForMerge(targetTransaction, sourceTransaction); if (shouldNavigateToReceiptReview([newTargetTransaction, newSourceTransaction])) { setMergeTransactionKey(transactionID, { diff --git a/src/types/onyx/MergeTransaction.ts b/src/types/onyx/MergeTransaction.ts index 6718c51e2e1ae..d5be77e98a507 100644 --- a/src/types/onyx/MergeTransaction.ts +++ b/src/types/onyx/MergeTransaction.ts @@ -53,6 +53,9 @@ type MergeTransaction = { /** The report ID of the transaction */ reportID: string; + /** The report name of the transaction */ + reportName: string; + /** Custom unit data for distance requests */ customUnit?: TransactionCustomUnit; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index f0cb54f1d3739..210ded80c79c0 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -481,6 +481,9 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< /** The iouReportID associated with the transaction */ reportID: string | undefined; + /** The name of iouReport associated with the transaction */ + reportName?: string; + /** Existing routes */ routes?: Routes; diff --git a/tests/unit/MergeTransactionUtilsTest.ts b/tests/unit/MergeTransactionUtilsTest.ts index 72494f145e3e7..dd093a3ff85a0 100644 --- a/tests/unit/MergeTransactionUtilsTest.ts +++ b/tests/unit/MergeTransactionUtilsTest.ts @@ -1,3 +1,4 @@ +import Onyx from 'react-native-onyx'; import { buildMergedTransactionData, getDisplayValue, @@ -15,14 +16,22 @@ import { } from '@libs/MergeTransactionUtils'; import {getTransactionDetails} from '@libs/ReportUtils'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; import createRandomMergeTransaction from '../utils/collections/mergeTransaction'; +import {createRandomReport} from '../utils/collections/reports'; import createRandomTransaction, {createRandomDistanceRequestTransaction} from '../utils/collections/transaction'; import {translateLocal} from '../utils/TestHelper'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; // Mock localeCompare function for tests const mockLocaleCompare = (a: string, b: string) => a.localeCompare(b); describe('MergeTransactionUtils', () => { + beforeAll(() => { + Onyx.init({keys: ONYXKEYS}); + return waitForBatchedUpdates(); + }); + describe('getSourceTransactionFromMergeTransaction', () => { it('should return undefined when mergeTransaction is undefined', () => { // Given a null merge transaction @@ -485,6 +494,27 @@ describe('MergeTransactionUtils', () => { expect(result.conflictFields).toContain('attendees'); }); }); + + it('auto-merges reportID and populates reportName when reportIDs match', () => { + const sharedReportID = 'R123'; + const targetTransaction = { + ...createRandomTransaction(10), + reportID: sharedReportID, + reportName: 'Shared Report Name', + }; + const sourceTransaction = { + ...createRandomTransaction(11), + reportID: sharedReportID, + }; + + const result = getMergeableDataAndConflictFields(targetTransaction, sourceTransaction, mockLocaleCompare); + + expect(result.conflictFields).not.toContain('reportID'); + expect(result.mergeableData).toMatchObject({ + reportID: sharedReportID, + reportName: 'Shared Report Name', + }); + }); }); describe('buildMergedTransactionData', () => { @@ -516,6 +546,7 @@ describe('MergeTransactionUtils', () => { receipt: {receiptID: 1235, source: 'merged.jpg', filename: 'merged.jpg'}, created: '2025-01-02T00:00:00.000Z', reportID: '1', + reportName: 'Test Report', waypoints: {waypoint0: {name: 'Selected waypoint'}}, customUnit: {name: CONST.CUSTOM_UNITS.NAME_DISTANCE, customUnitID: 'distance1', quantity: 100}, }; @@ -545,6 +576,7 @@ describe('MergeTransactionUtils', () => { created: '2025-01-02T00:00:00.000Z', modifiedCreated: '2025-01-02T00:00:00.000Z', reportID: '1', + reportName: 'Test Report', }); }); }); @@ -751,6 +783,61 @@ describe('MergeTransactionUtils', () => { expect(merchantResult).toBe('Starbucks Coffee'); expect(categoryResult).toBe('Food & Dining'); }); + + it('should return "None" for unreported reportID', () => { + // Given a transaction with unreported reportID + const transaction = { + ...createRandomTransaction(0), + reportID: CONST.REPORT.UNREPORTED_REPORT_ID, + }; + + // When we get display value for reportID + const result = getDisplayValue('reportID', transaction, translateLocal); + + // Then it should return translated "None" + expect(result).toBe('common.none'); + }); + + it("should return transaction's reportName when available for reportID", () => { + // Given a transaction with reportID and reportName + const transaction = { + ...createRandomTransaction(0), + reportID: '123', + reportName: 'Test Report Name', + }; + + // When we get display value for reportID + const result = getDisplayValue('reportID', transaction, translateLocal); + + // Then it should return the reportName + expect(result).toBe('Test Report Name'); + }); + + it("should return report's name when no reportName available on transaction", async () => { + // Given a random report + const reportID = 456; + const report = { + ...createRandomReport(reportID, undefined), + reportName: 'Test Report Name', + }; + + // Store the report in Onyx + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${reportID}`, report); + await waitForBatchedUpdates(); + + // Given a transaction with reportID but no reportName + const transaction = { + ...createRandomTransaction(0), + reportID: report.reportID, + reportName: undefined, + }; + + // When we get display value for reportID + const result = getDisplayValue('reportID', transaction, translateLocal); + + // Then it should return the report's name from Onyx + expect(result).toBe(report.reportName); + }); }); describe('getMergeFieldUpdatedValues', () => { @@ -786,6 +873,25 @@ describe('MergeTransactionUtils', () => { }); }); + it('should include reportName when field is reportID', () => { + // Given a transaction with a reportID and reportName + const transaction = { + ...createRandomTransaction(0), + reportID: '123', + reportName: 'Test Report', + }; + const fieldValue = '456'; + + // When we get updated values for reportID field + const result = getMergeFieldUpdatedValues(transaction, 'reportID', fieldValue); + + // Then it should include both reportID and reportName + expect(result).toEqual({ + reportID: '456', + reportName: 'Test Report', + }); + }); + it('should include additional fields when merchant field is selected for distance request', () => { // Given a distance request transaction const transaction = { diff --git a/tests/utils/collections/mergeTransaction.ts b/tests/utils/collections/mergeTransaction.ts index dd45e28fa951b..938720a013235 100644 --- a/tests/utils/collections/mergeTransaction.ts +++ b/tests/utils/collections/mergeTransaction.ts @@ -23,5 +23,6 @@ export default function createRandomMergeTransaction(index: number): MergeTransa receipt: {}, created: format(randPastDate(), CONST.DATE.FNS_DB_FORMAT_STRING), reportID: index.toString(), + reportName: randWord(), }; }