diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 2348aad087b73..c8c770077898e 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -699,10 +699,19 @@ function getUpdatedTransaction({ if (Object.hasOwn(transactionChanges, 'amount') && typeof transactionChanges.amount === 'number') { updatedTransaction.modifiedAmount = isFromExpenseReport || isUnReportedExpense ? -transactionChanges.amount : transactionChanges.amount; shouldStopSmartscan = true; + // Clear convertedAmount when amount is manually modified to prevent stale conversion data + // from being used during export (the server will recalculate if needed) + // Use null instead of undefined because Onyx.merge treats undefined as no-op + updatedTransaction.convertedAmount = null; } if (Object.hasOwn(transactionChanges, 'currency')) { updatedTransaction.modifiedCurrency = transactionChanges.currency; shouldStopSmartscan = true; + // Clear convertedAmount when currency is manually modified to prevent incorrect + // currency conversion being applied during export (fixes issue where manually entered + // amounts in a different currency were being re-converted) + // Use null instead of undefined because Onyx.merge treats undefined as no-op + updatedTransaction.convertedAmount = null; } if (Object.hasOwn(transactionChanges, 'merchant')) { diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 58fcb8c57f9f9..694e1b873711b 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -441,7 +441,7 @@ type Transaction = OnyxCommon.OnyxValueWithOfflineFeedback< accountant?: Accountant; /** The transaction converted amount in report's currency */ - convertedAmount?: number; + convertedAmount?: number | null; /** The transaction tax amount */ taxAmount?: number; diff --git a/tests/unit/TransactionUtilsTest.ts b/tests/unit/TransactionUtilsTest.ts index 5a4283fd5f498..e42b9d3c0c4ce 100644 --- a/tests/unit/TransactionUtilsTest.ts +++ b/tests/unit/TransactionUtilsTest.ts @@ -425,6 +425,50 @@ describe('TransactionUtils', () => { merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, }); }); + + it('should clear convertedAmount when currency is changed to prevent incorrect export conversion', () => { + // Given: a transaction with a converted amount (simulating a foreign currency transaction) + const transaction = generateTransaction({ + amount: -4500, // $45.00 USD + currency: CONST.CURRENCY.USD, + convertedAmount: -6285, // C$62.85 CAD (server-calculated conversion) + }); + + // When: user manually changes the currency to CAD + const updatedTransaction = TransactionUtils.getUpdatedTransaction({ + transaction, + isFromExpenseReport: true, + transactionChanges: {currency: 'CAD'}, + }); + + // Then: convertedAmount should be cleared (set to null, not undefined, because + // Onyx.merge treats undefined as no-op) to prevent stale conversion data + // being incorrectly applied during export + expect(updatedTransaction.modifiedCurrency).toBe('CAD'); + expect(updatedTransaction.convertedAmount).toBeNull(); + }); + + it('should clear convertedAmount when amount is manually changed to prevent incorrect export conversion', () => { + // Given: a transaction with a converted amount + const transaction = generateTransaction({ + amount: -4500, // $45.00 USD + currency: CONST.CURRENCY.USD, + convertedAmount: -6285, // C$62.85 CAD + }); + + // When: user manually changes the amount + const updatedTransaction = TransactionUtils.getUpdatedTransaction({ + transaction, + isFromExpenseReport: true, + transactionChanges: {amount: 6285}, // C$62.85 + }); + + // Then: convertedAmount should be cleared (set to null, not undefined, because + // Onyx.merge treats undefined as no-op) since the manual amount + // is the final value and shouldn't be re-converted during export + expect(updatedTransaction.modifiedAmount).toBe(-6285); + expect(updatedTransaction.convertedAmount).toBeNull(); + }); }); describe('getTransactionType', () => {