diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index 98877386d74ed..2241b85c27525 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -76,6 +76,7 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles, dismissErr prompt: translate('common.genericErrorMessage'), confirmText: translate('common.ok'), shouldShowCancelButton: false, + shouldHandleNavigationBack: false, }); }); } else if (href.endsWith('download')) { diff --git a/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts b/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts index a8e7dae2cc1b7..bbe4c240f462a 100644 --- a/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts +++ b/src/libs/ReceiptUploadRetryHandler/handleFileRetry.ts @@ -1,6 +1,8 @@ import * as IOU from '@userActions/IOU'; import {startSplitBill} from '@userActions/IOU/Split'; +import {clearError} from '@userActions/Transaction'; import CONST from '@src/CONST'; +import type Transaction from '@src/types/onyx/Transaction'; import type {ReceiptError} from '@src/types/onyx/Transaction'; export default function handleFileRetry(message: ReceiptError, file: File, dismissError: () => void, setShouldShowErrorModal: (value: boolean) => void) { @@ -9,16 +11,27 @@ export default function handleFileRetry(message: ReceiptError, file: File, dismi ? (JSON.parse(message.retryParams) as IOU.ReplaceReceipt | IOU.StartSplitBilActionParams | IOU.CreateTrackExpenseParams | IOU.RequestMoneyInformation) : message.retryParams; + // Use non-destructive error clearing when transactionID is available. + // The full dismissError() triggers cleanUpMoneyRequest() for pending ADD transactions, + // which deletes the transaction/report and navigates away before the retry can complete. + const clearReceiptError = () => { + if (message.transactionID) { + clearError(message.transactionID); + } else { + dismissError(); + } + }; + switch (message.action) { case CONST.IOU.ACTION_PARAMS.REPLACE_RECEIPT: { - dismissError(); + clearReceiptError(); const replaceReceiptParams = {...retryParams} as IOU.ReplaceReceipt; replaceReceiptParams.file = file; IOU.replaceReceipt(replaceReceiptParams); break; } case CONST.IOU.ACTION_PARAMS.START_SPLIT_BILL: { - dismissError(); + clearReceiptError(); const startSplitBillParams = {...retryParams} as IOU.StartSplitBilActionParams; startSplitBillParams.receipt = file; startSplitBillParams.shouldPlaySound = false; @@ -26,20 +39,32 @@ export default function handleFileRetry(message: ReceiptError, file: File, dismi break; } case CONST.IOU.ACTION_PARAMS.TRACK_EXPENSE: { - dismissError(); + clearReceiptError(); const trackExpenseParams = {...retryParams} as IOU.CreateTrackExpenseParams; trackExpenseParams.transactionParams.receipt = file; trackExpenseParams.isRetry = true; trackExpenseParams.shouldPlaySound = false; + trackExpenseParams.shouldHandleNavigation = false; + // Reuse the existing transaction so retry errors appear on the same + // transaction the user is viewing instead of creating a new one. + if (message.transactionID) { + trackExpenseParams.existingTransaction = {transactionID: message.transactionID} as Transaction; + } IOU.trackExpense(trackExpenseParams); break; } case CONST.IOU.ACTION_PARAMS.MONEY_REQUEST: { - dismissError(); + clearReceiptError(); const requestMoneyParams = {...retryParams} as IOU.RequestMoneyInformation; requestMoneyParams.transactionParams.receipt = file; requestMoneyParams.isRetry = true; requestMoneyParams.shouldPlaySound = false; + requestMoneyParams.shouldHandleNavigation = false; + // Reuse the existing transaction so retry errors appear on the same + // transaction the user is viewing instead of creating a new one. + if (message.transactionID) { + requestMoneyParams.existingTransactionDraft = {transactionID: message.transactionID} as Transaction; + } IOU.requestMoney(requestMoneyParams); break; } diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index 26262802c5688..4cc0b699a76ef 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -610,7 +610,7 @@ function duplicateExpenseTransaction({ iouRequestType: getRequestType(transaction), modifiedCreated: '', reportID: '1', - transactionID: '1', + transactionID: NumberUtils.rand64(), }, transactionParams: { ...(params.transactionParams ?? {}), @@ -652,7 +652,7 @@ function duplicateExpenseTransaction({ iouRequestType: getRequestType(transaction), modifiedCreated: '', reportID: '1', - transactionID: '1', + transactionID: NumberUtils.rand64(), }, transactionParams: { ...(params.transactionParams ?? {}), diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 527ac81709797..18aea7501205b 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -1748,6 +1748,7 @@ function getReceiptError( errorKey?: number, action?: IOUActionParams, retryParams?: StartSplitBilActionParams | CreateTrackExpenseParams | RequestMoneyInformation | ReplaceReceipt, + transactionID?: string, ): Errors | ErrorFields { const formattedRetryParams = typeof retryParams === 'string' ? retryParams : JSON.stringify(retryParams); @@ -1760,6 +1761,7 @@ function getReceiptError( filename: filename ?? '', action: action ?? '', retryParams: formattedRetryParams, + ...(transactionID && {transactionID}), }, errorKey, ); @@ -2394,7 +2396,15 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, value: { - errors: getReceiptError(transaction.receipt, transaction.receipt?.filename, isScanRequest, errorKey, CONST.IOU.ACTION_PARAMS.MONEY_REQUEST, retryParams), + errors: getReceiptError( + transaction.receipt, + transaction.receipt?.filename, + isScanRequest, + errorKey, + CONST.IOU.ACTION_PARAMS.MONEY_REQUEST, + retryParams, + transaction.transactionID, + ), pendingFields: clearedPendingFields, }, }, @@ -2405,7 +2415,15 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR ...(shouldCreateNewMoneyRequestReport ? { [iou.createdAction.reportActionID]: { - errors: getReceiptError(transaction.receipt, transaction.receipt?.filename, isScanRequest, errorKey, CONST.IOU.ACTION_PARAMS.MONEY_REQUEST, retryParams), + errors: getReceiptError( + transaction.receipt, + transaction.receipt?.filename, + isScanRequest, + errorKey, + CONST.IOU.ACTION_PARAMS.MONEY_REQUEST, + retryParams, + transaction.transactionID, + ), }, [iou.action.reportActionID]: { errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), @@ -2413,7 +2431,15 @@ function buildOnyxDataForMoneyRequest(moneyRequestParams: BuildOnyxDataForMoneyR } : { [iou.action.reportActionID]: { - errors: getReceiptError(transaction.receipt, transaction.receipt?.filename, isScanRequest, errorKey, CONST.IOU.ACTION_PARAMS.MONEY_REQUEST, retryParams), + errors: getReceiptError( + transaction.receipt, + transaction.receipt?.filename, + isScanRequest, + errorKey, + CONST.IOU.ACTION_PARAMS.MONEY_REQUEST, + retryParams, + transaction.transactionID, + ), }, }), }, @@ -2898,7 +2924,15 @@ function buildOnyxDataForTrackExpense({ ...(shouldCreateNewMoneyRequestReport ? { [iouCreatedAction.reportActionID]: { - errors: getReceiptError(transaction.receipt, transaction.receipt?.filename, isScanRequest, undefined, CONST.IOU.ACTION_PARAMS.TRACK_EXPENSE, retryParams), + errors: getReceiptError( + transaction.receipt, + transaction.receipt?.filename, + isScanRequest, + undefined, + CONST.IOU.ACTION_PARAMS.TRACK_EXPENSE, + retryParams, + transaction.transactionID, + ), }, [iouAction.reportActionID]: { errors: getMicroSecondOnyxErrorWithTranslationKey('iou.error.genericCreateFailureMessage'), @@ -2906,7 +2940,15 @@ function buildOnyxDataForTrackExpense({ } : { [iouAction.reportActionID]: { - errors: getReceiptError(transaction.receipt, transaction.receipt?.filename, isScanRequest, undefined, CONST.IOU.ACTION_PARAMS.TRACK_EXPENSE, retryParams), + errors: getReceiptError( + transaction.receipt, + transaction.receipt?.filename, + isScanRequest, + undefined, + CONST.IOU.ACTION_PARAMS.TRACK_EXPENSE, + retryParams, + transaction.transactionID, + ), }, }), }, @@ -2918,7 +2960,15 @@ function buildOnyxDataForTrackExpense({ key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${chatReport?.reportID}`, value: { [iouAction.reportActionID]: { - errors: getReceiptError(transaction.receipt, transaction.receipt?.filename, isScanRequest, undefined, CONST.IOU.ACTION_PARAMS.TRACK_EXPENSE, retryParams), + errors: getReceiptError( + transaction.receipt, + transaction.receipt?.filename, + isScanRequest, + undefined, + CONST.IOU.ACTION_PARAMS.TRACK_EXPENSE, + retryParams, + transaction.transactionID, + ), }, }, }); @@ -2950,7 +3000,15 @@ function buildOnyxDataForTrackExpense({ onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, value: { - errors: getReceiptError(transaction.receipt, transaction.receipt?.filename, isScanRequest, undefined, CONST.IOU.ACTION_PARAMS.TRACK_EXPENSE, retryParams), + errors: getReceiptError( + transaction.receipt, + transaction.receipt?.filename, + isScanRequest, + undefined, + CONST.IOU.ACTION_PARAMS.TRACK_EXPENSE, + retryParams, + transaction.transactionID, + ), pendingFields: clearedPendingFields, }, }, @@ -6723,7 +6781,7 @@ function trackExpense(params: CreateTrackExpenseParams) { existingTransactionID: isMovingTransactionFromTrackExpense && linkedTrackedExpenseReportAction && isMoneyRequestAction(linkedTrackedExpenseReportAction) ? getOriginalMessage(linkedTrackedExpenseReportAction)?.IOUTransactionID - : undefined, + : existingTransaction?.transactionID, participantParams: { participant, payeeAccountID, @@ -11563,7 +11621,7 @@ function replaceReceipt({transactionID, file, source, state, transactionPolicy, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, value: { receipt: !isEmptyObject(oldReceipt) ? oldReceipt : null, - errors: getReceiptError(receiptOptimistic, file.name, undefined, undefined, CONST.IOU.ACTION_PARAMS.REPLACE_RECEIPT, retryParams), + errors: getReceiptError(receiptOptimistic, file.name, undefined, undefined, CONST.IOU.ACTION_PARAMS.REPLACE_RECEIPT, retryParams, transactionID), pendingFields: { receipt: null, }, diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 0e24d766b337e..46cd260b55a6f 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -257,6 +257,9 @@ type ReceiptError = { /** Parameters required to retry the failed action */ retryParams: StartSplitBilActionParams | CreateTrackExpenseParams | RequestMoneyInformation | ReplaceReceipt; + /** Transaction ID associated with the receipt error, used for non-destructive retry */ + transactionID?: string; + /** The type of receipt error */ error: typeof CONST.IOU.RECEIPT_ERROR; };