diff --git a/config/eslint/eslint.seatbelt.tsv b/config/eslint/eslint.seatbelt.tsv index 968073a518b3..86896a4902bb 100644 --- a/config/eslint/eslint.seatbelt.tsv +++ b/config/eslint/eslint.seatbelt.tsv @@ -632,7 +632,6 @@ "../../src/libs/actions/IOU/MoneyRequestBuilder.ts" "@typescript-eslint/no-deprecated/getReportTransactions" 2 "../../src/libs/actions/IOU/MoneyRequestBuilder.ts" "@typescript-eslint/no-deprecated/translateLocal" 1 "../../src/libs/actions/IOU/MoneyRequestBuilder.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 -"../../src/libs/actions/IOU/PayMoneyRequest.ts" "@typescript-eslint/no-deprecated/getReportTransactions" 2 "../../src/libs/actions/IOU/PayMoneyRequest.ts" "@typescript-eslint/no-unsafe-type-assertion" 2 "../../src/libs/actions/IOU/PayMoneyRequest.ts" "no-restricted-imports" 1 "../../src/libs/actions/IOU/PayMoneyRequest.ts" "no-restricted-syntax" 1 diff --git a/src/components/MoneyReportHeaderActions/MoneyReportHeaderSecondaryActions.tsx b/src/components/MoneyReportHeaderActions/MoneyReportHeaderSecondaryActions.tsx index 941fbf534f52..ea2554c1086e 100644 --- a/src/components/MoneyReportHeaderActions/MoneyReportHeaderSecondaryActions.tsx +++ b/src/components/MoneyReportHeaderActions/MoneyReportHeaderSecondaryActions.tsx @@ -156,7 +156,7 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo const {transactions: reportTransactions, violations} = useTransactionsAndViolationsForReport(moneyRequestReport?.reportID); // While offline, keep pending-delete transactions so a queued-for-delete expense still counts as a real transaction until the delete syncs. const nonPendingDeleteTransactions = Object.values(reportTransactions).filter((t) => isOffline || t.pendingAction !== CONST.RED_BRICK_ROAD_PENDING_ACTION.DELETE); - const allTransactions = Object.values(reportTransactions); + const moneyReportTransactions = Object.values(reportTransactions); const singleTransaction = nonPendingDeleteTransactions.length === 1 ? nonPendingDeleteTransactions.at(0) : undefined; const [originalTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(singleTransaction?.comment?.originalTransactionID)}`); const isTrackIntentUser = isTrackOnboardingChoice(introSelected?.choice); @@ -173,7 +173,7 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo const shouldCalculateTotals = useSearchShouldCalculateTotals(currentSearchKey, true); const isInvoiceReport = isInvoiceReportUtil(moneyRequestReport); - const isAnyTransactionOnHold = hasHeldExpensesReportUtils(allTransactions); + const isAnyTransactionOnHold = hasHeldExpensesReportUtils(moneyReportTransactions); const existingB2BInvoiceReport = useParticipantsInvoiceReport(activePolicyID, CONST.REPORT.INVOICE_RECEIVER_TYPE.BUSINESS, chatReport?.policyID); const getChatReportActions = usePayChatReportActions(chatReport, existingB2BInvoiceReport); @@ -209,6 +209,7 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo paymentMethodType: type, chatReport, invoiceReport: moneyRequestReport, + invoiceReportTransactions: moneyReportTransactions, introSelected, currentUserAccountIDParam: accountID, currentUserEmailParam: email ?? '', @@ -234,6 +235,7 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo paymentType: type, chatReport, iouReport: moneyRequestReport, + iouReportTransactions: moneyReportTransactions, introSelected, currentUserAccountID: accountID, currentUserLogin: currentUserLogin ?? '', @@ -283,9 +285,10 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo !canIOUBePaid && canIOUBePaidAction(moneyRequestReport, chatReport, policy, bankAccountList, currentUserLogin ?? '', accountID, undefined, true, isChatReportArchived, invoiceReceiverPolicy); const shouldShowPayButton = isPaidAnimationRunning || canIOUBePaid || onlyShowPayElsewhere; - const hasOnlyPendingTransactions = allTransactions.length > 0 && allTransactions.every((t) => isPending(t)); + const hasOnlyPendingTransactions = moneyReportTransactions.length > 0 && moneyReportTransactions.every((t) => isPending(t)); const shouldShowApproveButton = - (canApproveIOU(moneyRequestReport, policy, reportMetadata, currentUserPersonalDetails.accountID, allTransactions) && !hasOnlyPendingTransactions) || isApprovedAnimationRunning; + (canApproveIOU(moneyRequestReport, policy, reportMetadata, currentUserPersonalDetails.accountID, moneyReportTransactions) && !hasOnlyPendingTransactions) || + isApprovedAnimationRunning; const isApproveDisabled = shouldShowApproveButton && !isAllowedToApproveExpenseReport(moneyRequestReport); const totalAmount = getTotalAmountForIOUReportPreviewButton(moneyRequestReport, policy, CONST.REPORT.PRIMARY_ACTIONS.PAY, nonPendingDeleteTransactions, convertToDisplayString); diff --git a/src/components/MoneyReportHeaderPrimaryAction/PayPrimaryAction.tsx b/src/components/MoneyReportHeaderPrimaryAction/PayPrimaryAction.tsx index 8da49d2a3f78..d3486ca92b44 100644 --- a/src/components/MoneyReportHeaderPrimaryAction/PayPrimaryAction.tsx +++ b/src/components/MoneyReportHeaderPrimaryAction/PayPrimaryAction.tsx @@ -146,6 +146,7 @@ function PayPrimaryAction({reportID, chatReportID, isDisabled}: PayPrimaryAction paymentMethodType: type, chatReport, invoiceReport: moneyRequestReport, + invoiceReportTransactions: transactions, introSelected, currentUserAccountIDParam: accountID, currentUserEmailParam: email ?? '', @@ -171,6 +172,7 @@ function PayPrimaryAction({reportID, chatReportID, isDisabled}: PayPrimaryAction paymentType: type, chatReport, iouReport: moneyRequestReport, + iouReportTransactions: transactions, introSelected, currentUserAccountID: accountID, currentUserLogin: currentUserLogin ?? '', diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/PayActionButton.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/PayActionButton.tsx index 07856fdb2e3b..fe87858db3e8 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/PayActionButton.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/PayActionButton.tsx @@ -91,6 +91,7 @@ function PayActionButton() { paymentMethodType: type, chatReport: currentChatReport, invoiceReport: iouReport, + invoiceReportTransactions: transactions, introSelected, currentUserAccountIDParam: currentUserAccountID, currentUserEmailParam: currentUserEmail, @@ -115,6 +116,7 @@ function PayActionButton() { paymentType: type, chatReport: currentChatReport, iouReport, + iouReportTransactions: transactions, introSelected, currentUserAccountID, currentUserLogin: currentUserDetails.login ?? '', diff --git a/src/components/Search/SearchList/ListItem/ActionCell/PayActionCell.tsx b/src/components/Search/SearchList/ListItem/ActionCell/PayActionCell.tsx index 32c772abe7f4..94bfdaccaa8e 100644 --- a/src/components/Search/SearchList/ListItem/ActionCell/PayActionCell.tsx +++ b/src/components/Search/SearchList/ListItem/ActionCell/PayActionCell.tsx @@ -140,6 +140,7 @@ function PayActionCell({isLoading, policyID, reportID, hash, amount, shouldDisab paymentMethodType: type, chatReport, invoiceReport: iouReport, + invoiceReportTransactions: transactions, introSelected, currentUserAccountIDParam: currentUserAccountID, currentUserEmailParam: email ?? '', @@ -178,6 +179,7 @@ function PayActionCell({isLoading, policyID, reportID, hash, amount, shouldDisab chatReport: chatReportForPayment, isFallbackChatReport, iouReport, + iouReportTransactions: transactions, introSelected, currentUserAccountID, currentUserLogin: currentUserLogin ?? '', diff --git a/src/hooks/useHoldMenuSubmit.ts b/src/hooks/useHoldMenuSubmit.ts index b21e8da14ec1..e9f51000950e 100644 --- a/src/hooks/useHoldMenuSubmit.ts +++ b/src/hooks/useHoldMenuSubmit.ts @@ -26,6 +26,7 @@ import usePayChatReportActions from './usePayChatReportActions'; import usePermissions from './usePermissions'; import {usePersonalDetail} from './usePersonalDetails'; import usePolicy from './usePolicy'; +import useReportTransactions from './useReportTransactions'; type ActionHandledType = DeepValueOf; @@ -47,6 +48,7 @@ function useHoldMenuSubmit({moneyRequestReport, chatReport, requestType, payment const activePolicy = usePolicy(activePolicyID); const policy = usePolicy(moneyRequestReport?.policyID); const chatReportPolicy = usePolicy(chatReport?.policyID); + const moneyRequestReportTransactions = useReportTransactions(moneyRequestReport?.reportID); const getChatReportActions = usePayChatReportActions(chatReport, undefined); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [delegateEmail] = useOnyx(ONYXKEYS.ACCOUNT, {selector: delegateEmailSelector}); @@ -111,6 +113,7 @@ function useHoldMenuSubmit({moneyRequestReport, chatReport, requestType, payment paymentType, chatReport: currentChatReport, iouReport: currentMoneyRequestReport, + iouReportTransactions: moneyRequestReportTransactions, introSelected, currentUserAccountID: currentUserDetails.accountID, currentUserLogin: currentUserDetails.login ?? '', diff --git a/src/hooks/useSearchBulkActions.ts b/src/hooks/useSearchBulkActions.ts index 3e4cdabb9dbf..10841db48437 100644 --- a/src/hooks/useSearchBulkActions.ts +++ b/src/hooks/useSearchBulkActions.ts @@ -1713,6 +1713,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { } const isItemInvoice = isInvoiceReport(iouReport); + const iouReportTransactions = Object.values(allReportsTransactionsAndViolations?.[item.reportID]?.transactions ?? {}); const fallbackChatReportID = item.chatReportID ?? iouReport.chatReportID ?? iouReport.parentReportID; const fallbackPolicyID = iouReport.policyID ?? item.policyID; const {chatReport, isFallbackChatReport} = getChatReportWithFallback( @@ -1779,6 +1780,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { paymentMethodType: paymentItem.paymentType as PaymentMethodType, chatReport, invoiceReport: iouReport, + invoiceReportTransactions: iouReportTransactions, introSelected, currentUserAccountIDParam: accountID, currentUserEmailParam: email ?? '', @@ -1808,6 +1810,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { paymentType: paymentItem.paymentType as PaymentMethodType, chatReport, iouReport, + iouReportTransactions, introSelected, currentUserAccountID: accountID, currentUserLogin: currentUserLogin ?? '', @@ -1864,6 +1867,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { personalPolicyID, lastPaymentMethods, allTransactions, + allReportsTransactionsAndViolations, policyIDsWithVBBA, clearSelectedTransactions, accountID, diff --git a/src/hooks/useSelectionModePayment.ts b/src/hooks/useSelectionModePayment.ts index 60aaa6b22843..6afa44b92463 100644 --- a/src/hooks/useSelectionModePayment.ts +++ b/src/hooks/useSelectionModePayment.ts @@ -193,6 +193,7 @@ function useSelectionModePayment({ paymentMethodType: type, chatReport, invoiceReport: moneyRequestReport, + invoiceReportTransactions: transactions, introSelected, currentUserAccountIDParam: accountID, currentUserEmailParam: email ?? '', @@ -217,6 +218,7 @@ function useSelectionModePayment({ paymentType: type, chatReport, iouReport: moneyRequestReport, + iouReportTransactions: transactions, introSelected, currentUserAccountID: accountID, currentUserLogin: currentUserLogin ?? '', diff --git a/src/libs/actions/IOU/PayMoneyRequest.ts b/src/libs/actions/IOU/PayMoneyRequest.ts index 56f7675d202f..a2090468c2f5 100644 --- a/src/libs/actions/IOU/PayMoneyRequest.ts +++ b/src/libs/actions/IOU/PayMoneyRequest.ts @@ -17,7 +17,6 @@ import { buildOptimisticCancelPaymentReportAction, buildOptimisticIOUReportAction, getReimbursableTotal, - getReportTransactions, getUnheldReimbursableTotal, hasHeldExpenses as hasHeldExpensesReportUtils, hasOutstandingChildRequest, @@ -63,6 +62,7 @@ type PayInvoiceArgs = { paymentMethodType: PaymentMethodType; chatReport: OnyxTypes.Report; invoiceReport: OnyxEntry; + invoiceReportTransactions: OnyxTypes.Transaction[]; introSelected: OnyxEntry; currentUserAccountIDParam: number; currentUserEmailParam: string; @@ -102,6 +102,7 @@ type PayMoneyRequestFunctionParams = { paymentType: PaymentMethodType; chatReport: OnyxTypes.Report; iouReport: OnyxEntry; + iouReportTransactions: OnyxTypes.Transaction[]; introSelected: OnyxEntry; currentUserAccountID: number; currentUserLogin: string; @@ -131,6 +132,7 @@ type PayMoneyRequestFunctionParams = { function getPayMoneyRequestParams({ initialChatReport, iouReport, + iouReportTransactions, recipient, paymentMethodType, full, @@ -158,6 +160,7 @@ function getPayMoneyRequestParams({ }: { initialChatReport: OnyxTypes.Report; iouReport: OnyxEntry; + iouReportTransactions: OnyxTypes.Transaction[]; recipient: Participant; paymentMethodType: PaymentMethodType; full: boolean; @@ -254,14 +257,13 @@ function getPayMoneyRequestParams({ chatReport = existingB2BInvoiceReport; } - const reportTransactions = getReportTransactions(iouReport?.reportID); const unheldReimbursableTotal = getUnheldReimbursableTotal(iouReport); let total = getReimbursableTotal(iouReport); - if (hasHeldExpensesReportUtils(reportTransactions) && !full && !!unheldReimbursableTotal) { + if (hasHeldExpensesReportUtils(iouReportTransactions) && !full && !!unheldReimbursableTotal) { total = unheldReimbursableTotal; } - const shouldMoveScanFailedTransactions = !!full && isExpenseReport(iouReport) && shouldSplitScanFailedTransactions(reportTransactions, iouReport); + const shouldMoveScanFailedTransactions = !!full && isExpenseReport(iouReport) && shouldSplitScanFailedTransactions(iouReportTransactions, iouReport); const optimisticIOUReportAction = buildOptimisticIOUReportAction({ type: CONST.IOU.REPORT_ACTION_TYPE.PAY, @@ -439,7 +441,6 @@ function getPayMoneyRequestParams({ // Optimistically unhold all transactions if we pay all requests if (full) { - const iouReportTransactions = getReportTransactions(iouReport?.reportID); for (const transaction of iouReportTransactions) { onyxData.optimisticData?.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -868,6 +869,7 @@ function payMoneyRequest(params: PayMoneyRequestFunctionParams) { paymentType, chatReport, iouReport, + iouReportTransactions, introSelected, currentUserAccountID, currentUserLogin, @@ -910,6 +912,7 @@ function payMoneyRequest(params: PayMoneyRequestFunctionParams) { const {params: payMoneyRequestParams, onyxData} = getPayMoneyRequestParams({ initialChatReport: chatReport, iouReport, + iouReportTransactions, recipient, paymentMethodType: paymentType, full, @@ -1116,6 +1119,7 @@ function payInvoice({ paymentMethodType, chatReport, invoiceReport, + invoiceReportTransactions, introSelected, currentUserAccountIDParam, currentUserEmailParam, @@ -1155,6 +1159,7 @@ function payInvoice({ } = getPayMoneyRequestParams({ initialChatReport: chatReport, iouReport: invoiceReport, + iouReportTransactions: invoiceReportTransactions, recipient, paymentMethodType, full: true, diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 4e0bbab9bcb4..c2e3c1494f28 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -387,6 +387,7 @@ function handleActionButtonPress({ conciergeChat, getCurrencyDecimals, rules, + iouReportTransactions: allReportTransactions, }); return; case CONST.SEARCH.ACTION_TYPES.APPROVE: @@ -636,6 +637,7 @@ type GetPayActionCallbackParams = { conciergeChat: OnyxEntry; getCurrencyDecimals: CurrencyListActionsContextType['getCurrencyDecimals']; rules: OnyxCollection; + iouReportTransactions: Transaction[]; }; function getPayActionCallback({ @@ -666,6 +668,7 @@ function getPayActionCallback({ conciergeChat, getCurrencyDecimals, rules, + iouReportTransactions, }: GetPayActionCallbackParams) { if (!item.reportID) { Log.info('[SearchPay] Dropping row pay: item has no reportID'); @@ -698,6 +701,7 @@ function getPayActionCallback({ paymentType: lastPolicyPaymentMethod, chatReport: chatReportForPayment, iouReport: snapshotReport, + iouReportTransactions, introSelected, currentUserAccountID: currentUserAccountID ?? CONST.DEFAULT_NUMBER_ID, currentUserLogin: currentUserLogin ?? '', diff --git a/tests/actions/IOUTest/PayMoneyRequestTest.ts b/tests/actions/IOUTest/PayMoneyRequestTest.ts index fd8b2e7243ef..03666b907dfe 100644 --- a/tests/actions/IOUTest/PayMoneyRequestTest.ts +++ b/tests/actions/IOUTest/PayMoneyRequestTest.ts @@ -25,7 +25,7 @@ import type Transaction from '@src/types/onyx/Transaction'; import type {TransactionCollectionDataSet} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import type {OnyxEntry, OnyxInputValue} from 'react-native-onyx'; +import type {OnyxCollection, OnyxEntry, OnyxInputValue} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; @@ -93,6 +93,18 @@ const RORY_EMAIL = 'rory@expensifail.com'; const RORY_ACCOUNT_ID = 3; OnyxUpdateManager(); +/** Reads the expenses currently in Onyx for a report, i.e. what a component would pass as the report's transactions. */ +async function getReportTransactionsFromOnyx(reportID: string | undefined): Promise { + let allTransactions: OnyxCollection; + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + callback: (transactions) => { + allTransactions = transactions; + }, + }); + return Object.values(allTransactions ?? {}).filter((transaction): transaction is Transaction => !!transaction && transaction.reportID === reportID); +} + describe('actions/IOU/PayMoneyRequest', () => { beforeAll(() => { Onyx.init({ @@ -237,17 +249,19 @@ describe('actions/IOU/PayMoneyRequest', () => { }); }), ) - .then(() => { + .then(async () => { mockFetch?.pause?.(); if (!chatReport || !iouReport) { return waitForBatchedUpdates(); } + const iouReportTransactions = await getReportTransactionsFromOnyx(iouReport.reportID); payMoneyRequest({ isASAPSubmitBetaEnabled: false, conciergeChat: undefined, paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport, + iouReportTransactions, introSelected: undefined, currentUserAccountID, currentUserLogin, @@ -463,16 +477,18 @@ describe('actions/IOU/PayMoneyRequest', () => { }); }), ) - .then(() => { + .then(async () => { if (!chatReport || !expenseReport) { return waitForBatchedUpdates(); } + const iouReportTransactions = await getReportTransactionsFromOnyx(expenseReport.reportID); payMoneyRequest({ isASAPSubmitBetaEnabled: false, conciergeChat: undefined, paymentType: CONST.IOU.PAYMENT_TYPE.VBBA, chatReport, iouReport: expenseReport, + iouReportTransactions, introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -643,17 +659,19 @@ describe('actions/IOU/PayMoneyRequest', () => { }); }), ) - .then(() => { + .then(async () => { mockFetch?.fail?.(); if (!chatReport || !expenseReport) { return waitForBatchedUpdates(); } + const iouReportTransactions = await getReportTransactionsFromOnyx(expenseReport.reportID); payMoneyRequest({ isASAPSubmitBetaEnabled: false, conciergeChat: undefined, paymentType: 'ACH', chatReport, iouReport: expenseReport, + iouReportTransactions, introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -712,6 +730,7 @@ describe('actions/IOU/PayMoneyRequest', () => { paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport, + iouReportTransactions: [], introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -778,6 +797,65 @@ describe('actions/IOU/PayMoneyRequest', () => { mockFetch?.resume?.(); }); + it('puts the payment failure error on the chat report preview, not only inside the expense report', async () => { + // Given an outstanding IOU report that is previewed in the chat the payer is looking at + const chatReport = { + ...createRandomReport(20, undefined), + lastReadTime: DateUtils.getDBTime(), + lastVisibleActionCreated: DateUtils.getDBTime(), + }; + const iouReport = { + ...createRandomReport(21, undefined), + chatType: undefined, + type: CONST.REPORT.TYPE.IOU, + chatReportID: chatReport.reportID, + total: 10, + }; + const reportPreviewAction: ReportAction = { + ...createRandomReportAction(22), + actionName: CONST.REPORT.ACTIONS.TYPE.REPORT_PREVIEW, + originalMessage: {linkedReportID: iouReport.reportID}, + }; + const chatReportActions: ReportActions = {[reportPreviewAction.reportActionID]: reportPreviewAction}; + + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${chatReport.reportID}`, chatReport); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${iouReport.reportID}`, iouReport); + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}`, chatReportActions); + await waitForBatchedUpdates(); + + // When the server rejects the payment, which is what happens to a payment queued offline against an + // expense the requestor deleted in the meantime + mockFetch?.fail?.(); + payMoneyRequest({ + isASAPSubmitBetaEnabled: false, + conciergeChat: undefined, + paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, + chatReport, + iouReport, + iouReportTransactions: [], + introSelected: undefined, + currentUserAccountID: CARLOS_ACCOUNT_ID, + currentUserLogin: CARLOS_EMAIL, + isSelfTourViewed: false, + userBillingGracePeriodEnds: undefined, + amountOwed: 0, + chatReportPolicy: chatReportPolicyFromChat(chatReport), + chatReportActions, + delegateAccountID: undefined, + isTrackIntentUser: false, + getCurrencyDecimals: getCurrencyDecimalsLocal, + rules: undefined, + }); + await waitForBatchedUpdates(); + + // Then the error is on the preview in the chat. The pay action also carries it, but that action lives in + // the expense report, which the payer can no longer reach once it has been deleted. + const updatedChatActions = await getOnyxValue(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport.reportID}` as const); + expect(updatedChatActions?.[reportPreviewAction.reportActionID]?.errors).toBeTruthy(); + + mockFetch?.succeed?.(); + }); + describe('delegateAccountID forwarding', () => { it('sets delegateAccountID on the pay IOU action when delegateAccountID is provided', async () => { const DELEGATE_ACCOUNT_ID = 999; @@ -800,6 +878,7 @@ describe('actions/IOU/PayMoneyRequest', () => { paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport, + iouReportTransactions: [], introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -913,6 +992,7 @@ describe('actions/IOU/PayMoneyRequest', () => { paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport: partialPayChatReport, iouReport, + iouReportTransactions: [transaction1, transaction2], introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -1013,6 +1093,7 @@ describe('actions/IOU/PayMoneyRequest', () => { paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport: expenseReport, + iouReportTransactions: [], introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -1115,11 +1196,12 @@ describe('actions/IOU/PayMoneyRequest', () => { return expenseReport; }; - const pay = (expenseReport: Report) => + const pay = (expenseReport: Report, iouReportTransactions: Transaction[]) => payMoneyRequest({ paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport: expenseReport, + iouReportTransactions, introSelected: undefined, currentUserAccountID: RORY_ACCOUNT_ID, currentUserLogin: RORY_EMAIL, @@ -1143,7 +1225,7 @@ describe('actions/IOU/PayMoneyRequest', () => { const scanFailedTransaction = buildTransaction('scanFailed1', 0, true); const expenseReport = await setUpReport([validTransaction, scanFailedTransaction]); - const newReportID = pay(expenseReport); + const newReportID = pay(expenseReport, [validTransaction, scanFailedTransaction]); await waitForBatchedUpdates(); expect(newReportID).toBeDefined(); @@ -1161,7 +1243,7 @@ describe('actions/IOU/PayMoneyRequest', () => { const scanFailedTransaction = buildTransaction('scanFailed1', 0, true); const expenseReport = await setUpReport([scanFailedTransaction]); - const newReportID = pay(expenseReport); + const newReportID = pay(expenseReport, [scanFailedTransaction]); await waitForBatchedUpdates(); expect(newReportID).toBeUndefined(); @@ -1174,7 +1256,7 @@ describe('actions/IOU/PayMoneyRequest', () => { const scanFailedTransaction = buildTransaction('scanFailed1', -5000, true); const expenseReport = await setUpReport([validTransaction, scanFailedTransaction]); - const newReportID = pay(expenseReport); + const newReportID = pay(expenseReport, [validTransaction, scanFailedTransaction]); await waitForBatchedUpdates(); expect(newReportID).toBeUndefined(); @@ -1210,6 +1292,7 @@ describe('actions/IOU/PayMoneyRequest', () => { paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport, + iouReportTransactions: [], introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -1265,6 +1348,7 @@ describe('actions/IOU/PayMoneyRequest', () => { paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport, + iouReportTransactions: [], introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -1440,6 +1524,7 @@ describe('actions/IOU/PayMoneyRequest', () => { paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport, + iouReportTransactions: [], introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -1514,6 +1599,7 @@ describe('actions/IOU/PayMoneyRequest', () => { paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport, + iouReportTransactions: [], introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -1578,6 +1664,7 @@ describe('actions/IOU/PayMoneyRequest', () => { paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport, + iouReportTransactions: [], introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -1621,6 +1708,7 @@ describe('actions/IOU/PayMoneyRequest', () => { paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport, + iouReportTransactions: [], introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -2049,17 +2137,19 @@ describe('actions/IOU/PayMoneyRequest', () => { }, }), ) - .then(() => { + .then(async () => { // When the expense report is paid elsewhere (but really, any payment option would work) if (!chatReport || !expenseReport) { return waitForBatchedUpdates(); } + const iouReportTransactions = await getReportTransactionsFromOnyx(expenseReport.reportID); payMoneyRequest({ isASAPSubmitBetaEnabled: false, conciergeChat: undefined, paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport: expenseReport, + iouReportTransactions, introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL, @@ -2565,12 +2655,14 @@ describe('actions/IOU/PayMoneyRequest', () => { // When the expense report is paid elsewhere (but really, any payment option would work) if (chatReport && expenseReport) { mockFetch?.pause?.(); + const iouReportTransactions = await getReportTransactionsFromOnyx(expenseReport.reportID); payMoneyRequest({ isASAPSubmitBetaEnabled: false, conciergeChat: undefined, paymentType: CONST.IOU.PAYMENT_TYPE.ELSEWHERE, chatReport, iouReport: expenseReport, + iouReportTransactions, introSelected: undefined, currentUserAccountID: CARLOS_ACCOUNT_ID, currentUserLogin: CARLOS_EMAIL,