diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index 7c62c303053c..662596b2467d 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -78,6 +78,9 @@ type TransactionWithOptionalSearchFields = TransactionWithOptionalHighlight & { /** Used to initiate payment from search page */ hash?: number; + + /** Report to which the transaction belongs */ + report?: Report; }; type TransactionItemRowProps = { diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 99bd81d69858..7e7fad778690 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -22,6 +22,7 @@ import * as defaultGroupAvatars from '@components/Icon/GroupDefaultAvatars'; import * as defaultWorkspaceAvatars from '@components/Icon/WorkspaceDefaultAvatars'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import type {MoneyRequestAmountInputProps} from '@components/MoneyRequestAmountInput'; +import type {TransactionWithOptionalSearchFields} from '@components/TransactionItemRow'; import type {ThemeColors} from '@styles/theme/types'; import type {IOUAction, IOUType, OnboardingAccounting} from '@src/CONST'; import CONST, {TASK_TO_FEATURE} from '@src/CONST'; @@ -1190,10 +1191,10 @@ function getChatType(report: OnyxInputOrEntry | Participant): ValueOf | SearchReport { +function getReportOrDraftReport(reportID: string | undefined, searchReports?: SearchReport[], fallbackReport?: Report): OnyxEntry | SearchReport { const searchReport = searchReports?.find((report) => report.reportID === reportID); const onyxReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; - return searchReport ?? onyxReport ?? allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`]; + return searchReport ?? onyxReport ?? allReportsDraft?.[`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${reportID}`] ?? fallbackReport; } function reportTransactionsSelector(transactions: OnyxCollection, reportID: string | undefined): Transaction[] { @@ -4259,7 +4260,7 @@ function getMoneyRequestReportName({ */ function getTransactionDetails( - transaction: OnyxInputOrEntry, + transaction: OnyxInputOrEntry | TransactionWithOptionalSearchFields, createdDateFormat: string = CONST.DATE.FNS_FORMAT_STRING, policy: OnyxEntry = undefined, allowNegativeAmount = false, @@ -4268,20 +4269,16 @@ function getTransactionDetails( if (!transaction) { return; } - const report = getReportOrDraftReport(transaction?.reportID); + + const report = getReportOrDraftReport(transaction?.reportID, undefined, 'report' in transaction ? transaction.report : undefined); const isManualDistanceRequest = isManualDistanceRequestTransactionUtils(transaction); + const isFromExpenseReport = !isEmptyObject(report) && isExpenseReport(report); return { created: getFormattedCreated(transaction, createdDateFormat), - amount: getTransactionAmount( - transaction, - !isEmptyObject(report) && isExpenseReport(report), - transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID, - allowNegativeAmount, - disableOppositeConversion, - ), + amount: getTransactionAmount(transaction, isFromExpenseReport, transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID, allowNegativeAmount, disableOppositeConversion), attendees: getAttendees(transaction), - taxAmount: getTaxAmount(transaction, !isEmptyObject(report) && isExpenseReport(report)), + taxAmount: getTaxAmount(transaction, isFromExpenseReport), taxCode: getTaxCode(transaction), currency: getCurrency(transaction), comment: getDescription(transaction), diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index 5ca35c952756..e9caa166a4f2 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -59,6 +59,7 @@ import { getReportActionActorAccountID, getReportIDFromLink, getReportName, + getReportOrDraftReport, getReportStatusTranslation, getReportURLForCurrentContext, getSearchReportName, @@ -115,7 +116,7 @@ import type {ErrorFields, Errors} from '@src/types/onyx/OnyxCommon'; import type {JoinWorkspaceResolution} from '@src/types/onyx/OriginalMessage'; import type {ACHAccount} from '@src/types/onyx/Policy'; import type {Participant, Participants} from '@src/types/onyx/Report'; -import type {SearchTransaction} from '@src/types/onyx/SearchResults'; +import type {SearchReport, SearchTransaction} from '@src/types/onyx/SearchResults'; import {toCollectionDataSet} from '@src/types/utils/CollectionDataSet'; import {chatReportR14932 as mockedChatReport} from '../../__mocks__/reportData/reports'; import * as NumberUtils from '../../src/libs/NumberUtils'; @@ -8285,4 +8286,110 @@ describe('ReportUtils', () => { expect(canRejectReportAction(approver, expenseReport, reportPolicy)).toBe(false); }); }); + + describe('getReportOrDraftReport', () => { + const mockReportIDIndex = 1; + const mockReportID = mockReportIDIndex.toString(); + const mockSearchReport: SearchReport = { + ...createRandomReport(mockReportIDIndex), + reportName: 'Search Report', + type: CONST.REPORT.TYPE.CHAT, + }; + const mockOnyxReport: Report = { + ...createPolicyExpenseChat(mockReportIDIndex), + reportName: 'Onyx Report', + }; + const mockDraftReport: Report = { + ...createExpenseReport(mockReportIDIndex), + reportName: 'Draft Report', + }; + const mockFallbackReport: Report = { + ...createExpenseRequestReport(mockReportIDIndex), + reportName: 'Fallback Report', + }; + + beforeEach(async () => { + await Onyx.clear(); + }); + + test('returns search report when found in searchReports array', () => { + const searchReports = [mockSearchReport]; + const result = getReportOrDraftReport(mockReportID, searchReports); + expect(result).toEqual(mockSearchReport); + }); + + test('returns onyx report when search report is not found but onyx report exists', async () => { + const searchReports: SearchReport[] = []; + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${mockReportID}`, mockOnyxReport); + const result = getReportOrDraftReport(mockReportID, searchReports); + expect(result).toEqual(mockOnyxReport); + }); + + test('returns draft report when neither search nor onyx report exists but draft exists', async () => { + const searchReports: SearchReport[] = []; + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${mockReportID}`, mockDraftReport); + const result = getReportOrDraftReport(mockReportID, searchReports); + expect(result).toEqual(mockDraftReport); + }); + + test('returns fallback report when no other reports exist', () => { + const searchReports: SearchReport[] = []; + const result = getReportOrDraftReport('unknownReportID', searchReports, mockFallbackReport); + expect(result).toEqual(mockFallbackReport); + }); + + test('returns undefined when no reports exist and no fallback provided', () => { + const searchReports: SearchReport[] = []; + const result = getReportOrDraftReport(mockReportID, searchReports); + expect(result).toBeUndefined(); + }); + + test('returns undefined when reportID is undefined', () => { + const searchReports = [mockSearchReport]; + const result = getReportOrDraftReport(undefined, searchReports); + expect(result).toBeUndefined(); + }); + + test('returns undefined when only reportID is provided and it is not found', () => { + const result = getReportOrDraftReport('unknownReportID'); + expect(result).toBeUndefined(); + }); + + test('returns fallback report when reportID is undefined', () => { + const searchReports = [mockSearchReport]; + const result = getReportOrDraftReport(undefined, searchReports, mockFallbackReport); + expect(result).toEqual(mockFallbackReport); + }); + + test('prioritizes search report over onyx report when both exist', async () => { + const searchReports = [mockSearchReport]; + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${mockReportID}`, mockOnyxReport); + const result = getReportOrDraftReport(mockReportID, searchReports); + expect(result).toEqual(mockSearchReport); + expect(result).not.toEqual(mockOnyxReport); + }); + + test('prioritizes onyx report over draft report when both exist', async () => { + const searchReports: SearchReport[] = []; + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${mockReportID}`, mockOnyxReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${mockReportID}`, mockDraftReport); + const result = getReportOrDraftReport(mockReportID, searchReports); + expect(result).toEqual(mockOnyxReport); + expect(result).not.toEqual(mockDraftReport); + }); + + test('prioritizes draft report over fallback when both exist', async () => { + const searchReports: SearchReport[] = []; + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_DRAFT}${mockReportID}`, mockDraftReport); + const result = getReportOrDraftReport(mockReportID, searchReports, mockFallbackReport); + expect(result).toEqual(mockDraftReport); + expect(result).not.toEqual(mockFallbackReport); + }); + + test('handles empty searchReports array gracefully', async () => { + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${mockReportID}`, mockOnyxReport); + const result = getReportOrDraftReport(mockReportID); + expect(result).toEqual(mockOnyxReport); + }); + }); });