diff --git a/src/libs/actions/IOU/ReportWorkflow.ts b/src/libs/actions/IOU/ReportWorkflow.ts index 6ad5d1c5d248..c6fe74547add 100644 --- a/src/libs/actions/IOU/ReportWorkflow.ts +++ b/src/libs/actions/IOU/ReportWorkflow.ts @@ -306,6 +306,20 @@ function getBadgeFromIOUReport( return undefined; } +/** + * Determines if a p2p IOU can be paid by the current user using only the REPORTPREVIEW action's + * child* fields, without requiring the full IOU report to be loaded in Onyx. This is used as a + * fallback when the IOU report hasn't been fetched yet (e.g. right after login). + */ +function canPayIOUFromReportAction(action: ReportAction, chatReport: OnyxEntry, currentUserAccountID: number): boolean { + return ( + action.childType === CONST.REPORT.TYPE.IOU && + action.childReportID === chatReport?.iouReportID && + action.childManagerAccountID === currentUserAccountID && + action.childStatusNum !== CONST.REPORT.STATUS_NUM.REIMBURSED + ); +} + function getIOUReportActionWithBadge( chatReport: OnyxEntry, policy: OnyxEntry, @@ -324,13 +338,25 @@ function getIOUReportActionWithBadge( continue; } const iouReport = getReportOrDraftReport(action.childReportID); - const badge = getBadgeFromIOUReport(iouReport, chatReport, policy, reportMetadata, invoiceReceiverPolicy, currentUserLogin, currentUserAccountID); - if (!badge) { + + if (!iouReport) { + // Fallback for p2p IOUs when the IOU report isn't loaded in Onyx yet (e.g. right after login). + // Use the REPORTPREVIEW action's child* fields to determine PAY badge without the full report. + if (chatReport?.hasOutstandingChildRequest && canPayIOUFromReportAction(action, chatReport, currentUserAccountID)) { + if (!earliestAction || isOlderReportAction(action, earliestAction)) { + earliestAction = action; + actionBadge = CONST.REPORT.ACTION_BADGE.PAY; + } + } continue; } - if (!earliestAction || isOlderReportAction(action, earliestAction)) { - earliestAction = action; - actionBadge = badge; + + const badge = getBadgeFromIOUReport(iouReport, chatReport, policy, reportMetadata, invoiceReceiverPolicy, currentUserLogin, currentUserAccountID); + if (badge) { + if (!earliestAction || isOlderReportAction(action, earliestAction)) { + earliestAction = action; + actionBadge = badge; + } } } diff --git a/tests/actions/IOUTest/ReportWorkflowTest.ts b/tests/actions/IOUTest/ReportWorkflowTest.ts index b95064183008..01de01f90443 100644 --- a/tests/actions/IOUTest/ReportWorkflowTest.ts +++ b/tests/actions/IOUTest/ReportWorkflowTest.ts @@ -3655,6 +3655,107 @@ describe('actions/IOU/ReportWorkflow', () => { expect(result.reportAction).toBeUndefined(); expect(result.actionBadge).toBeUndefined(); }); + + it('should return PAY badge for p2p IOU when IOU report is not loaded in Onyx', async () => { + const chatReportID = '800'; + const iouReportID = '801'; + + const fakeChatReport: Report = { + ...createRandomReport(Number(chatReportID), undefined), + reportID: chatReportID, + iouReportID, + hasOutstandingChildRequest: true, + }; + + // Do NOT set the IOU report in Onyx — simulates fresh login where it hasn't loaded yet + + const reportPreviewAction = { + reportActionID: '802', + actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW, + created: '2024-08-08 19:00:00.000', + childReportID: iouReportID, + childType: CONST.REPORT.TYPE.IOU, + childManagerAccountID: RORY_ACCOUNT_ID, + childOwnerAccountID: CARLOS_ACCOUNT_ID, + childStatusNum: CONST.REPORT.STATUS_NUM.OPEN, + message: [{type: 'TEXT', text: 'IOU preview'}], + }; + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, fakeChatReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, { + [reportPreviewAction.reportActionID]: reportPreviewAction, + }); + await waitForBatchedUpdates(); + + const result = getIOUReportActionWithBadge(fakeChatReport, undefined, {}, undefined, RORY_EMAIL, RORY_ACCOUNT_ID); + expect(result.reportAction).toMatchObject(reportPreviewAction); + expect(result.actionBadge).toBe(CONST.REPORT.ACTION_BADGE.PAY); + }); + + it('should NOT return PAY badge fallback when IOU is already settled', async () => { + const chatReportID = '810'; + const iouReportID = '811'; + + const fakeChatReport: Report = { + ...createRandomReport(Number(chatReportID), undefined), + reportID: chatReportID, + iouReportID, + hasOutstandingChildRequest: true, + }; + + const reportPreviewAction = { + reportActionID: '812', + actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW, + created: '2024-08-08 19:00:00.000', + childReportID: iouReportID, + childType: CONST.REPORT.TYPE.IOU, + childManagerAccountID: RORY_ACCOUNT_ID, + childOwnerAccountID: CARLOS_ACCOUNT_ID, + childStatusNum: CONST.REPORT.STATUS_NUM.REIMBURSED, + message: [{type: 'TEXT', text: 'IOU preview'}], + }; + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, fakeChatReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, { + [reportPreviewAction.reportActionID]: reportPreviewAction, + }); + await waitForBatchedUpdates(); + + const result = getIOUReportActionWithBadge(fakeChatReport, undefined, {}, undefined, RORY_EMAIL, RORY_ACCOUNT_ID); + expect(result.reportAction).toBeUndefined(); + expect(result.actionBadge).toBeUndefined(); + }); + + it('should NOT return PAY badge fallback when current user is not the payer', async () => { + const chatReportID = '820'; + const iouReportID = '821'; + + const fakeChatReport: Report = { + ...createRandomReport(Number(chatReportID), undefined), + reportID: chatReportID, + iouReportID, + hasOutstandingChildRequest: true, + }; + + const reportPreviewAction = { + reportActionID: '822', + actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW, + created: '2024-08-08 19:00:00.000', + childReportID: iouReportID, + childType: CONST.REPORT.TYPE.IOU, + childManagerAccountID: CARLOS_ACCOUNT_ID, // Someone else is the payer + childOwnerAccountID: RORY_ACCOUNT_ID, + childStatusNum: CONST.REPORT.STATUS_NUM.OPEN, + message: [{type: 'TEXT', text: 'IOU preview'}], + }; + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, fakeChatReport); + await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReportID}`, { + [reportPreviewAction.reportActionID]: reportPreviewAction, + }); + await waitForBatchedUpdates(); + + const result = getIOUReportActionWithBadge(fakeChatReport, undefined, {}, undefined, RORY_EMAIL, RORY_ACCOUNT_ID); + expect(result.reportAction).toBeUndefined(); + expect(result.actionBadge).toBeUndefined(); + }); }); describe('getBadgeFromIOUReport', () => {