diff --git a/src/libs/actions/IOU/SendMoney.ts b/src/libs/actions/IOU/SendMoney.ts new file mode 100644 index 000000000000..41eef7ffe10a --- /dev/null +++ b/src/libs/actions/IOU/SendMoney.ts @@ -0,0 +1,522 @@ +import {Str} from 'expensify-common'; +import type {OnyxEntry, OnyxUpdate} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; +import type {PaymentMethodType} from '@components/KYCWall/types'; +import {isEmptyObject} from '@github/libs/isEmptyObject'; +import * as API from '@libs/API'; +import type {SendMoneyParams} from '@libs/API/parameters'; +import {WRITE_COMMANDS} from '@libs/API/types'; +import DateUtils from '@libs/DateUtils'; +import {getMicroSecondOnyxErrorWithTranslationKey} from '@libs/ErrorUtils'; +import {addSMSDomainIfPhoneNumber} from '@libs/PhoneNumber'; +import {getReportActionHtml, getReportActionText} from '@libs/ReportActionsUtils'; +import type {OptionData} from '@libs/ReportUtils'; +import { + buildOptimisticChatReport, + buildOptimisticIOUReport, + buildOptimisticMoneyRequestEntities, + buildOptimisticReportPreview, + getChatByParticipants, + getParsedComment, +} from '@libs/ReportUtils'; +import playSound, {SOUNDS} from '@libs/Sound'; +import {buildOptimisticTransaction} from '@libs/TransactionUtils'; +import {notifyNewAction} from '@userActions/Report'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {Participant} from '@src/types/onyx/IOU'; +import type {Receipt} from '@src/types/onyx/Transaction'; +import {dismissModalAndOpenReportInInboxTab} from '.'; + +type SendMoneyParamsData = { + params: SendMoneyParams; + optimisticData: OnyxUpdate[]; + successData: OnyxUpdate[]; + failureData: OnyxUpdate[]; +}; + +/** + * @param managerID - Account ID of the person sending the money + * @param recipient - The user receiving the money + */ +function getSendMoneyParams({ + report, + quickAction, + amount, + currency, + commentParam, + paymentMethodType, + managerID, + recipient, + created, + merchant, + receipt, +}: { + report: OnyxEntry; + quickAction: OnyxEntry; + amount: number; + currency: string; + commentParam: string; + paymentMethodType: PaymentMethodType; + managerID: number; + recipient: Participant; + created?: string; + merchant?: string; + receipt?: Receipt; +}): SendMoneyParamsData { + const recipientEmail = addSMSDomainIfPhoneNumber(recipient.login ?? ''); + const recipientAccountID = Number(recipient.accountID); + const comment = getParsedComment(commentParam); + const newIOUReportDetails = JSON.stringify({ + amount, + currency, + requestorEmail: recipientEmail, + requestorAccountID: recipientAccountID, + comment, + idempotencyKey: Str.guid(), + ...(created && {created}), + ...(merchant && {merchant}), + }); + + let chatReport = !isEmptyObject(report) && report?.reportID ? report : getChatByParticipants([recipientAccountID, managerID]); + let isNewChat = false; + if (!chatReport) { + chatReport = buildOptimisticChatReport({ + participantList: [recipientAccountID, managerID], + }); + isNewChat = true; + } + const optimisticIOUReport = buildOptimisticIOUReport(recipientAccountID, managerID, amount, chatReport.reportID, currency, true); + + const optimisticTransaction = buildOptimisticTransaction({ + transactionParams: { + amount, + currency, + reportID: optimisticIOUReport.reportID, + comment, + created, + merchant, + receipt, + }, + }); + const optimisticTransactionData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, + value: optimisticTransaction, + }; + + const [optimisticCreatedActionForChat, optimisticCreatedActionForIOUReport, optimisticIOUReportAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] = + buildOptimisticMoneyRequestEntities({ + iouReport: optimisticIOUReport, + type: CONST.IOU.REPORT_ACTION_TYPE.PAY, + amount, + currency, + comment, + payeeEmail: recipientEmail, + participants: [recipient], + transactionID: optimisticTransaction.transactionID, + paymentType: paymentMethodType, + isSendMoneyFlow: true, + }); + + const reportPreviewAction = buildOptimisticReportPreview(chatReport, optimisticIOUReport); + + // 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 + ? { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + ...chatReport, + // Set and clear pending fields on the chat report + pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, + lastReadTime: DateUtils.getDBTime(), + lastVisibleActionCreated: reportPreviewAction.created, + }, + } + : { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + ...chatReport, + lastReadTime: DateUtils.getDBTime(), + lastVisibleActionCreated: reportPreviewAction.created, + }, + }; + const optimisticQuickActionData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, + value: { + action: CONST.QUICK_ACTIONS.SEND_MONEY, + chatReportID: chatReport.reportID, + isFirstQuickAction: isEmptyObject(quickAction), + }, + }; + const optimisticIOUReportData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticIOUReport.reportID}`, + value: { + ...optimisticIOUReport, + lastMessageText: getReportActionText(optimisticIOUReportAction), + lastMessageHtml: getReportActionHtml(optimisticIOUReportAction), + pendingFields: { + createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }, + }; + const optimisticTransactionThreadData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTransactionThread.reportID}`, + value: optimisticTransactionThread, + }; + const optimisticIOUReportActionsData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, + value: { + [optimisticCreatedActionForIOUReport.reportActionID]: optimisticCreatedActionForIOUReport, + [optimisticIOUReportAction.reportActionID]: { + ...(optimisticIOUReportAction as OnyxTypes.ReportAction), + pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, + }, + }, + }; + const optimisticChatReportActionsData: OnyxUpdate = { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + value: { + [reportPreviewAction.reportActionID]: reportPreviewAction, + }, + }; + const optimisticTransactionThreadReportActionsData: OnyxUpdate | undefined = optimisticCreatedActionForTransactionThread + ? { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThread.reportID}`, + value: {[optimisticCreatedActionForTransactionThread?.reportActionID]: optimisticCreatedActionForTransactionThread}, + } + : undefined; + + const optimisticMetaData: Array> = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${chatReport.reportID}`, + value: { + isOptimisticReport: true, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${optimisticTransactionThread.reportID}`, + value: { + isOptimisticReport: true, + }, + }, + ]; + + const successData: Array< + OnyxUpdate< + | typeof ONYXKEYS.PERSONAL_DETAILS_LIST + | typeof ONYXKEYS.COLLECTION.REPORT + | typeof ONYXKEYS.COLLECTION.REPORT_METADATA + | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS + | typeof ONYXKEYS.COLLECTION.TRANSACTION + > + > = []; + + // Add optimistic personal details for recipient + let optimisticPersonalDetailListData: OnyxUpdate | null = null; + const optimisticPersonalDetailListAction = isNewChat + ? { + [recipientAccountID]: { + accountID: recipientAccountID, + // Disabling this line since participant.displayName can be an empty string + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + displayName: recipient.displayName || recipient.login, + login: recipient.login, + }, + } + : {}; + + const redundantParticipants: Record = {}; + if (!isEmptyObject(optimisticPersonalDetailListAction)) { + const successPersonalDetailListAction: Record = {}; + + // BE will send different participants. We clear the optimistic ones to avoid duplicated entries + for (const accountIDKey of Object.keys(optimisticPersonalDetailListAction)) { + const accountID = Number(accountIDKey); + successPersonalDetailListAction[accountID] = null; + redundantParticipants[accountID] = null; + } + + optimisticPersonalDetailListData = { + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + value: optimisticPersonalDetailListAction, + }; + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: ONYXKEYS.PERSONAL_DETAILS_LIST, + value: successPersonalDetailListAction, + }); + } + + successData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticIOUReport.reportID}`, + value: { + participants: redundantParticipants, + pendingFields: { + createChat: null, + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTransactionThread.reportID}`, + value: { + participants: redundantParticipants, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${optimisticTransactionThread.reportID}`, + value: { + isOptimisticReport: false, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, + value: { + [optimisticCreatedActionForIOUReport.reportActionID]: {pendingAction: null}, + [optimisticIOUReportAction.reportActionID]: { + pendingAction: null, + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, + value: {pendingAction: null}, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${chatReport.reportID}`, + value: { + isOptimisticReport: false, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, + value: { + [reportPreviewAction.reportActionID]: { + isOptimisticAction: null, + pendingAction: null, + childLastActorAccountID: reportPreviewAction.childLastActorAccountID, + }, + }, + }, + ); + + const failureData: Array< + OnyxUpdate + > = [ + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, + value: { + errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'), + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTransactionThread.reportID}`, + value: { + errorFields: { + createChat: getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), + }, + }, + }, + { + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, + value: quickAction ?? null, + }, + ]; + + if (optimisticCreatedActionForTransactionThread?.reportActionID) { + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThread.reportID}`, + value: {[optimisticCreatedActionForTransactionThread?.reportActionID]: {pendingAction: null}}, + }); + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThread.reportID}`, + value: {[optimisticCreatedActionForTransactionThread?.reportActionID]: {errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage')}}, + }); + } + + // Now, let's add the data we need just when we are creating a new chat report + if (isNewChat) { + successData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: {pendingFields: null, participants: redundantParticipants}, + }); + failureData.push( + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, + value: { + errorFields: { + createChat: getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), + }, + }, + }, + { + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, + value: { + [optimisticIOUReportAction.reportActionID]: { + errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), + }, + }, + }, + ); + + const optimisticChatReportActionsValue = optimisticChatReportActionsData.value as Record; + + if (optimisticChatReportActionsValue) { + // Add an optimistic created action to the optimistic chat reportActions data + optimisticChatReportActionsValue[optimisticCreatedActionForChat.reportActionID] = optimisticCreatedActionForChat; + } + } else { + failureData.push({ + onyxMethod: Onyx.METHOD.MERGE, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, + value: { + [optimisticIOUReportAction.reportActionID]: { + errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'), + }, + }, + }); + } + + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment + const optimisticData: OnyxUpdate[] = [ + optimisticChatReportData, + optimisticQuickActionData, + optimisticIOUReportData, + optimisticChatReportActionsData, + optimisticIOUReportActionsData, + optimisticTransactionData, + optimisticTransactionThreadData, + ...optimisticMetaData, + ]; + + if (optimisticTransactionThreadReportActionsData) { + optimisticData.push(optimisticTransactionThreadReportActionsData); + } + if (!isEmptyObject(optimisticPersonalDetailListData)) { + optimisticData.push(optimisticPersonalDetailListData); + } + + return { + params: { + iouReportID: optimisticIOUReport.reportID, + chatReportID: chatReport.reportID, + reportActionID: optimisticIOUReportAction.reportActionID, + paymentMethodType, + transactionID: optimisticTransaction.transactionID, + newIOUReportDetails, + createdReportActionID: isNewChat ? optimisticCreatedActionForChat.reportActionID : undefined, + reportPreviewReportActionID: reportPreviewAction.reportActionID, + createdIOUReportActionID: optimisticCreatedActionForIOUReport.reportActionID, + transactionThreadReportID: optimisticTransactionThread.reportID, + createdReportActionIDForThread: optimisticCreatedActionForTransactionThread?.reportActionID, + receipt, + receiptState: receipt?.state, + }, + optimisticData, + successData, + failureData, + }; +} + +/** + * @param managerID - Account ID of the person sending the money + * @param recipient - The user receiving the money + */ +function sendMoneyElsewhere( + report: OnyxEntry, + quickAction: OnyxEntry, + amount: number, + currency: string, + comment: string, + managerID: number, + recipient: Participant, + created?: string, + merchant?: string, + receipt?: Receipt, +) { + const {params, optimisticData, successData, failureData} = getSendMoneyParams({ + report, + quickAction, + amount, + currency, + commentParam: comment, + paymentMethodType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, + managerID, + recipient, + created, + merchant, + receipt, + }); + playSound(SOUNDS.DONE); + API.write(WRITE_COMMANDS.SEND_MONEY_ELSEWHERE, params, {optimisticData, successData, failureData}); + + dismissModalAndOpenReportInInboxTab(params.chatReportID); + notifyNewAction(params.chatReportID, managerID); +} + +/** + * @param managerID - Account ID of the person sending the money + * @param recipient - The user receiving the money + */ +function sendMoneyWithWallet( + report: OnyxEntry, + quickAction: OnyxEntry, + amount: number, + currency: string, + comment: string, + managerID: number, + recipient: Participant | OptionData, + created?: string, + merchant?: string, + receipt?: Receipt, +) { + const {params, optimisticData, successData, failureData} = getSendMoneyParams({ + report, + quickAction, + amount, + currency, + commentParam: comment, + paymentMethodType: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, + managerID, + recipient, + created, + merchant, + receipt, + }); + playSound(SOUNDS.DONE); + API.write(WRITE_COMMANDS.SEND_MONEY_WITH_WALLET, params, {optimisticData, successData, failureData}); + + dismissModalAndOpenReportInInboxTab(params.chatReportID); + notifyNewAction(params.chatReportID, managerID); +} + +export {sendMoneyElsewhere, sendMoneyWithWallet}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU/index.ts similarity index 97% rename from src/libs/actions/IOU.ts rename to src/libs/actions/IOU/index.ts index 618480ba9dce..991a123bab9d 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU/index.ts @@ -1,6 +1,6 @@ /* eslint-disable max-lines */ import {eachDayOfInterval, format} from 'date-fns'; -import {fastMerge, Str} from 'expensify-common'; +import {fastMerge} from 'expensify-common'; import cloneDeep from 'lodash/cloneDeep'; // eslint-disable-next-line you-dont-need-lodash-underscore/union-by import lodashUnionBy from 'lodash/unionBy'; @@ -36,7 +36,6 @@ import type { RetractReportParams, RevertSplitTransactionParams, SendInvoiceParams, - SendMoneyParams, SetNameValuePairParams, ShareTrackedExpenseParams, SplitBillParams, @@ -113,7 +112,7 @@ import { isMoneyRequestAction, isReportPreviewAction, } from '@libs/ReportActionsUtils'; -import type {OptimisticChatReport, OptimisticCreatedReportAction, OptimisticIOUReportAction, OptionData, TransactionDetails} from '@libs/ReportUtils'; +import type {OptimisticChatReport, OptimisticCreatedReportAction, OptimisticIOUReportAction, TransactionDetails} from '@libs/ReportUtils'; import { buildOptimisticActionableTrackExpenseWhisper, buildOptimisticAddCommentReportAction, @@ -236,6 +235,17 @@ import { removeTransactionFromDuplicateTransactionViolation, } from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; +import {clearByKey as clearPdfByOnyxKey} from '@userActions/CachedPDFPaths'; +import {buildAddMembersToWorkspaceOnyxData, buildUpdateWorkspaceMembersRoleOnyxData} from '@userActions/Policy/Member'; +// eslint-disable-next-line @typescript-eslint/no-deprecated +import {buildOptimisticRecentlyUsedCurrencies, buildPolicyData, generatePolicyID} from '@userActions/Policy/Policy'; +import {buildOptimisticPolicyRecentlyUsedTags, getPolicyTagsData} from '@userActions/Policy/Tag'; +import type {GuidedSetupData} from '@userActions/Report'; +import {buildInviteToRoomOnyxData, completeOnboarding, getCurrentUserAccountID, notifyNewAction, optimisticReportLastData} from '@userActions/Report'; +import {clearAllRelatedReportActionErrors} from '@userActions/ReportActions'; +import {sanitizeRecentWaypoints} from '@userActions/Transaction'; +import {removeDraftSplitTransaction, removeDraftTransaction, removeDraftTransactions} from '@userActions/TransactionEdit'; +import {getOnboardingMessages} from '@userActions/Welcome/OnboardingFlow'; import type {IOUAction, IOUActionParams, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import NAVIGATORS from '@src/NAVIGATORS'; @@ -256,17 +266,6 @@ import type {OnyxData} from '@src/types/onyx/Request'; import type {SearchTransaction} from '@src/types/onyx/SearchResults'; import type {Comment, Receipt, ReceiptSource, Routes, SplitShares, TransactionChanges, TransactionCustomUnit, WaypointCollection} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import {clearByKey as clearPdfByOnyxKey} from './CachedPDFPaths'; -import {buildAddMembersToWorkspaceOnyxData, buildUpdateWorkspaceMembersRoleOnyxData} from './Policy/Member'; -// eslint-disable-next-line @typescript-eslint/no-deprecated -import {buildOptimisticRecentlyUsedCurrencies, buildPolicyData, generatePolicyID} from './Policy/Policy'; -import {buildOptimisticPolicyRecentlyUsedTags, getPolicyTagsData} from './Policy/Tag'; -import type {GuidedSetupData} from './Report'; -import {buildInviteToRoomOnyxData, completeOnboarding, getCurrentUserAccountID, notifyNewAction, optimisticReportLastData} from './Report'; -import {clearAllRelatedReportActionErrors} from './ReportActions'; -import {sanitizeRecentWaypoints} from './Transaction'; -import {removeDraftSplitTransaction, removeDraftTransaction, removeDraftTransactions} from './TransactionEdit'; -import {getOnboardingMessages} from './Welcome/OnboardingFlow'; type IOURequestType = ValueOf; @@ -436,13 +435,6 @@ type PayMoneyRequestData = { failureData: OnyxUpdate[]; }; -type SendMoneyParamsData = { - params: SendMoneyParams; - optimisticData: OnyxUpdate[]; - successData: OnyxUpdate[]; - failureData: OnyxUpdate[]; -}; - type GPSPoint = { lat: number; long: number; @@ -9750,417 +9742,6 @@ function deleteTrackExpense({ return urlToNavigateBack; } -/** - * @param managerID - Account ID of the person sending the money - * @param recipient - The user receiving the money - */ -function getSendMoneyParams({ - report, - quickAction, - amount, - currency, - commentParam, - paymentMethodType, - managerID, - recipient, - created, - merchant, - receipt, -}: { - report: OnyxEntry; - quickAction: OnyxEntry; - amount: number; - currency: string; - commentParam: string; - paymentMethodType: PaymentMethodType; - managerID: number; - recipient: Participant; - created?: string; - merchant?: string; - receipt?: Receipt; -}): SendMoneyParamsData { - const recipientEmail = addSMSDomainIfPhoneNumber(recipient.login ?? ''); - const recipientAccountID = Number(recipient.accountID); - const comment = getParsedComment(commentParam); - const newIOUReportDetails = JSON.stringify({ - amount, - currency, - requestorEmail: recipientEmail, - requestorAccountID: recipientAccountID, - comment, - idempotencyKey: Str.guid(), - ...(created && {created}), - ...(merchant && {merchant}), - }); - - let chatReport = !isEmptyObject(report) && report?.reportID ? report : getChatByParticipants([recipientAccountID, managerID]); - let isNewChat = false; - if (!chatReport) { - chatReport = buildOptimisticChatReport({ - participantList: [recipientAccountID, managerID], - }); - isNewChat = true; - } - const optimisticIOUReport = buildOptimisticIOUReport(recipientAccountID, managerID, amount, chatReport.reportID, currency, true); - - const optimisticTransaction = buildOptimisticTransaction({ - transactionParams: { - amount, - currency, - reportID: optimisticIOUReport.reportID, - comment, - created, - merchant, - receipt, - }, - }); - const optimisticTransactionData: OnyxUpdate = { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, - value: optimisticTransaction, - }; - - const [optimisticCreatedActionForChat, optimisticCreatedActionForIOUReport, optimisticIOUReportAction, optimisticTransactionThread, optimisticCreatedActionForTransactionThread] = - buildOptimisticMoneyRequestEntities({ - iouReport: optimisticIOUReport, - type: CONST.IOU.REPORT_ACTION_TYPE.PAY, - amount, - currency, - comment, - payeeEmail: recipientEmail, - participants: [recipient], - transactionID: optimisticTransaction.transactionID, - paymentType: paymentMethodType, - isSendMoneyFlow: true, - }); - - const reportPreviewAction = buildOptimisticReportPreview(chatReport, optimisticIOUReport); - - // 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 - ? { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - ...chatReport, - // Set and clear pending fields on the chat report - pendingFields: {createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}, - lastReadTime: DateUtils.getDBTime(), - lastVisibleActionCreated: reportPreviewAction.created, - }, - } - : { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - ...chatReport, - lastReadTime: DateUtils.getDBTime(), - lastVisibleActionCreated: reportPreviewAction.created, - }, - }; - const optimisticQuickActionData: OnyxUpdate = { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, - value: { - action: CONST.QUICK_ACTIONS.SEND_MONEY, - chatReportID: chatReport.reportID, - isFirstQuickAction: isEmptyObject(quickAction), - }, - }; - const optimisticIOUReportData: OnyxUpdate = { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticIOUReport.reportID}`, - value: { - ...optimisticIOUReport, - lastMessageText: getReportActionText(optimisticIOUReportAction), - lastMessageHtml: getReportActionHtml(optimisticIOUReportAction), - pendingFields: { - createChat: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - }, - }, - }; - const optimisticTransactionThreadData: OnyxUpdate = { - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTransactionThread.reportID}`, - value: optimisticTransactionThread, - }; - const optimisticIOUReportActionsData: OnyxUpdate = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, - value: { - [optimisticCreatedActionForIOUReport.reportActionID]: optimisticCreatedActionForIOUReport, - [optimisticIOUReportAction.reportActionID]: { - ...(optimisticIOUReportAction as OnyxTypes.ReportAction), - pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD, - }, - }, - }; - const optimisticChatReportActionsData: OnyxUpdate = { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, - value: { - [reportPreviewAction.reportActionID]: reportPreviewAction, - }, - }; - const optimisticTransactionThreadReportActionsData: OnyxUpdate | undefined = optimisticCreatedActionForTransactionThread - ? { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThread.reportID}`, - value: {[optimisticCreatedActionForTransactionThread?.reportActionID]: optimisticCreatedActionForTransactionThread}, - } - : undefined; - - const optimisticMetaData: Array> = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${chatReport.reportID}`, - value: { - isOptimisticReport: true, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${optimisticTransactionThread.reportID}`, - value: { - isOptimisticReport: true, - }, - }, - ]; - - const successData: Array< - OnyxUpdate< - | typeof ONYXKEYS.PERSONAL_DETAILS_LIST - | typeof ONYXKEYS.COLLECTION.REPORT - | typeof ONYXKEYS.COLLECTION.REPORT_METADATA - | typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS - | typeof ONYXKEYS.COLLECTION.TRANSACTION - > - > = []; - - // Add optimistic personal details for recipient - let optimisticPersonalDetailListData: OnyxUpdate | null = null; - const optimisticPersonalDetailListAction = isNewChat - ? { - [recipientAccountID]: { - accountID: recipientAccountID, - // Disabling this line since participant.displayName can be an empty string - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - displayName: recipient.displayName || recipient.login, - login: recipient.login, - }, - } - : {}; - - const redundantParticipants: Record = {}; - if (!isEmptyObject(optimisticPersonalDetailListAction)) { - const successPersonalDetailListAction: Record = {}; - - // BE will send different participants. We clear the optimistic ones to avoid duplicated entries - for (const accountIDKey of Object.keys(optimisticPersonalDetailListAction)) { - const accountID = Number(accountIDKey); - successPersonalDetailListAction[accountID] = null; - redundantParticipants[accountID] = null; - } - - optimisticPersonalDetailListData = { - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - value: optimisticPersonalDetailListAction, - }; - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: ONYXKEYS.PERSONAL_DETAILS_LIST, - value: successPersonalDetailListAction, - }); - } - - successData.push( - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticIOUReport.reportID}`, - value: { - participants: redundantParticipants, - pendingFields: { - createChat: null, - }, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTransactionThread.reportID}`, - value: { - participants: redundantParticipants, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${optimisticTransactionThread.reportID}`, - value: { - isOptimisticReport: false, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, - value: { - [optimisticCreatedActionForIOUReport.reportActionID]: {pendingAction: null}, - [optimisticIOUReportAction.reportActionID]: { - pendingAction: null, - }, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, - value: {pendingAction: null}, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_METADATA}${chatReport.reportID}`, - value: { - isOptimisticReport: false, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, - value: { - [reportPreviewAction.reportActionID]: { - isOptimisticAction: null, - pendingAction: null, - childLastActorAccountID: reportPreviewAction.childLastActorAccountID, - }, - }, - }, - ); - - const failureData: Array< - OnyxUpdate - > = [ - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.TRANSACTION}${optimisticTransaction.transactionID}`, - value: { - errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'), - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${optimisticTransactionThread.reportID}`, - value: { - errorFields: { - createChat: getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), - }, - }, - }, - { - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, - value: quickAction ?? null, - }, - ]; - - if (optimisticCreatedActionForTransactionThread?.reportActionID) { - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThread.reportID}`, - value: {[optimisticCreatedActionForTransactionThread?.reportActionID]: {pendingAction: null}}, - }); - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticTransactionThread.reportID}`, - value: {[optimisticCreatedActionForTransactionThread?.reportActionID]: {errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage')}}, - }); - } - - // Now, let's add the data we need just when we are creating a new chat report - if (isNewChat) { - successData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: {pendingFields: null, participants: redundantParticipants}, - }); - failureData.push( - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, - value: { - errorFields: { - createChat: getMicroSecondOnyxErrorWithTranslationKey('report.genericCreateReportFailureMessage'), - }, - }, - }, - { - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, - value: { - [optimisticIOUReportAction.reportActionID]: { - errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), - }, - }, - }, - ); - - const optimisticChatReportActionsValue = optimisticChatReportActionsData.value as Record; - - if (optimisticChatReportActionsValue) { - // Add an optimistic created action to the optimistic chat reportActions data - optimisticChatReportActionsValue[optimisticCreatedActionForChat.reportActionID] = optimisticCreatedActionForChat; - } - } else { - failureData.push({ - onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${optimisticIOUReport.reportID}`, - value: { - [optimisticIOUReportAction.reportActionID]: { - errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.other'), - }, - }, - }); - } - - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - const optimisticData: OnyxUpdate[] = [ - optimisticChatReportData, - optimisticQuickActionData, - optimisticIOUReportData, - optimisticChatReportActionsData, - optimisticIOUReportActionsData, - optimisticTransactionData, - optimisticTransactionThreadData, - ...optimisticMetaData, - ]; - - if (optimisticTransactionThreadReportActionsData) { - optimisticData.push(optimisticTransactionThreadReportActionsData); - } - if (!isEmptyObject(optimisticPersonalDetailListData)) { - optimisticData.push(optimisticPersonalDetailListData); - } - - return { - params: { - iouReportID: optimisticIOUReport.reportID, - chatReportID: chatReport.reportID, - reportActionID: optimisticIOUReportAction.reportActionID, - paymentMethodType, - transactionID: optimisticTransaction.transactionID, - newIOUReportDetails, - createdReportActionID: isNewChat ? optimisticCreatedActionForChat.reportActionID : undefined, - reportPreviewReportActionID: reportPreviewAction.reportActionID, - createdIOUReportActionID: optimisticCreatedActionForIOUReport.reportActionID, - transactionThreadReportID: optimisticTransactionThread.reportID, - createdReportActionIDForThread: optimisticCreatedActionForTransactionThread?.reportActionID, - receipt, - receiptState: receipt?.state, - }, - optimisticData, - successData, - failureData, - }; -} - type OptimisticHoldReportExpenseActionID = { optimisticReportActionID: string; oldReportActionID: string; @@ -10765,78 +10346,6 @@ function getPayMoneyRequestParams({ }; } -/** - * @param managerID - Account ID of the person sending the money - * @param recipient - The user receiving the money - */ -function sendMoneyElsewhere( - report: OnyxEntry, - quickAction: OnyxEntry, - amount: number, - currency: string, - comment: string, - managerID: number, - recipient: Participant, - created?: string, - merchant?: string, - receipt?: Receipt, -) { - const {params, optimisticData, successData, failureData} = getSendMoneyParams({ - report, - quickAction, - amount, - currency, - commentParam: comment, - paymentMethodType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, - managerID, - recipient, - created, - merchant, - receipt, - }); - playSound(SOUNDS.DONE); - API.write(WRITE_COMMANDS.SEND_MONEY_ELSEWHERE, params, {optimisticData, successData, failureData}); - - dismissModalAndOpenReportInInboxTab(params.chatReportID); - notifyNewAction(params.chatReportID, managerID); -} - -/** - * @param managerID - Account ID of the person sending the money - * @param recipient - The user receiving the money - */ -function sendMoneyWithWallet( - report: OnyxEntry, - quickAction: OnyxEntry, - amount: number, - currency: string, - comment: string, - managerID: number, - recipient: Participant | OptionData, - created?: string, - merchant?: string, - receipt?: Receipt, -) { - const {params, optimisticData, successData, failureData} = getSendMoneyParams({ - report, - quickAction, - amount, - currency, - commentParam: comment, - paymentMethodType: CONST.IOU.PAYMENT_TYPE.EXPENSIFY, - managerID, - recipient, - created, - merchant, - receipt, - }); - playSound(SOUNDS.DONE); - API.write(WRITE_COMMANDS.SEND_MONEY_WITH_WALLET, params, {optimisticData, successData, failureData}); - - dismissModalAndOpenReportInInboxTab(params.chatReportID); - notifyNewAction(params.chatReportID, managerID); -} - function canApproveIOU(iouReport: OnyxTypes.OnyxInputOrEntry, policy: OnyxTypes.OnyxInputOrEntry, iouTransactions?: OnyxTypes.Transaction[]) { // Only expense reports can be approved if (!isExpenseReport(iouReport) || !(policy && isPaidGroupPolicy(policy))) { @@ -15566,8 +15075,6 @@ export { resetDraftTransactionsCustomUnit, savePreferredPaymentMethod, sendInvoice, - sendMoneyElsewhere, - sendMoneyWithWallet, setCustomUnitRateID, setCustomUnitID, removeSubrate, diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 461f71fc0239..23b8b4c69112 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -28,8 +28,6 @@ import { getMoneyRequestParticipantsFromReport, requestMoney, resetSplitShares, - sendMoneyElsewhere, - sendMoneyWithWallet, setDraftSplitTransaction, setMoneyRequestAmount, setMoneyRequestParticipantsFromReport, @@ -37,6 +35,7 @@ import { trackExpense, updateMoneyRequestAmountAndCurrency, } from '@userActions/IOU'; +import {sendMoneyElsewhere, sendMoneyWithWallet} from '@userActions/IOU/SendMoney'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index d1ad6564e9e2..2b39a1ab2e5f 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -77,8 +77,6 @@ import { getReceiverType, requestMoney as requestMoneyIOUActions, sendInvoice, - sendMoneyElsewhere, - sendMoneyWithWallet, setMoneyRequestBillable, setMoneyRequestCategory, setMoneyRequestParticipantsFromReport, @@ -92,6 +90,7 @@ import { trackExpense as trackExpenseIOUActions, updateLastLocationPermissionPrompt, } from '@userActions/IOU'; +import {sendMoneyElsewhere, sendMoneyWithWallet} from '@userActions/IOU/SendMoney'; import {openDraftWorkspaceRequest} from '@userActions/Policy/Policy'; import {removeDraftTransaction, removeDraftTransactions, replaceDefaultDraftTransaction} from '@userActions/TransactionEdit'; import CONST from '@src/CONST';