diff --git a/src/libs/TransactionUtils.js b/src/libs/TransactionUtils.js index a8fd828d07d4..cd24c252b35e 100644 --- a/src/libs/TransactionUtils.js +++ b/src/libs/TransactionUtils.js @@ -30,12 +30,23 @@ Onyx.connect({ * @param {String} [originalTransactionID] * @param {String} [merchant] * @param {Object} [receipt] + * @param {String} [existingTransactionID] When creating a distance request, an empty transaction has already been created with a transactionID. In that case, the transaction here needs to have it's transactionID match what was already generated. * @returns {Object} */ -function buildOptimisticTransaction(amount, currency, reportID, comment = '', source = '', originalTransactionID = '', merchant = CONST.REPORT.TYPE.IOU, receipt = {}) { +function buildOptimisticTransaction( + amount, + currency, + reportID, + comment = '', + source = '', + originalTransactionID = '', + merchant = CONST.REPORT.TYPE.IOU, + receipt = {}, + existingTransactionID = null, +) { // transactionIDs are random, positive, 64-bit numeric strings. // Because JS can only handle 53-bit numbers, transactionIDs are strings in the front-end (just like reportActionID) - const transactionID = NumberUtils.rand64(); + const transactionID = existingTransactionID || NumberUtils.rand64(); const commentJSON = {comment}; if (source) { diff --git a/src/libs/actions/IOU.js b/src/libs/actions/IOU.js index c5e2c532a888..83282dba5e13 100644 --- a/src/libs/actions/IOU.js +++ b/src/libs/actions/IOU.js @@ -302,19 +302,33 @@ function buildOnyxDataForMoneyRequest( } /** - * Request money from another user + * Gathers all the data needed to make a money request. It attempts to find existing reports, iouReports, and receipts. If it doesn't find them, then + * it creates optimistic versions of them and uses those instead * * @param {Object} report - * @param {Number} amount - always in the smallest unit of the currency - * @param {String} currency - * @param {String} payeeEmail - * @param {Number} payeeAccountID * @param {Object} participant * @param {String} comment + * @param {Number} amount + * @param {String} currency + * @param {Number} payeeAccountID + * @param {String} payeeEmail * @param {Object} [receipt] - * + * @returns {Object} data + * @returns {String} data.payerEmail + * @returns {Object} data.iouReport + * @returns {Object} data.chatReport + * @returns {Object} data.transaction + * @returns {Object} data.iouAction + * @returns {Object} data.createdChatReportActionID + * @returns {Object} data.createdIOUReportActionID + * @returns {Object} data.reportPreviewAction + * @returns {Object} data.onyxData + * @returns {Object} data.onyxData.optimisticData + * @returns {Object} data.onyxData.successData + * @returns {Object} data.onyxData.failureData + * @param {String} [existingTransactionID] */ -function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, participant, comment, receipt = undefined) { +function getMoneyRequestInformation(report, participant, comment, amount, currency, payeeAccountID, payeeEmail, receipt = undefined, existingTransactionID = null) { const payerEmail = OptionsListUtils.addSMSDomainIfPhoneNumber(participant.login); const payerAccountID = Number(participant.accountID); const isPolicyExpenseChat = participant.isPolicyExpenseChat; @@ -364,7 +378,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part receiptObject.source = receipt.source; receiptObject.state = CONST.IOU.RECEIPT_STATE.SCANREADY; } - const optimisticTransaction = TransactionUtils.buildOptimisticTransaction( + let optimisticTransaction = TransactionUtils.buildOptimisticTransaction( ReportUtils.isExpenseReport(iouReport) ? -amount : amount, currency, iouReport.reportID, @@ -373,8 +387,22 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part '', undefined, receiptObject, + existingTransactionID, ); + // If there is an existing transaction (which is the case for distance requests), then the data from the existing transaction + // needs to be manually merged into the optimistic transaction. This is because buildOnyxDataForMoneyRequest() uses `Onyx.set()` for the transaction + // data. This is a big can of worms to change it to `Onyx.merge()` as explored in https://expensify.slack.com/archives/C05DWUDHVK7/p1692139468252109. + // I want to clean this up at some point, but it's possible this will live in the code for a while so I've created https://github.com/Expensify/App/issues/25417 + // to remind me to do this. + const existingTransaction = existingTransactionID && TransactionUtils.getTransaction(existingTransactionID); + if (existingTransaction) { + optimisticTransaction = { + ...optimisticTransaction, + ...existingTransaction, + }; + } + // STEP 4: Build optimistic reportActions. We need: // 1. CREATED action for the chatReport // 2. CREATED action for the iouReport @@ -383,7 +411,7 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part // 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 optimisticCreatedActionForChat = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); const optimisticCreatedActionForIOU = ReportUtils.buildOptimisticCreatedReportAction(payeeEmail); - const optimisticIOUAction = ReportUtils.buildOptimisticIOUReportAction( + const iouAction = ReportUtils.buildOptimisticIOUReportAction( CONST.IOU.REPORT_ACTION_TYPE.CREATE, amount, currency, @@ -395,6 +423,13 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part receiptObject, ); + let reportPreviewAction = isNewIOUReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID); + if (reportPreviewAction) { + reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, comment); + } else { + reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment); + } + // Add optimistic personal details for participant const optimisticPersonalDetailListAction = isNewChatReport ? { @@ -407,13 +442,6 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part } : undefined; - let reportPreviewAction = isNewIOUReport ? null : ReportActionsUtils.getReportPreviewAction(chatReport.reportID, iouReport.reportID); - if (reportPreviewAction) { - reportPreviewAction = ReportUtils.updateReportPreview(iouReport, reportPreviewAction, comment); - } else { - reportPreviewAction = ReportUtils.buildOptimisticReportPreview(chatReport, iouReport, comment); - } - // STEP 5: Build Onyx Data const [optimisticData, successData, failureData] = buildOnyxDataForMoneyRequest( chatReport, @@ -421,14 +449,101 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part optimisticTransaction, optimisticCreatedActionForChat, optimisticCreatedActionForIOU, - optimisticIOUAction, + iouAction, optimisticPersonalDetailListAction, reportPreviewAction, isNewChatReport, isNewIOUReport, ); - // STEP 6: Make the request + return { + payerEmail, + iouReport, + chatReport, + transaction: optimisticTransaction, + iouAction, + createdChatReportActionID: isNewChatReport ? optimisticCreatedActionForChat.reportActionID : 0, + createdIOUReportActionID: isNewIOUReport ? optimisticCreatedActionForIOU.reportActionID : 0, + reportPreviewAction, + onyxData: { + optimisticData, + successData, + failureData, + }, + }; +} + +/** + * Requests money based on a distance (eg. mileage from a map) + * + * @param {Object} report + * @param {String} payeeEmail + * @param {Number} payeeAccountID + * @param {Object} participant + * @param {String} comment + * @param {Object[]} waypoints + * @param {String} waypoints[].address required and must be non empty + * @param {String} [waypoints[].lat] optional + * @param {String} [waypoints[].lng] optional + * @param {String} created + * @param {String} [transactionID] + */ +function createDistanceRequest(report, payeeEmail, payeeAccountID, participant, comment, waypoints, created, transactionID) { + const {payerEmail, iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation( + report, + participant, + comment, + 0, + 'USD', + payeeAccountID, + payeeEmail, + null, + transactionID, + ); + + API.write( + 'CreateDistanceRequest', + { + debtorEmail: payerEmail, + comment, + iouReportID: iouReport.reportID, + chatReportID: chatReport.reportID, + transactionID: transaction.transactionID, + reportActionID: iouAction.reportActionID, + createdChatReportActionID, + createdIOUReportActionID, + reportPreviewReportActionID: reportPreviewAction.reportActionID, + waypoints, + created, + }, + onyxData, + ); +} + +/** + * Request money from another user + * + * @param {Object} report + * @param {Number} amount - always in the smallest unit of the currency + * @param {String} currency + * @param {String} payeeEmail + * @param {Number} payeeAccountID + * @param {Object} participant + * @param {String} comment + * @param {Object} [receipt] + */ +function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, participant, comment, receipt = undefined) { + const {payerEmail, iouReport, chatReport, transaction, iouAction, createdChatReportActionID, createdIOUReportActionID, reportPreviewAction, onyxData} = getMoneyRequestInformation( + report, + participant, + comment, + amount, + currency, + payeeAccountID, + payeeEmail, + receipt, + ); + API.write( 'RequestMoney', { @@ -438,14 +553,14 @@ function requestMoney(report, amount, currency, payeeEmail, payeeAccountID, part comment, iouReportID: iouReport.reportID, chatReportID: chatReport.reportID, - transactionID: optimisticTransaction.transactionID, - reportActionID: optimisticIOUAction.reportActionID, - createdChatReportActionID: isNewChatReport ? optimisticCreatedActionForChat.reportActionID : 0, - createdIOUReportActionID: isNewIOUReport ? optimisticCreatedActionForIOU.reportActionID : 0, + transactionID: transaction.transactionID, + reportActionID: iouAction.reportActionID, + createdChatReportActionID, + createdIOUReportActionID, reportPreviewReportActionID: reportPreviewAction.reportActionID, receipt, }, - {optimisticData, successData, failureData}, + onyxData, ); resetMoneyRequestInfo(); Navigation.dismissModal(chatReport.reportID); @@ -1622,6 +1737,7 @@ function navigateToNextPage(iou, iouType, reportID, report) { } export { + createDistanceRequest, editMoneyRequest, deleteMoneyRequest, splitBill,