diff --git a/src/components/ReportActionItem/MoneyRequestAction.tsx b/src/components/ReportActionItem/MoneyRequestAction.tsx index 9e169b23391a..05891311ba6d 100644 --- a/src/components/ReportActionItem/MoneyRequestAction.tsx +++ b/src/components/ReportActionItem/MoneyRequestAction.tsx @@ -8,9 +8,7 @@ import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; -import * as ReportUtils from '@libs/ReportUtils'; import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; import * as Report from '@userActions/Report'; import CONST from '@src/CONST'; @@ -91,15 +89,7 @@ function MoneyRequestAction({ return; } - // If the childReportID is not present, we need to create a new thread - const childReportID = action?.childReportID; - if (!childReportID) { - const thread = ReportUtils.buildTransactionThread(action, requestReportID); - const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs ?? []); - Report.openReport(thread.reportID, userLogins, thread, action.reportActionID); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(thread.reportID)); - return; - } + const childReportID = action?.childReportID ?? '0'; Report.openReport(childReportID); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID)); }; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 8e74eb00ba27..6c5b19704af4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -175,6 +175,7 @@ type OptimisticIOUReportAction = Pick< | 'pendingAction' | 'receipt' | 'whisperedToAccountIDs' + | 'childReportID' >; type ReportRouteParams = { @@ -3160,7 +3161,6 @@ function getIOUReportActionMessage(iouReportID: string, type: string, total: num * @param [receipt] * @param [isOwnPolicyExpenseChat] - Whether this is an expense report create from the current user's policy expense chat */ - function buildOptimisticIOUReportAction( type: ValueOf, amount: number, @@ -3925,16 +3925,15 @@ function buildOptimisticTaskReport( * A helper method to create transaction thread * * @param reportAction - the parent IOU report action from which to create the thread - * - * @param moneyRequestReportID - the reportID which the report action belong to + * @param moneyRequestReport - the report which the report action belongs to */ -function buildTransactionThread(reportAction: OnyxEntry, moneyRequestReportID: string): OptimisticChatReport { +function buildTransactionThread(reportAction: OnyxEntry, moneyRequestReport: Report): OptimisticChatReport { const participantAccountIDs = [...new Set([currentUserAccountID, Number(reportAction?.actorAccountID)])].filter(Boolean) as number[]; return buildOptimisticChatReport( participantAccountIDs, getTransactionReportName(reportAction), undefined, - getReport(moneyRequestReportID)?.policyID ?? CONST.POLICY.OWNER_EMAIL_FAKE, + moneyRequestReport.policyID, CONST.POLICY.OWNER_ACCOUNT_ID_FAKE, false, '', @@ -3942,10 +3941,63 @@ function buildTransactionThread(reportAction: OnyxEntry, + amount: number, + currency: string, + comment: string, + payeeEmail: string, + participants: Participant[], + transactionID: string, + paymentType?: PaymentMethodType, + isSettlingUp = false, + isSendMoneyFlow = false, + receipt: Receipt = {}, + isOwnPolicyExpenseChat = false, +): [OptimisticCreatedReportAction, OptimisticIOUReportAction, OptimisticChatReport, OptimisticCreatedReportAction] { + const iouActionCreationTime = DateUtils.getDBTime(); + + // The `CREATED` action must be optimistically generated before the IOU action so that it won't appear after the IOU action in the chat. + const createdActionForIOUReport = buildOptimisticCreatedReportAction(payeeEmail, DateUtils.subtractMillisecondsFromDateTime(iouActionCreationTime, 1)); + const iouAction = buildOptimisticIOUReportAction( + type, + amount, + currency, + comment, + participants, + transactionID, + paymentType, + iouReport.reportID, + isSettlingUp, + isSendMoneyFlow, + receipt, + isOwnPolicyExpenseChat, + iouActionCreationTime, + ); + + // Create optimistic transactionThread and the `CREATED` action for it + const transactionThread = buildTransactionThread(iouAction, iouReport); + const createdActionForTransactionThread = buildOptimisticCreatedReportAction(payeeEmail); + + // The IOU action and the transactionThread are co-dependent as parent-child, so we need to link them together + iouAction.childReportID = transactionThread.reportID; + + return [createdActionForIOUReport, iouAction, transactionThread, createdActionForTransactionThread]; +} + function isUnread(report: OnyxEntry): boolean { if (!report) { return false; @@ -5279,6 +5331,7 @@ export { buildOptimisticSubmittedReportAction, buildOptimisticExpenseReport, buildOptimisticIOUReportAction, + buildOptimisticMoneyRequestEntities, buildOptimisticReportPreview, buildOptimisticModifiedExpenseReportAction, buildOptimisticCancelPaymentReportAction, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 4b88bb7a77a8..af5c40836c74 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -914,26 +914,22 @@ function getMoneyRequestInformation( // 4. The transaction thread, which requires the iouAction, and CREATED action for the transaction thread // 5. REPORTPREVIEW action for the chatReport // Note: The CREATED action for the IOU report must be optimistically generated before the IOU action so there's no chance that it appears after the IOU action in the chat - const currentTime = DateUtils.getDBTime(); const optimisticCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); - const optimisticCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail, DateUtils.subtractMillisecondsFromDateTime(currentTime, 1)); - const iouAction = ReportUtils.buildOptimisticIOUReportAction( + const [optimisticCreatedActionForIOUReport, iouAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] = ReportUtils.buildOptimisticMoneyRequestEntities( + iouReport, CONST.IOU.REPORT_ACTION_TYPE.CREATE, amount, currency, comment, + payeeEmail, [participant], optimisticTransaction.transactionID, undefined, - iouReport.reportID, false, false, receiptObject, false, - currentTime, ); - const optimisticTransactionThread = ReportUtils.buildTransactionThread(iouAction, iouReport.reportID); - const optimisticCreatedActionForTransactionThread = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); let reportPreviewAction = shouldCreateNewMoneyRequestReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID); if (reportPreviewAction) { @@ -970,7 +966,7 @@ function getMoneyRequestInformation( iouReport, optimisticTransaction, optimisticCreatedActionForChat, - optimisticCreatedActionForIOU, + optimisticCreatedActionForIOUReport, iouAction, optimisticPersonalDetailListAction, reportPreviewAction, @@ -994,7 +990,7 @@ function getMoneyRequestInformation( transaction: optimisticTransaction, iouAction, createdChatReportActionID: isNewChatReport ? optimisticCreatedActionForChat.reportActionID : '0', - createdIOUReportActionID: shouldCreateNewMoneyRequestReport ? optimisticCreatedActionForIOU.reportActionID : '0', + createdIOUReportActionID: shouldCreateNewMoneyRequestReport ? optimisticCreatedActionForIOUReport.reportActionID : '0', reportPreviewAction, transactionThreadReportID: optimisticTransactionThread.reportID, createdReportActionIDForThread: optimisticCreatedActionForTransactionThread.reportActionID, @@ -1889,25 +1885,18 @@ function createSplitsAndOnyxData( // 1. CREATED action for the chatReport // 2. CREATED action for the iouReport // 3. IOU action for the iouReport - // 4. REPORTPREVIEW action for the chatReport - // Note: The CREATED action for the IOU report must be optimistically generated before the IOU action so there's no chance that it appears after the IOU action in the chat - const currentTime = DateUtils.getDBTime(); + // 4. Transaction Thread and the CREATED action for it + // 5. REPORTPREVIEW action for the chatReport const oneOnOneCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); - const oneOnOneCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit, DateUtils.subtractMillisecondsFromDateTime(currentTime, 1)); - const oneOnOneIOUAction = ReportUtils.buildOptimisticIOUReportAction( + const [oneOnOneCreatedActionForIOU, oneOnOneIOUAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] = ReportUtils.buildOptimisticMoneyRequestEntities( + oneOnOneIOUReport, CONST.IOU.REPORT_ACTION_TYPE.CREATE, splitAmount, currency, comment, + currentUserEmailForIOUSplit, [participant], oneOnOneTransaction.transactionID, - undefined, - oneOnOneIOUReport.reportID, - undefined, - undefined, - undefined, - undefined, - currentTime, ); // Add optimistic personal details for new participants @@ -1938,10 +1927,6 @@ function createSplitsAndOnyxData( // Add tag to optimistic policy recently used tags when a participant is a workspace const optimisticPolicyRecentlyUsedTags = isPolicyExpenseChat ? Policy.buildOptimisticPolicyRecentlyUsedTags(participant.policyID, tag) : {}; - // Create optimistic transactionThread - const optimisticTransactionThread = ReportUtils.buildTransactionThread(oneOnOneIOUAction, oneOnOneIOUReport.reportID); - const optimisticCreatedActionForTransactionThread = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); - // STEP 5: Build Onyx Data const [oneOnOneOptimisticData, oneOnOneSuccessData, oneOnOneFailureData] = buildOnyxDataForMoneyRequest( oneOnOneChatReport, @@ -2058,7 +2043,7 @@ function splitBill( } /** - * @param amount - always in smallest currency unit + * @param amount - always in the smallest currency unit */ function splitBillAndOpenReport( participants: Participant[], @@ -2523,16 +2508,16 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA ); const oneOnOneCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); - const oneOnOneCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); - const oneOnOneIOUAction = ReportUtils.buildOptimisticIOUReportAction( + const [oneOnOneCreatedActionForIOU, oneOnOneIOUAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] = ReportUtils.buildOptimisticMoneyRequestEntities( + oneOnOneIOUReport, CONST.IOU.REPORT_ACTION_TYPE.CREATE, splitAmount, currency ?? '', updatedTransaction.comment.comment ?? '', + currentUserEmailForIOUSplit, [participant], oneOnOneTransaction.transactionID, undefined, - oneOnOneIOUReport?.reportID, ); let oneOnOneReportPreviewAction = ReportActionsUtils.getReportPreviewAction(oneOnOneChatReport?.reportID ?? '', oneOnOneIOUReport?.reportID ?? ''); @@ -2542,9 +2527,6 @@ function completeSplitBill(chatReportID: string, reportAction: OnyxTypes.ReportA oneOnOneReportPreviewAction = ReportUtils.buildOptimisticReportPreview(oneOnOneChatReport, oneOnOneIOUReport, '', oneOnOneTransaction); } - const optimisticTransactionThread = ReportUtils.buildTransactionThread(oneOnOneIOUAction, oneOnOneIOUReport.reportID); - const optimisticCreatedActionForTransactionThread = ReportUtils.buildOptimisticCreatedReportAction(currentUserEmailForIOUSplit); - const [oneOnOneOptimisticData, oneOnOneSuccessData, oneOnOneFailureData] = buildOnyxDataForMoneyRequest( oneOnOneChatReport, oneOnOneIOUReport, @@ -3252,26 +3234,23 @@ function getSendMoneyParams( value: optimisticTransaction, }; - // Note: The created action must be optimistically generated before the IOU action so there's no chance that the created action appears after the IOU action in the chat - const optimisticCreatedAction = ReportUtils.buildOptimisticCreatedReportAction(recipientEmail); - const optimisticIOUReportAction = ReportUtils.buildOptimisticIOUReportAction( - CONST.IOU.REPORT_ACTION_TYPE.PAY, - amount, - currency, - comment, - [recipient], - optimisticTransaction.transactionID, - paymentMethodType, - optimisticIOUReport.reportID, - false, - true, - ); + const [optimisticCreatedActionForIOUReport, optimisticIOUReportAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] = + ReportUtils.buildOptimisticMoneyRequestEntities( + optimisticIOUReport, + CONST.IOU.REPORT_ACTION_TYPE.PAY, + amount, + currency, + comment, + recipientEmail, + [recipient], + optimisticTransaction.transactionID, + paymentMethodType, + false, + true, + ); const reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, optimisticIOUReport); - const optimisticTransactionThread = ReportUtils.buildTransactionThread(optimisticIOUReportAction, optimisticIOUReport.reportID); - const optimisticCreatedActionForTransactionThread = ReportUtils.buildOptimisticCreatedReportAction(recipientEmail); - // Change the method to set for new reports because it doesn't exist yet, is faster, // and we need the data to be available when we navigate to the chat page const optimisticChatReportData: OnyxUpdate = isNewChat @@ -3445,7 +3424,7 @@ function getSendMoneyParams( if (optimisticChatReportActionsData.value) { // Add an optimistic created action to the optimistic chat reportActions data - optimisticChatReportActionsData.value[optimisticCreatedAction.reportActionID] = optimisticCreatedAction; + optimisticChatReportActionsData.value[optimisticCreatedActionForIOUReport.reportActionID] = optimisticCreatedActionForIOUReport; } } else { failureData.push({ @@ -3481,7 +3460,7 @@ function getSendMoneyParams( paymentMethodType, transactionID: optimisticTransaction.transactionID, newIOUReportDetails, - createdReportActionID: isNewChat ? optimisticCreatedAction.reportActionID : '0', + createdReportActionID: isNewChat ? optimisticCreatedActionForIOUReport.reportActionID : '0', reportPreviewReportActionID: reportPreviewAction.reportActionID, transactionThreadReportID: optimisticTransactionThread.reportID, createdReportActionIDForThread: optimisticCreatedActionForTransactionThread.reportActionID, diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index ecfc1dc4a1e2..c5ab9bbff1f5 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -18,7 +18,6 @@ import * as Localize from '@libs/Localize'; import ModifiedExpenseMessage from '@libs/ModifiedExpenseMessage'; import Navigation from '@libs/Navigation/Navigation'; import Permissions from '@libs/Permissions'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; @@ -220,13 +219,6 @@ const ContextMenuActions: ContextMenuAction[] = [ if (ReportActionsUtils.isMoneyRequestAction(reportAction)) { hideContextMenu(false); const childReportID = reportAction?.childReportID ?? '0'; - if (!childReportID) { - const thread = ReportUtils.buildTransactionThread(reportAction, reportID); - const userLogins = PersonalDetailsUtils.getLoginsByAccountIDs(thread.participantAccountIDs ?? []); - Report.openReport(thread.reportID, userLogins, thread, reportAction?.reportActionID); - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(thread.reportID)); - return; - } Report.openReport(childReportID); Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(childReportID)); return; diff --git a/tests/actions/IOUTest.js b/tests/actions/IOUTest.js index ec32be4d241c..64c8edb134b1 100644 --- a/tests/actions/IOUTest.js +++ b/tests/actions/IOUTest.js @@ -1509,7 +1509,7 @@ describe('actions/IOU', () => { }), ) .then(() => { - thread = ReportUtils.buildTransactionThread(iouAction, iouReport.reportID); + thread = ReportUtils.buildTransactionThread(iouAction, iouReport); Onyx.set(`report_${thread.reportID}`, thread); return waitForBatchedUpdates(); }) @@ -1643,7 +1643,7 @@ describe('actions/IOU', () => { }), ) .then(() => { - thread = ReportUtils.buildTransactionThread(iouAction, iouReport.reportID); + thread = ReportUtils.buildTransactionThread(iouAction, iouReport); Onyx.set(`report_${thread.reportID}`, thread); return waitForBatchedUpdates(); }) @@ -2214,7 +2214,7 @@ describe('actions/IOU', () => { jest.advanceTimersByTime(10); // Given a transaction thread - thread = ReportUtils.buildTransactionThread(createIOUAction, IOU_REPORT_ID); + thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID}); expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); @@ -2295,7 +2295,7 @@ describe('actions/IOU', () => { jest.advanceTimersByTime(10); // Given a transaction thread - thread = ReportUtils.buildTransactionThread(createIOUAction, IOU_REPORT_ID); + thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID}); Onyx.connect({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${thread.reportID}`, @@ -2374,7 +2374,7 @@ describe('actions/IOU', () => { await waitForBatchedUpdates(); // Given a transaction thread - thread = ReportUtils.buildTransactionThread(createIOUAction); + thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID}); expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); @@ -2460,7 +2460,7 @@ describe('actions/IOU', () => { // Given a thread report jest.advanceTimersByTime(10); - thread = ReportUtils.buildTransactionThread(createIOUAction, IOU_REPORT_ID); + thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID}); expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN); @@ -2686,7 +2686,7 @@ describe('actions/IOU', () => { // Given a thread report jest.advanceTimersByTime(10); - thread = ReportUtils.buildTransactionThread(createIOUAction, IOU_REPORT_ID); + thread = ReportUtils.buildTransactionThread(createIOUAction, {reportID: IOU_REPORT_ID}); expect(thread.notificationPreference).toBe(CONST.REPORT.NOTIFICATION_PREFERENCE.HIDDEN);