From 064cefa22c9c66288c7e5950a2dd8fe6f539d78e Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sat, 7 Feb 2026 00:40:07 +0530 Subject: [PATCH 1/5] Update MoneyRequestDistanceRate functions --- src/libs/actions/IOU/index.ts | 16 ++++++++-------- .../request/step/IOURequestStepDistanceRate.tsx | 4 ++-- .../distanceRates/CreateDistanceRatePage.tsx | 5 ++++- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 823e6a08861b..095775b1f444 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -1741,13 +1741,14 @@ function removeMoneyRequestOdometerImage(transactionID: string, imageType: Odome * Set the distance rate of a transaction. * Used when creating a new transaction or moving an existing one from Self DM */ -function setMoneyRequestDistanceRate(transactionID: string, customUnitRateID: string, policy: OnyxEntry, isDraft: boolean) { +function setMoneyRequestDistanceRate(currentTransaction: OnyxEntry, customUnitRateID: string, policy: OnyxEntry, isDraft: boolean) { if (policy) { Onyx.merge(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, {[policy.id]: customUnitRateID}); } const distanceRate = DistanceRequestUtils.getRateByCustomUnitRateID({policy, customUnitRateID}); - const transaction = isDraft ? allTransactionDrafts[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`] : allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const transactionID = currentTransaction?.transactionID; + const transaction = isDraft ? allTransactionDrafts[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`] : currentTransaction; let newDistance; if (distanceRate?.unit && distanceRate?.unit !== transaction?.comment?.customUnit?.distanceUnit) { newDistance = DistanceRequestUtils.convertDistanceUnit(getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit), distanceRate.unit); @@ -5792,7 +5793,7 @@ function updateMoneyRequestDescription({ /** Updates the distance rate of an expense */ function updateMoneyRequestDistanceRate({ - transactionID, + transaction, transactionThreadReport, parentReport, rateID, @@ -5806,7 +5807,7 @@ function updateMoneyRequestDistanceRate({ updatedTaxCode, parentReportNextStep, }: { - transactionID: string; + transactionID: OnyxEntry; transactionThreadReport: OnyxEntry; parentReport: OnyxEntry; rateID: string; @@ -5826,8 +5827,7 @@ function updateMoneyRequestDistanceRate({ ...(updatedTaxCode ? {taxCode: updatedTaxCode} : {}), }; - const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; - if (transaction) { + if (transaction?.transactionID) { const existingDistanceUnit = transaction?.comment?.customUnit?.distanceUnit; const newDistanceUnit = DistanceRequestUtils.getRateByCustomUnitRateID({customUnitRateID: rateID, policy})?.unit; @@ -5840,10 +5840,10 @@ function updateMoneyRequestDistanceRate({ let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { - data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy); + data = getUpdateTrackExpenseParams(transaction?.transactionID, transactionThreadReport?.reportID, transactionChanges, policy); } else { data = getUpdateMoneyRequestParams({ - transactionID, + transactionID: transaction?.transactionID, transactionThreadReport, iouReport: parentReport, transactionChanges, diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 26468764c004..874d014545bb 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -119,11 +119,11 @@ function IOURequestStepDistanceRate({ } if (currentRateID !== customUnitRateID) { - setMoneyRequestDistanceRate(transactionID, customUnitRateID, policy, shouldUseTransactionDraft(action)); + setMoneyRequestDistanceRate(transaction, customUnitRateID, policy, shouldUseTransactionDraft(action)); if (isEditing && transaction?.transactionID) { updateMoneyRequestDistanceRate({ - transactionID: transaction.transactionID, + transaction, transactionThreadReport: report, parentReport, parentReportNextStep, diff --git a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index 5275804aac94..c12cf3bcce2e 100644 --- a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -9,9 +9,11 @@ import HeaderWithBackButton from '@components/HeaderWithBackButton'; import ScreenWrapper from '@components/ScreenWrapper'; import useAutoFocusInput from '@hooks/useAutoFocusInput'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import {setMoneyRequestDistanceRate} from '@libs/actions/IOU'; +import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import {getOptimisticRateName, validateRateValue} from '@libs/PolicyDistanceRatesUtils'; import {getDistanceRateCustomUnit} from '@libs/PolicyUtils'; @@ -43,6 +45,7 @@ function CreateDistanceRatePage({ const customUnitRateID = generateCustomUnitID(); const {inputCallbackRef} = useAutoFocusInput(); const isDistanceRateUpgrade = transactionID && reportID; + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`, {canBeMissing: true}); const FullPageBlockingView = !customUnitID ? FullPageOfflineBlockingView : View; @@ -67,7 +70,7 @@ function CreateDistanceRatePage({ createPolicyDistanceRate(policyID, customUnitID, newRate); if (isDistanceRateUpgrade) { - setMoneyRequestDistanceRate(transactionID, customUnitRateID, policy, true); + setMoneyRequestDistanceRate(transaction, customUnitRateID, policy, true); Navigation.goBack(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.SUBMIT, transactionID, reportID), {compareParams: false}); return; } From d39b776228877c24789019d73a4d348270aa202e Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 19 Mar 2026 20:31:25 +0530 Subject: [PATCH 2/5] fix type --- src/libs/actions/IOU/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 2543af8c071c..07b767094731 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -5521,7 +5521,7 @@ function updateMoneyRequestDistanceRate({ updatedTaxCode, parentReportNextStep, }: { - transactionID: OnyxEntry; + transaction: OnyxEntry; transactionThreadReport: OnyxEntry; parentReport: OnyxEntry; rateID: string; From 21fa28cdd657ea64fa54071f885f42c3675948ef Mon Sep 17 00:00:00 2001 From: parasharrajat Date: Fri, 20 Mar 2026 17:26:56 +0530 Subject: [PATCH 3/5] Update CreateDistanceRatePage.tsx --- src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx index c12cf3bcce2e..d87d81d5549c 100644 --- a/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx +++ b/src/pages/workspace/distanceRates/CreateDistanceRatePage.tsx @@ -45,7 +45,7 @@ function CreateDistanceRatePage({ const customUnitRateID = generateCustomUnitID(); const {inputCallbackRef} = useAutoFocusInput(); const isDistanceRateUpgrade = transactionID && reportID; - const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`, {canBeMissing: true}); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`); const FullPageBlockingView = !customUnitID ? FullPageOfflineBlockingView : View; From d2edd0ed030c7c6b9d9838eab279c3e1d293eb32 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 22 Mar 2026 11:07:56 +0530 Subject: [PATCH 4/5] Add unit tests --- src/libs/actions/IOU/index.ts | 4 + tests/actions/IOUTest.ts | 202 +++++++++++++++++++++++++++++++++- 2 files changed, 205 insertions(+), 1 deletion(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index b4f2cad2d9a6..26a027194657 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -1728,6 +1728,10 @@ function removeMoneyRequestOdometerImage(transactionID: string, imageType: Odome * Used when creating a new transaction or moving an existing one from Self DM */ function setMoneyRequestDistanceRate(currentTransaction: OnyxEntry, customUnitRateID: string, policy: OnyxEntry, isDraft: boolean) { + if (!currentTransaction) { + Log.warn('setMoneyRequestDistanceRate is called without a valid transaction, skipping setting distance rate.'); + return; + } if (policy) { Onyx.merge(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, {[policy.id]: customUnitRateID}); } diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 9bb6210111ab..fbf66e21b394 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -36,6 +36,7 @@ import { requestMoney, retractReport, setMoneyRequestCategory, + setMoneyRequestDistanceRate, shouldOptimisticallyUpdateSearch, submitReport, trackExpense, @@ -53,6 +54,7 @@ import {addComment, createNewReport, deleteReport, notifyNewAction, openReport} import {subscribeToUserEvents} from '@libs/actions/User'; import type {ApiCommand} from '@libs/API/types'; import {WRITE_COMMANDS} from '@libs/API/types'; +import Log from '@libs/Log'; import isReportTopmostSplitNavigator from '@libs/Navigation/helpers/isReportTopmostSplitNavigator'; import Navigation from '@libs/Navigation/Navigation'; import {rand64} from '@libs/NumberUtils'; @@ -89,7 +91,18 @@ import DateUtils from '@src/libs/DateUtils'; import * as SearchQueryUtils from '@src/libs/SearchQueryUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import type {IntroSelected, PersonalDetailsList, Policy, PolicyTagLists, RecentlyUsedTags, RecentWaypoint, Report, ReportNameValuePairs, SearchResults} from '@src/types/onyx'; +import type { + IntroSelected, + LastSelectedDistanceRates, + PersonalDetailsList, + Policy, + PolicyTagLists, + RecentlyUsedTags, + RecentWaypoint, + Report, + ReportNameValuePairs, + SearchResults, +} from '@src/types/onyx'; import type {Accountant, Attendee, Participant as IOUParticipant, SplitExpense} from '@src/types/onyx/IOU'; import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails'; import type {Participant} from '@src/types/onyx/Report'; @@ -4950,6 +4963,193 @@ describe('actions/IOU', () => { }); }); + describe('setMoneyRequestDistanceRate', () => { + it('does not set distance rate if transaction is invalid', async () => { + // Given an invalid transaction + const consoleWarnSpy = jest.spyOn(Log, 'warn').mockImplementation(() => {}); + + // When calling setMoneyRequestDistanceRate with invalid transaction + setMoneyRequestDistanceRate(undefined, 'customUnitRateID123', createRandomPolicy(1), false); + // Then a warning should be logged and distance rate should not be set + expect(consoleWarnSpy).toHaveBeenCalledWith('setMoneyRequestDistanceRate is called without a valid transaction, skipping setting distance rate.'); + const distanceRates = await getOnyxValue(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); + expect(distanceRates).toBeUndefined(); + consoleWarnSpy.mockRestore(); + }); + + it('sets the last selected distance rate for valid transaction', async () => { + const policy = createRandomPolicy(1); + // Given a valid transaction + const testTransaction: Transaction = { + transactionID: 'testTransaction123', + amount: 1000, + currency: CONST.CURRENCY.USD, + comment: { + comment: 'Test transaction', + attendees: [], + }, + created: DateUtils.getDBTime(), + merchant: 'Test Merchant', + reportID: 'testReport123', + }; + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${testTransaction.transactionID}`, testTransaction); + + // When calling setMoneyRequestDistanceRate with valid transaction + const customUnitRateID = 'customUnitRateID123'; + setMoneyRequestDistanceRate(testTransaction, customUnitRateID, policy, false); + await waitForBatchedUpdates(); + // Then the distance rate should be set in Onyx + const lastdistanceRates = (await getOnyxValue(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES)) as LastSelectedDistanceRates | undefined; + expect(lastdistanceRates?.[policy.id]).toBeDefined(); + expect(lastdistanceRates?.[policy.id]).toBe(customUnitRateID); + }); + + it('sets distance rate and distance unit for draft transaction', async () => { + const policy = createRandomPolicy(1); + policy.customUnits = { + distanceUnitID: { + customUnitID: 'distanceUnitID', + name: CONST.CUSTOM_UNITS.NAME_DISTANCE, + attributes: { + unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS, + }, + rates: {}, + }, + }; + + const testTransaction: Transaction = { + transactionID: 'testTransaction123', + amount: 1000, + currency: CONST.CURRENCY.USD, + comment: { + comment: 'Test transaction', + }, + created: DateUtils.getDBTime(), + merchant: 'Test Merchant', + reportID: 'testReport123', + }; + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${testTransaction.transactionID}`, testTransaction); + + const customUnitRateID = 'customUnitRateID123'; + setMoneyRequestDistanceRate(testTransaction, customUnitRateID, policy, true); + await waitForBatchedUpdates(); + + const transactionDraft = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${testTransaction.transactionID}`); + expect(transactionDraft?.comment?.customUnit?.customUnitRateID).toBe(customUnitRateID); + expect(transactionDraft?.comment?.customUnit?.distanceUnit).toBe(CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS); + expect(transactionDraft?.comment?.customUnit?.defaultP2PRate).toBeUndefined(); + }); + + it('converts distance quantity if distance unit changes', async () => { + const policy = createRandomPolicy(1); + policy.customUnits = { + distanceUnitID: { + customUnitID: 'distanceUnitID', + name: CONST.CUSTOM_UNITS.NAME_DISTANCE, + attributes: { + unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS, + }, + rates: {}, + }, + }; + + const testTransaction: Transaction = { + transactionID: 'testTransaction123', + amount: 1000, + currency: CONST.CURRENCY.USD, + comment: { + comment: 'Test transaction', + customUnit: { + distanceUnit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + quantity: 10, + }, + }, + created: DateUtils.getDBTime(), + merchant: 'Test Merchant', + reportID: 'testReport123', + }; + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${testTransaction.transactionID}`, testTransaction); + + const customUnitRateID = 'customUnitRateID123'; + setMoneyRequestDistanceRate(testTransaction, customUnitRateID, policy, false); + await waitForBatchedUpdates(); + + const transaction = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION}${testTransaction.transactionID}`); + expect(transaction?.comment?.customUnit?.customUnitRateID).toBe(customUnitRateID); + expect(transaction?.comment?.customUnit?.distanceUnit).toBe(CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS); + // 10 miles to kilometers = 10 / 0.000621371 * 0.001 = 16.093444978925636 + expect(transaction?.comment?.customUnit?.quantity).toBe(16.093444978925636); + }); + + it('does not convert distance quantity if distance unit changes but it is an odometer request', async () => { + const policy = createRandomPolicy(1); + policy.customUnits = { + distanceUnitID: { + customUnitID: 'distanceUnitID', + name: CONST.CUSTOM_UNITS.NAME_DISTANCE, + attributes: { + unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS, + }, + rates: {}, + }, + }; + + const testTransaction: Transaction = { + transactionID: 'testTransaction123', + amount: 1000, + currency: CONST.CURRENCY.USD, + iouRequestType: CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER, + comment: { + comment: 'Test transaction', + customUnit: { + distanceUnit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + quantity: 10, + }, + }, + created: DateUtils.getDBTime(), + merchant: 'Test Merchant', + reportID: 'testReport123', + }; + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${testTransaction.transactionID}`, testTransaction); + + const customUnitRateID = 'customUnitRateID123'; + setMoneyRequestDistanceRate(testTransaction, customUnitRateID, policy, false); + await waitForBatchedUpdates(); + + const transaction = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION}${testTransaction.transactionID}`); + expect(transaction?.comment?.customUnit?.customUnitRateID).toBe(customUnitRateID); + expect(transaction?.comment?.customUnit?.distanceUnit).toBe(CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS); + // Quantity should remain 10 for odometer requests + expect(transaction?.comment?.customUnit?.quantity).toBe(10); + }); + + it('does not set defaultP2PRate to null when policy is undefined', async () => { + const testTransaction: Transaction = { + transactionID: 'testTransaction123', + amount: 1000, + currency: CONST.CURRENCY.USD, + comment: { + comment: 'Test transaction', + customUnit: { + defaultP2PRate: CONST.CUSTOM_UNITS.MILES_TO_KILOMETERS, + }, + }, + created: DateUtils.getDBTime(), + merchant: 'Test Merchant', + reportID: 'testReport123', + }; + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${testTransaction.transactionID}`, testTransaction); + + const customUnitRateID = 'customUnitRateID123'; + setMoneyRequestDistanceRate(testTransaction, customUnitRateID, undefined, false); + await waitForBatchedUpdates(); + + const transaction = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION}${testTransaction.transactionID}`); + expect(transaction?.comment?.customUnit?.customUnitRateID).toBe(customUnitRateID); + expect(transaction?.comment?.customUnit?.defaultP2PRate).toBe(CONST.CUSTOM_UNITS.MILES_TO_KILOMETERS); + }); + }); + describe('split expense', () => { const splitMockPersonalDetails: PersonalDetailsList = { [RORY_ACCOUNT_ID]: {accountID: RORY_ACCOUNT_ID, login: RORY_EMAIL, displayName: 'Rory'}, From 09b6d5e95cd2b6be02c12b8753ac5259a6e4fee9 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Mon, 23 Mar 2026 19:02:27 +0530 Subject: [PATCH 5/5] Fix spell check --- tests/actions/IOUTest.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index fbf66e21b394..3a00eed2dcc3 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -4999,9 +4999,9 @@ describe('actions/IOU', () => { setMoneyRequestDistanceRate(testTransaction, customUnitRateID, policy, false); await waitForBatchedUpdates(); // Then the distance rate should be set in Onyx - const lastdistanceRates = (await getOnyxValue(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES)) as LastSelectedDistanceRates | undefined; - expect(lastdistanceRates?.[policy.id]).toBeDefined(); - expect(lastdistanceRates?.[policy.id]).toBe(customUnitRateID); + const lastDistanceRates = (await getOnyxValue(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES)) as LastSelectedDistanceRates | undefined; + expect(lastDistanceRates?.[policy.id]).toBeDefined(); + expect(lastDistanceRates?.[policy.id]).toBe(customUnitRateID); }); it('sets distance rate and distance unit for draft transaction', async () => {