From 7adaf4c735947ee6f00f5eb7d0b394448684701e Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Tue, 10 Mar 2026 19:34:02 +0000 Subject: [PATCH 01/51] Fetch default P2P mileage rate from Auth when creating distance expense Instead of relying solely on the hardcoded CURRENCY_TO_DEFAULT_MILEAGE_RATE constant, the App now fetches the default mileage rate from Auth via the GetDefaultMileageRates command as soon as the user starts creating a distance expense. The fetched rates are stored in Onyx and used as the primary source of truth for P2P distance requests, with the hardcoded constant serving as a fallback. Co-authored-by: Neil Marcellini --- src/ONYXKEYS.ts | 4 ++ .../GetDefaultMileageRatesParams.ts | 5 +++ src/libs/API/parameters/index.ts | 1 + src/libs/API/types.ts | 2 + src/libs/DistanceRequestUtils.ts | 42 +++++++++++++++++-- src/libs/actions/IOU/index.ts | 5 ++- src/libs/actions/Transaction.ts | 14 ++++++- src/types/onyx/DefaultMileageRate.ts | 12 ++++++ src/types/onyx/index.ts | 2 + 9 files changed, 81 insertions(+), 6 deletions(-) create mode 100644 src/libs/API/parameters/GetDefaultMileageRatesParams.ts create mode 100644 src/types/onyx/DefaultMileageRate.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 90fbdba1ce94..4077bab9125b 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -420,6 +420,9 @@ const ONYXKEYS = { // The access token to be used with the Mapbox library MAPBOX_ACCESS_TOKEN: 'mapboxAccessToken', + /** Default mileage rates for P2P distance requests, fetched from Auth */ + DEFAULT_MILEAGE_RATES: 'defaultMileageRates', + // Max area supported for HTML element MAX_CANVAS_AREA: 'maxCanvasArea', @@ -1355,6 +1358,7 @@ type OnyxValuesMapping = { [ONYXKEYS.VERIFY_3DS_SUBSCRIPTION]: string; [ONYXKEYS.PREFERRED_THEME]: ValueOf; [ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken; + [ONYXKEYS.DEFAULT_MILEAGE_RATES]: Record; [ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: OnyxTypes.AnyOnyxUpdatesFromServer; [ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT]: number; [ONYXKEYS.MAX_CANVAS_AREA]: number; diff --git a/src/libs/API/parameters/GetDefaultMileageRatesParams.ts b/src/libs/API/parameters/GetDefaultMileageRatesParams.ts new file mode 100644 index 000000000000..4dfbda4fd433 --- /dev/null +++ b/src/libs/API/parameters/GetDefaultMileageRatesParams.ts @@ -0,0 +1,5 @@ +type GetDefaultMileageRatesParams = { + currency?: string; +}; + +export default GetDefaultMileageRatesParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 26b7ccb5176b..b42444354a6a 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -488,6 +488,7 @@ export type {default as RegisterAuthenticationKeyParams} from './RegisterAuthent export type {default as TroubleshootMultifactorAuthenticationParams} from './TroubleshootMultifactorAuthenticationParams'; export type {default as RequestAuthenticationChallengeParams} from './RequestAuthenticationChallengeParams'; export type {default as GetTransactionsMatchingCodingRuleParams} from './GetTransactionsMatchingCodingRuleParams'; +export type {default as GetDefaultMileageRatesParams} from './GetDefaultMileageRatesParams'; export type {default as SetPolicyTimeTrackingDefaultRateParams} from './SetPolicyTimeTrackingDefaultRateParams'; export type {default as ToggleTwoFactorAuthRequiredForDomainParams} from './ToggleTwoFactorAuthRequiredForDomainParams'; export type {default as SetReportDetailsColumnsParams} from './SetReportDetailsColumnsParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 237d17517315..b877365f7d66 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1234,6 +1234,7 @@ const READ_COMMANDS = { GET_SAML_SETTINGS: 'GetSAMLSettings', GET_DUPLICATE_TRANSACTION_DETAILS: 'GetDuplicateTransactionDetails', GET_TRANSACTIONS_MATCHING_CODING_RULE: 'GetTransactionsMatchingCodingRule', + GET_DEFAULT_MILEAGE_RATES: 'GetDefaultMileageRates', } as const; type ReadCommand = ValueOf; @@ -1323,6 +1324,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_DOMAIN_INITIAL_PAGE]: Parameters.DomainParams; [READ_COMMANDS.GET_DUPLICATE_TRANSACTION_DETAILS]: Parameters.GetDuplicateTransactionDetailsParams; [READ_COMMANDS.GET_TRANSACTIONS_MATCHING_CODING_RULE]: Parameters.GetTransactionsMatchingCodingRuleParams; + [READ_COMMANDS.GET_DEFAULT_MILEAGE_RATES]: Parameters.GetDefaultMileageRatesParams; }; const SIDE_EFFECT_REQUEST_COMMANDS = { diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index db7712eab890..0f7b26b854dc 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -1,8 +1,10 @@ import type {OnyxEntry} from 'react-native-onyx'; +import Onyx from 'react-native-onyx'; import type {CurrencyListActionsContextType} from '@components/CurrencyListContextProvider'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; -import type {LastSelectedDistanceRates, OnyxInputOrEntry, Transaction} from '@src/types/onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {DefaultMileageRate, LastSelectedDistanceRates, OnyxInputOrEntry, Transaction} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -12,6 +14,14 @@ import {replaceAllDigits} from './MoneyRequestUtils'; import {getDistanceRateCustomUnit, getDistanceRateCustomUnitRate, getPersonalPolicy, getUnitRateValue} from './PolicyUtils'; import {getCurrency, getRateID, isCustomUnitRateIDForP2P, isExpenseUnreported} from './TransactionUtils'; +let defaultMileageRatesFromServer: Record | undefined; +Onyx.connect({ + key: ONYXKEYS.DEFAULT_MILEAGE_RATES, + callback: (value) => { + defaultMileageRatesFromServer = value ?? undefined; + }, +}); + type MileageRate = { customUnitRateID?: string; rate?: number; @@ -274,6 +284,30 @@ function ensureRateDefined(rate: number | undefined): asserts rate is number { throw new Error('All default P2P rates should have a rate defined'); } +/** + * Returns true if a default mileage rate exists for the given currency in either the server-fetched rates or the hardcoded constant. + */ +function hasDefaultMileageRateForCurrency(currency: string): boolean { + return !!(defaultMileageRatesFromServer?.[currency] ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency]); +} + +/** + * Returns the default mileage rate for the given currency, preferring rates fetched from the server + * (stored in Onyx) over the hardcoded CURRENCY_TO_DEFAULT_MILEAGE_RATE constant. + */ +function getDefaultMileageRateForCurrency(currency: string): {rate: number; unit: Unit} { + const serverRate = defaultMileageRatesFromServer?.[currency]; + if (serverRate) { + return serverRate; + } + const fallbackRate = CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency]; + if (fallbackRate) { + return fallbackRate; + } + // Fall back to USD if the currency isn't found in either source + return defaultMileageRatesFromServer?.USD ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; +} + /** * Retrieves the rate and unit for a P2P distance expense for a given currency. * @@ -283,8 +317,8 @@ function ensureRateDefined(rate: number | undefined): asserts rate is number { * @returns The rate and unit in MileageRate object. */ function getRateForP2P(currency: string, transaction: OnyxEntry): MileageRate { - const currencyWithExistingRate = CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ? currency : CONST.CURRENCY.USD; - const mileageRate = CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currencyWithExistingRate]; + const currencyWithExistingRate = hasDefaultMileageRateForCurrency(currency) ? currency : CONST.CURRENCY.USD; + const mileageRate = getDefaultMileageRateForCurrency(currencyWithExistingRate); ensureRateDefined(mileageRate.rate); // Ensure the rate is updated when the currency changes, otherwise use the stored rate @@ -491,6 +525,8 @@ export default { getDistanceForDisplay, getRoundedDistanceInUnits, getRateForP2P, + getDefaultMileageRateForCurrency, + hasDefaultMileageRateForCurrency, getCustomUnitRateID, convertToDistanceInMeters, getTaxableAmount, diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 32854a8020f7..26a348b73d9d 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -240,7 +240,7 @@ import type {BuildPolicyDataKeys} from '@userActions/Policy/Policy'; import {buildOptimisticPolicyRecentlyUsedTags} from '@userActions/Policy/Tag'; import type {GuidedSetupData} from '@userActions/Report'; import {buildInviteToRoomOnyxData, completeOnboarding, notifyNewAction, optimisticReportLastData} from '@userActions/Report'; -import {mergeTransactionIdsHighlightOnSearchRoute, sanitizeRecentWaypoints} from '@userActions/Transaction'; +import {fetchDefaultMileageRates, mergeTransactionIdsHighlightOnSearchRoute, sanitizeRecentWaypoints} from '@userActions/Transaction'; import {removeDraftTransaction, removeDraftTransactions, removeDraftTransactionsByIDs} from '@userActions/TransactionEdit'; import {getOnboardingMessages} from '@userActions/Welcome/OnboardingFlow'; import type {OnboardingCompanySize} from '@userActions/Welcome/OnboardingFlow'; @@ -1346,6 +1346,9 @@ function initMoneyRequest({ comment.odometerStartImage = undefined; comment.odometerEndImage = undefined; } + + // Fetch the default mileage rate for the user's currency from Auth (single source of truth) + fetchDefaultMileageRates(currency); } if (newIouRequestType === CONST.IOU.REQUEST_TYPE.PER_DIEM) { diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index d575d9c8c116..7024cc151118 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -792,6 +792,14 @@ function openDraftDistanceExpense() { API.read(READ_COMMANDS.OPEN_DRAFT_DISTANCE_EXPENSE, null, onyxData); } +/** + * Fetches the default mileage rates from Auth for the given currency. + * These rates are used for P2P distance requests and stored in Onyx. + */ +function fetchDefaultMileageRates(currency: string) { + API.read(READ_COMMANDS.GET_DEFAULT_MILEAGE_RATES, {currency}, {}); +} + /** * Returns a client generated 16 character hexadecimal value for the transactionID */ @@ -1019,8 +1027,9 @@ function changeTransactionsReport({ // For distance requests we need to update its custom unit ID to `_FAKE_P2P_ID_` so it's no longer tied to the policy's rate which would cause the "Rate out of policy" violation to appear. // Let's also set the defaultP2PRate and update the distanceUnit, the quantity, the amount, the currency and the merchant to match the P2P rate. if (isDistanceRequest(transaction)) { - const currency = destinationCurrency && CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[destinationCurrency] ? destinationCurrency : CONST.CURRENCY.USD; - const {rate, unit} = CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency]; + const currency = + destinationCurrency && DistanceRequestUtils.hasDefaultMileageRateForCurrency(destinationCurrency) ? destinationCurrency : CONST.CURRENCY.USD; + const {rate, unit} = DistanceRequestUtils.getDefaultMileageRateForCurrency(currency); const distance = parseFloat( DistanceRequestUtils.getRoundedDistanceInUnits( getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES), @@ -1682,6 +1691,7 @@ export { setReviewDuplicatesKey, abandonReviewDuplicateTransactions, openDraftDistanceExpense, + fetchDefaultMileageRates, sanitizeRecentWaypoints, getLastModifiedExpense, revert, diff --git a/src/types/onyx/DefaultMileageRate.ts b/src/types/onyx/DefaultMileageRate.ts new file mode 100644 index 000000000000..d3927d953697 --- /dev/null +++ b/src/types/onyx/DefaultMileageRate.ts @@ -0,0 +1,12 @@ +import type {Unit} from './Policy'; + +/** Model of a default mileage rate fetched from the backend */ +type DefaultMileageRate = { + /** Rate in cents per unit */ + rate: number; + + /** Unit of measurement: 'km' or 'mi' */ + unit: Unit; +}; + +export default DefaultMileageRate; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 07b1e30bf1dd..dbfff23f182e 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -39,6 +39,7 @@ import type {CorpayFields, CorpayFormField} from './CorpayFields'; import type {CorpayOnboardingFields} from './CorpayOnboardingFields'; import type Credentials from './Credentials'; import type Currency from './Currency'; +import type DefaultMileageRate from './DefaultMileageRate'; import type {CurrencyList} from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; import type { @@ -200,6 +201,7 @@ export type { Currency, CurrencyList, CustomStatusDraft, + DefaultMileageRate, UnshareBankAccount, DismissedReferralBanners, Domain, From 23f03e3461ec0ff0e8cd5076ec55ed59720588c7 Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Tue, 10 Mar 2026 19:40:01 +0000 Subject: [PATCH 02/51] Fix: run prettier on Transaction.ts and onyx/index.ts Co-authored-by: Neil Marcellini --- src/libs/actions/Transaction.ts | 3 +-- src/types/onyx/index.ts | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 7024cc151118..fbb63c9c628d 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -1027,8 +1027,7 @@ function changeTransactionsReport({ // For distance requests we need to update its custom unit ID to `_FAKE_P2P_ID_` so it's no longer tied to the policy's rate which would cause the "Rate out of policy" violation to appear. // Let's also set the defaultP2PRate and update the distanceUnit, the quantity, the amount, the currency and the merchant to match the P2P rate. if (isDistanceRequest(transaction)) { - const currency = - destinationCurrency && DistanceRequestUtils.hasDefaultMileageRateForCurrency(destinationCurrency) ? destinationCurrency : CONST.CURRENCY.USD; + const currency = destinationCurrency && DistanceRequestUtils.hasDefaultMileageRateForCurrency(destinationCurrency) ? destinationCurrency : CONST.CURRENCY.USD; const {rate, unit} = DistanceRequestUtils.getDefaultMileageRateForCurrency(currency); const distance = parseFloat( DistanceRequestUtils.getRoundedDistanceInUnits( diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index dbfff23f182e..7f58911dbb4e 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -39,9 +39,9 @@ import type {CorpayFields, CorpayFormField} from './CorpayFields'; import type {CorpayOnboardingFields} from './CorpayOnboardingFields'; import type Credentials from './Credentials'; import type Currency from './Currency'; -import type DefaultMileageRate from './DefaultMileageRate'; import type {CurrencyList} from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; +import type DefaultMileageRate from './DefaultMileageRate'; import type { CardFeedErrorsDerivedValue, NonPersonalAndWorkspaceCardListDerivedValue, From ba039cd526769415f7bc70ade69f28120218b4d5 Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Tue, 10 Mar 2026 19:50:17 +0000 Subject: [PATCH 03/51] Fix: narrow MileageRate types in getDefaultMileageRateForCurrency The return type requires rate: number but MileageRate.rate is number | undefined. Properly narrow the type by checking rate !== undefined for fallback rates and splitting the USD fallback into separate type-safe branches. Co-authored-by: Neil Marcellini --- src/libs/DistanceRequestUtils.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 0f7b26b854dc..ac96a354f6c8 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -301,11 +301,17 @@ function getDefaultMileageRateForCurrency(currency: string): {rate: number; unit return serverRate; } const fallbackRate = CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency]; - if (fallbackRate) { - return fallbackRate; + if (fallbackRate?.rate !== undefined) { + return {rate: fallbackRate.rate, unit: fallbackRate.unit}; } // Fall back to USD if the currency isn't found in either source - return defaultMileageRatesFromServer?.USD ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; + const usdServerRate = defaultMileageRatesFromServer?.USD; + if (usdServerRate) { + return usdServerRate; + } + const usdFallbackRate = CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; + ensureRateDefined(usdFallbackRate?.rate); + return {rate: usdFallbackRate.rate, unit: usdFallbackRate.unit}; } /** From c782f2c39edfb5108c344b40b309c2bf39188129 Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Mon, 23 Mar 2026 14:50:04 +0000 Subject: [PATCH 04/51] Simplify to store a single P2P mileage rate from Auth Auth now returns a single rate+unit instead of a mapping. Updated the App to store one DefaultMileageRate in Onyx and use it directly, falling back to the hardcoded constant when the server rate is not yet available. Co-authored-by: Neil Marcellini --- src/ONYXKEYS.ts | 2 +- src/libs/DistanceRequestUtils.ts | 44 ++++++++++---------------------- src/libs/actions/Transaction.ts | 4 +-- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 4077bab9125b..6804e4c1be69 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1358,7 +1358,7 @@ type OnyxValuesMapping = { [ONYXKEYS.VERIFY_3DS_SUBSCRIPTION]: string; [ONYXKEYS.PREFERRED_THEME]: ValueOf; [ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken; - [ONYXKEYS.DEFAULT_MILEAGE_RATES]: Record; + [ONYXKEYS.DEFAULT_MILEAGE_RATES]: OnyxTypes.DefaultMileageRate; [ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: OnyxTypes.AnyOnyxUpdatesFromServer; [ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT]: number; [ONYXKEYS.MAX_CANVAS_AREA]: number; diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index ac96a354f6c8..6d23c2c710c7 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -14,11 +14,11 @@ import {replaceAllDigits} from './MoneyRequestUtils'; import {getDistanceRateCustomUnit, getDistanceRateCustomUnitRate, getPersonalPolicy, getUnitRateValue} from './PolicyUtils'; import {getCurrency, getRateID, isCustomUnitRateIDForP2P, isExpenseUnreported} from './TransactionUtils'; -let defaultMileageRatesFromServer: Record | undefined; +let defaultP2PMileageRate: DefaultMileageRate | undefined; Onyx.connect({ key: ONYXKEYS.DEFAULT_MILEAGE_RATES, callback: (value) => { - defaultMileageRatesFromServer = value ?? undefined; + defaultP2PMileageRate = value ?? undefined; }, }); @@ -285,33 +285,16 @@ function ensureRateDefined(rate: number | undefined): asserts rate is number { } /** - * Returns true if a default mileage rate exists for the given currency in either the server-fetched rates or the hardcoded constant. + * Returns the default P2P mileage rate, preferring the server-fetched rate (from Auth) + * over the hardcoded CURRENCY_TO_DEFAULT_MILEAGE_RATE constant. */ -function hasDefaultMileageRateForCurrency(currency: string): boolean { - return !!(defaultMileageRatesFromServer?.[currency] ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency]); -} - -/** - * Returns the default mileage rate for the given currency, preferring rates fetched from the server - * (stored in Onyx) over the hardcoded CURRENCY_TO_DEFAULT_MILEAGE_RATE constant. - */ -function getDefaultMileageRateForCurrency(currency: string): {rate: number; unit: Unit} { - const serverRate = defaultMileageRatesFromServer?.[currency]; - if (serverRate) { - return serverRate; - } - const fallbackRate = CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency]; - if (fallbackRate?.rate !== undefined) { - return {rate: fallbackRate.rate, unit: fallbackRate.unit}; - } - // Fall back to USD if the currency isn't found in either source - const usdServerRate = defaultMileageRatesFromServer?.USD; - if (usdServerRate) { - return usdServerRate; +function getDefaultP2PMileageRate(currency: string): {rate: number; unit: Unit} { + if (defaultP2PMileageRate) { + return defaultP2PMileageRate; } - const usdFallbackRate = CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; - ensureRateDefined(usdFallbackRate?.rate); - return {rate: usdFallbackRate.rate, unit: usdFallbackRate.unit}; + const fallbackRate = CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; + ensureRateDefined(fallbackRate?.rate); + return {rate: fallbackRate.rate, unit: fallbackRate.unit}; } /** @@ -323,8 +306,8 @@ function getDefaultMileageRateForCurrency(currency: string): {rate: number; unit * @returns The rate and unit in MileageRate object. */ function getRateForP2P(currency: string, transaction: OnyxEntry): MileageRate { - const currencyWithExistingRate = hasDefaultMileageRateForCurrency(currency) ? currency : CONST.CURRENCY.USD; - const mileageRate = getDefaultMileageRateForCurrency(currencyWithExistingRate); + const currencyWithExistingRate = defaultP2PMileageRate || CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ? currency : CONST.CURRENCY.USD; + const mileageRate = getDefaultP2PMileageRate(currencyWithExistingRate); ensureRateDefined(mileageRate.rate); // Ensure the rate is updated when the currency changes, otherwise use the stored rate @@ -531,8 +514,7 @@ export default { getDistanceForDisplay, getRoundedDistanceInUnits, getRateForP2P, - getDefaultMileageRateForCurrency, - hasDefaultMileageRateForCurrency, + getDefaultP2PMileageRate, getCustomUnitRateID, convertToDistanceInMeters, getTaxableAmount, diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index fbb63c9c628d..8c5099b408e9 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -1027,8 +1027,8 @@ function changeTransactionsReport({ // For distance requests we need to update its custom unit ID to `_FAKE_P2P_ID_` so it's no longer tied to the policy's rate which would cause the "Rate out of policy" violation to appear. // Let's also set the defaultP2PRate and update the distanceUnit, the quantity, the amount, the currency and the merchant to match the P2P rate. if (isDistanceRequest(transaction)) { - const currency = destinationCurrency && DistanceRequestUtils.hasDefaultMileageRateForCurrency(destinationCurrency) ? destinationCurrency : CONST.CURRENCY.USD; - const {rate, unit} = DistanceRequestUtils.getDefaultMileageRateForCurrency(currency); + const currency = destinationCurrency && CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[destinationCurrency] ? destinationCurrency : CONST.CURRENCY.USD; + const {rate, unit} = DistanceRequestUtils.getDefaultP2PMileageRate(currency); const distance = parseFloat( DistanceRequestUtils.getRoundedDistanceInUnits( getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES), From ce020833c9b1ff51a7e4a5e495f56495d91d7d9f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 23 Mar 2026 12:13:32 -0700 Subject: [PATCH 05/51] Rename to GetDefaultP2PMileageRate and use DEFAULT_P2P_MILEAGE_RATE Onyx key - Renamed Onyx key from DEFAULT_MILEAGE_RATES to DEFAULT_P2P_MILEAGE_RATE - Renamed type from DefaultMileageRate to DefaultP2PMileageRate - Renamed API command from GetDefaultMileageRates to GetDefaultP2PMileageRate - Renamed action from fetchDefaultMileageRates to fetchDefaultP2PMileageRate - Auth now pushes onyxData with the rate, so no manual Onyx merge is needed Made-with: Cursor --- src/ONYXKEYS.ts | 6 +++--- .../API/parameters/GetDefaultMileageRatesParams.ts | 5 ----- .../API/parameters/GetDefaultP2PMileageRateParams.ts | 5 +++++ src/libs/API/parameters/index.ts | 2 +- src/libs/API/types.ts | 4 ++-- src/libs/DistanceRequestUtils.ts | 6 +++--- src/libs/actions/IOU/index.ts | 5 ++--- src/libs/actions/Transaction.ts | 10 +++++----- ...{DefaultMileageRate.ts => DefaultP2PMileageRate.ts} | 6 +++--- src/types/onyx/index.ts | 4 ++-- 10 files changed, 26 insertions(+), 27 deletions(-) delete mode 100644 src/libs/API/parameters/GetDefaultMileageRatesParams.ts create mode 100644 src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts rename src/types/onyx/{DefaultMileageRate.ts => DefaultP2PMileageRate.ts} (54%) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 6804e4c1be69..24c46f2a8e47 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -420,8 +420,8 @@ const ONYXKEYS = { // The access token to be used with the Mapbox library MAPBOX_ACCESS_TOKEN: 'mapboxAccessToken', - /** Default mileage rates for P2P distance requests, fetched from Auth */ - DEFAULT_MILEAGE_RATES: 'defaultMileageRates', + /** Default P2P mileage rate fetched from Auth */ + DEFAULT_P2P_MILEAGE_RATE: 'defaultP2PMileageRate', // Max area supported for HTML element MAX_CANVAS_AREA: 'maxCanvasArea', @@ -1358,7 +1358,7 @@ type OnyxValuesMapping = { [ONYXKEYS.VERIFY_3DS_SUBSCRIPTION]: string; [ONYXKEYS.PREFERRED_THEME]: ValueOf; [ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken; - [ONYXKEYS.DEFAULT_MILEAGE_RATES]: OnyxTypes.DefaultMileageRate; + [ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE]: OnyxTypes.DefaultP2PMileageRate; [ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: OnyxTypes.AnyOnyxUpdatesFromServer; [ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT]: number; [ONYXKEYS.MAX_CANVAS_AREA]: number; diff --git a/src/libs/API/parameters/GetDefaultMileageRatesParams.ts b/src/libs/API/parameters/GetDefaultMileageRatesParams.ts deleted file mode 100644 index 4dfbda4fd433..000000000000 --- a/src/libs/API/parameters/GetDefaultMileageRatesParams.ts +++ /dev/null @@ -1,5 +0,0 @@ -type GetDefaultMileageRatesParams = { - currency?: string; -}; - -export default GetDefaultMileageRatesParams; diff --git a/src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts b/src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts new file mode 100644 index 000000000000..72431cfae356 --- /dev/null +++ b/src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts @@ -0,0 +1,5 @@ +type GetDefaultP2PMileageRateParams = { + currency?: string; +}; + +export default GetDefaultP2PMileageRateParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index b42444354a6a..07bd8f43e58b 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -488,7 +488,7 @@ export type {default as RegisterAuthenticationKeyParams} from './RegisterAuthent export type {default as TroubleshootMultifactorAuthenticationParams} from './TroubleshootMultifactorAuthenticationParams'; export type {default as RequestAuthenticationChallengeParams} from './RequestAuthenticationChallengeParams'; export type {default as GetTransactionsMatchingCodingRuleParams} from './GetTransactionsMatchingCodingRuleParams'; -export type {default as GetDefaultMileageRatesParams} from './GetDefaultMileageRatesParams'; +export type {default as GetDefaultP2PMileageRateParams} from './GetDefaultP2PMileageRateParams'; export type {default as SetPolicyTimeTrackingDefaultRateParams} from './SetPolicyTimeTrackingDefaultRateParams'; export type {default as ToggleTwoFactorAuthRequiredForDomainParams} from './ToggleTwoFactorAuthRequiredForDomainParams'; export type {default as SetReportDetailsColumnsParams} from './SetReportDetailsColumnsParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index b877365f7d66..b55d3d138d9c 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1234,7 +1234,7 @@ const READ_COMMANDS = { GET_SAML_SETTINGS: 'GetSAMLSettings', GET_DUPLICATE_TRANSACTION_DETAILS: 'GetDuplicateTransactionDetails', GET_TRANSACTIONS_MATCHING_CODING_RULE: 'GetTransactionsMatchingCodingRule', - GET_DEFAULT_MILEAGE_RATES: 'GetDefaultMileageRates', + GET_DEFAULT_P2P_MILEAGE_RATE: 'GetDefaultP2PMileageRate', } as const; type ReadCommand = ValueOf; @@ -1324,7 +1324,7 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_DOMAIN_INITIAL_PAGE]: Parameters.DomainParams; [READ_COMMANDS.GET_DUPLICATE_TRANSACTION_DETAILS]: Parameters.GetDuplicateTransactionDetailsParams; [READ_COMMANDS.GET_TRANSACTIONS_MATCHING_CODING_RULE]: Parameters.GetTransactionsMatchingCodingRuleParams; - [READ_COMMANDS.GET_DEFAULT_MILEAGE_RATES]: Parameters.GetDefaultMileageRatesParams; + [READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE]: Parameters.GetDefaultP2PMileageRateParams; }; const SIDE_EFFECT_REQUEST_COMMANDS = { diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 6d23c2c710c7..2e89e7a9d39d 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -4,7 +4,7 @@ import type {CurrencyListActionsContextType} from '@components/CurrencyListConte import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {DefaultMileageRate, LastSelectedDistanceRates, OnyxInputOrEntry, Transaction} from '@src/types/onyx'; +import type {DefaultP2PMileageRate, LastSelectedDistanceRates, OnyxInputOrEntry, Transaction} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -14,9 +14,9 @@ import {replaceAllDigits} from './MoneyRequestUtils'; import {getDistanceRateCustomUnit, getDistanceRateCustomUnitRate, getPersonalPolicy, getUnitRateValue} from './PolicyUtils'; import {getCurrency, getRateID, isCustomUnitRateIDForP2P, isExpenseUnreported} from './TransactionUtils'; -let defaultP2PMileageRate: DefaultMileageRate | undefined; +let defaultP2PMileageRate: DefaultP2PMileageRate | undefined; Onyx.connect({ - key: ONYXKEYS.DEFAULT_MILEAGE_RATES, + key: ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE, callback: (value) => { defaultP2PMileageRate = value ?? undefined; }, diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 26a348b73d9d..580f505e23fe 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -240,7 +240,7 @@ import type {BuildPolicyDataKeys} from '@userActions/Policy/Policy'; import {buildOptimisticPolicyRecentlyUsedTags} from '@userActions/Policy/Tag'; import type {GuidedSetupData} from '@userActions/Report'; import {buildInviteToRoomOnyxData, completeOnboarding, notifyNewAction, optimisticReportLastData} from '@userActions/Report'; -import {fetchDefaultMileageRates, mergeTransactionIdsHighlightOnSearchRoute, sanitizeRecentWaypoints} from '@userActions/Transaction'; +import {fetchDefaultP2PMileageRate, mergeTransactionIdsHighlightOnSearchRoute, sanitizeRecentWaypoints} from '@userActions/Transaction'; import {removeDraftTransaction, removeDraftTransactions, removeDraftTransactionsByIDs} from '@userActions/TransactionEdit'; import {getOnboardingMessages} from '@userActions/Welcome/OnboardingFlow'; import type {OnboardingCompanySize} from '@userActions/Welcome/OnboardingFlow'; @@ -1347,8 +1347,7 @@ function initMoneyRequest({ comment.odometerEndImage = undefined; } - // Fetch the default mileage rate for the user's currency from Auth (single source of truth) - fetchDefaultMileageRates(currency); + fetchDefaultP2PMileageRate(currency); } if (newIouRequestType === CONST.IOU.REQUEST_TYPE.PER_DIEM) { diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 8c5099b408e9..e288f465634d 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -793,11 +793,11 @@ function openDraftDistanceExpense() { } /** - * Fetches the default mileage rates from Auth for the given currency. - * These rates are used for P2P distance requests and stored in Onyx. + * Fetches the default P2P mileage rate from Auth for the given currency. + * The rate is stored in Onyx via the onyxData returned by Auth. */ -function fetchDefaultMileageRates(currency: string) { - API.read(READ_COMMANDS.GET_DEFAULT_MILEAGE_RATES, {currency}, {}); +function fetchDefaultP2PMileageRate(currency: string) { + API.read(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, {currency}, {}); } /** @@ -1690,7 +1690,7 @@ export { setReviewDuplicatesKey, abandonReviewDuplicateTransactions, openDraftDistanceExpense, - fetchDefaultMileageRates, + fetchDefaultP2PMileageRate, sanitizeRecentWaypoints, getLastModifiedExpense, revert, diff --git a/src/types/onyx/DefaultMileageRate.ts b/src/types/onyx/DefaultP2PMileageRate.ts similarity index 54% rename from src/types/onyx/DefaultMileageRate.ts rename to src/types/onyx/DefaultP2PMileageRate.ts index d3927d953697..f413df043732 100644 --- a/src/types/onyx/DefaultMileageRate.ts +++ b/src/types/onyx/DefaultP2PMileageRate.ts @@ -1,7 +1,7 @@ import type {Unit} from './Policy'; -/** Model of a default mileage rate fetched from the backend */ -type DefaultMileageRate = { +/** Model of the default P2P mileage rate fetched from Auth */ +type DefaultP2PMileageRate = { /** Rate in cents per unit */ rate: number; @@ -9,4 +9,4 @@ type DefaultMileageRate = { unit: Unit; }; -export default DefaultMileageRate; +export default DefaultP2PMileageRate; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 7f58911dbb4e..a1dd96fa133a 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -41,7 +41,7 @@ import type Credentials from './Credentials'; import type Currency from './Currency'; import type {CurrencyList} from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; -import type DefaultMileageRate from './DefaultMileageRate'; +import type DefaultP2PMileageRate from './DefaultP2PMileageRate'; import type { CardFeedErrorsDerivedValue, NonPersonalAndWorkspaceCardListDerivedValue, @@ -201,7 +201,7 @@ export type { Currency, CurrencyList, CustomStatusDraft, - DefaultMileageRate, + DefaultP2PMileageRate, UnshareBankAccount, DismissedReferralBanners, Domain, From 189c5588382a73da47fb82be84c1e8502871a445 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 23 Mar 2026 12:30:22 -0700 Subject: [PATCH 06/51] Remove CURRENCY_TO_DEFAULT_MILEAGE_RATE from App Auth is now the single source of truth for default P2P mileage rates. The rate is fetched from Auth and stored in Onyx. Removed the ~670 line hardcoded JSON mapping from CONST/index.ts and updated DistanceRequestUtils and Transaction actions to use the Onyx value with a minimal USD fallback. Made-with: Cursor --- src/CONST/index.ts | 673 ------------------------------- src/libs/DistanceRequestUtils.ts | 30 +- src/libs/actions/Transaction.ts | 2 +- 3 files changed, 9 insertions(+), 696 deletions(-) diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 5742357fa85f..33a717aa14ca 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -6,7 +6,6 @@ import * as KeyCommand from 'react-native-key-command'; import type {ValueOf} from 'type-fest'; import type {SearchFilterKey} from '@components/Search/types'; import type ResponsiveLayoutResult from '@hooks/useResponsiveLayout/types'; -import type {MileageRate} from '@libs/DistanceRequestUtils'; import MULTIFACTOR_AUTHENTICATION_VALUES from '@libs/MultifactorAuthentication/Biometrics/VALUES'; import addTrailingForwardSlash from '@libs/UrlUtils'; import variables from '@styles/variables'; @@ -6374,678 +6373,6 @@ const CONST = { }, }, - /* If we update these values, let's ensure this logic is consistent with the logic in the backend (Auth), since we're using the same method to calculate the rate value in distance requests created via Concierge. */ - CURRENCY_TO_DEFAULT_MILEAGE_RATE: JSON.parse(`{ - "AED": { - "rate": 428.5, - "unit": "km" - }, - "AFN": { - "rate": 7703.93, - "unit": "km" - }, - "ALL": { - "rate": 9633.54, - "unit": "km" - }, - "AMD": { - "rate": 44504.28, - "unit": "km" - }, - "ANG": { - "rate": 208.86, - "unit": "km" - }, - "AOA": { - "rate": 106762.22, - "unit": "km" - }, - "ARS": { - "rate": 171174.53, - "unit": "km" - }, - "AUD": { - "rate": 88, - "unit": "km" - }, - "AWG": { - "rate": 210.31, - "unit": "km" - }, - "AZN": { - "rate": 198.35, - "unit": "km" - }, - "BAM": { - "rate": 194.86, - "unit": "km" - }, - "BBD": { - "rate": 233.35, - "unit": "km" - }, - "BDT": { - "rate": 14251.98, - "unit": "km" - }, - "BGN": { - "rate": 195.25, - "unit": "km" - }, - "BHD": { - "rate": 43.98, - "unit": "km" - }, - "BIF": { - "rate": 345436.14, - "unit": "km" - }, - "BMD": { - "rate": 116.68, - "unit": "km" - }, - "BND": { - "rate": 149.25, - "unit": "km" - }, - "BOB": { - "rate": 807.7, - "unit": "km" - }, - "BRL": { - "rate": 626.95, - "unit": "km" - }, - "BSD": { - "rate": 116.68, - "unit": "km" - }, - "BTN": { - "rate": 10516.86, - "unit": "km" - }, - "BWP": { - "rate": 1619.97, - "unit": "km" - }, - "BYN": { - "rate": 342.9, - "unit": "km" - }, - "BYR": { - "rate": 2336612.1, - "unit": "km" - }, - "BZD": { - "rate": 234.56, - "unit": "km" - }, - "CAD": { - "rate": 72, - "unit": "km" - }, - "CDF": { - "rate": 264869.39, - "unit": "km" - }, - "CHF": { - "rate": 76, - "unit": "km" - }, - "CLP": { - "rate": 104301.62, - "unit": "km" - }, - "CNY": { - "rate": 814.84, - "unit": "km" - }, - "COP": { - "rate": 439901.44, - "unit": "km" - }, - "CRC": { - "rate": 57973.09, - "unit": "km" - }, - "CUC": { - "rate": 116.68, - "unit": "km" - }, - "CUP": { - "rate": 3004.45, - "unit": "km" - }, - "CVE": { - "rate": 11004.01, - "unit": "km" - }, - "CZK": { - "rate": 2413.34, - "unit": "km" - }, - "DJF": { - "rate": 20752.5, - "unit": "km" - }, - "DKK": { - "rate": 394, - "unit": "km" - }, - "DOP": { - "rate": 7398.54, - "unit": "km" - }, - "DZD": { - "rate": 15153.28, - "unit": "km" - }, - "EEK": { - "rate": 1704.36, - "unit": "km" - }, - "EGP": { - "rate": 5512.97, - "unit": "km" - }, - "ERN": { - "rate": 1750.16, - "unit": "km" - }, - "ETB": { - "rate": 18077.09, - "unit": "km" - }, - "EUR": { - "rate": 30, - "unit": "km" - }, - "FJD": { - "rate": 265.25, - "unit": "km" - }, - "FKP": { - "rate": 86.44, - "unit": "km" - }, - "GBP": { - "rate": 45, - "unit": "mi" - }, - "GEL": { - "rate": 313.87, - "unit": "km" - }, - "GHS": { - "rate": 1246.23, - "unit": "km" - }, - "GIP": { - "rate": 86.44, - "unit": "km" - }, - "GMD": { - "rate": 8575.79, - "unit": "km" - }, - "GNF": { - "rate": 1019841.61, - "unit": "km" - }, - "GTQ": { - "rate": 893.94, - "unit": "km" - }, - "GYD": { - "rate": 24400.12, - "unit": "km" - }, - "HKD": { - "rate": 908.6, - "unit": "km" - }, - "HNL": { - "rate": 3080.03, - "unit": "km" - }, - "HRK": { - "rate": 752.16, - "unit": "km" - }, - "HTG": { - "rate": 15266.76, - "unit": "km" - }, - "HUF": { - "rate": 38407.92, - "unit": "km" - }, - "IDR": { - "rate": 1954232.16, - "unit": "km" - }, - "ILS": { - "rate": 540, - "unit": "km" - }, - "INR": { - "rate": 10518.19, - "unit": "km" - }, - "IQD": { - "rate": 152781.51, - "unit": "km" - }, - "IRR": { - "rate": 4910488.26, - "unit": "km" - }, - "ISK": { - "rate": 14694.92, - "unit": "km" - }, - "JMD": { - "rate": 18515.8, - "unit": "km" - }, - "JOD": { - "rate": 82.72, - "unit": "km" - }, - "JPY": { - "rate": 18278.93, - "unit": "km" - }, - "KES": { - "rate": 15058.57, - "unit": "km" - }, - "KGS": { - "rate": 10202.68, - "unit": "km" - }, - "KHR": { - "rate": 468558.4, - "unit": "km" - }, - "KMF": { - "rate": 49237.89, - "unit": "km" - }, - "KPW": { - "rate": 105009.73, - "unit": "km" - }, - "KRW": { - "rate": 168722.07, - "unit": "km" - }, - "KWD": { - "rate": 35.82, - "unit": "km" - }, - "KYD": { - "rate": 97.19, - "unit": "km" - }, - "KZT": { - "rate": 59441.07, - "unit": "km" - }, - "LAK": { - "rate": 2520371.89, - "unit": "km" - }, - "LBP": { - "rate": 10444573.37, - "unit": "km" - }, - "LKR": { - "rate": 36161.18, - "unit": "km" - }, - "LRD": { - "rate": 20916.15, - "unit": "km" - }, - "LSL": { - "rate": 1909.99, - "unit": "km" - }, - "LTL": { - "rate": 376.1, - "unit": "km" - }, - "LVL": { - "rate": 76.56, - "unit": "km" - }, - "LYD": { - "rate": 631.41, - "unit": "km" - }, - "MAD": { - "rate": 1070.73, - "unit": "km" - }, - "MDL": { - "rate": 1950.82, - "unit": "km" - }, - "MGA": { - "rate": 537347.25, - "unit": "km" - }, - "MKD": { - "rate": 6144.12, - "unit": "km" - }, - "MMK": { - "rate": 245011.43, - "unit": "km" - }, - "MNT": { - "rate": 415371.81, - "unit": "km" - }, - "MOP": { - "rate": 935.53, - "unit": "km" - }, - "MRO": { - "rate": 41653.05, - "unit": "km" - }, - "MRU": { - "rate": 4631.69, - "unit": "km" - }, - "MUR": { - "rate": 5402.17, - "unit": "km" - }, - "MVR": { - "rate": 1803.83, - "unit": "km" - }, - "MWK": { - "rate": 202409.9, - "unit": "km" - }, - "MXN": { - "rate": 93, - "unit": "km" - }, - "MYR": { - "rate": 472.6, - "unit": "km" - }, - "MZN": { - "rate": 7456.85, - "unit": "km" - }, - "NAD": { - "rate": 1910.94, - "unit": "km" - }, - "NGN": { - "rate": 166247.91, - "unit": "km" - }, - "NIO": { - "rate": 4291.54, - "unit": "km" - }, - "NOK": { - "rate": 350, - "unit": "km" - }, - "NPR": { - "rate": 16827.52, - "unit": "km" - }, - "NZD": { - "rate": 117, - "unit": "km" - }, - "OMR": { - "rate": 44.87, - "unit": "km" - }, - "PAB": { - "rate": 116.68, - "unit": "km" - }, - "PEN": { - "rate": 392.29, - "unit": "km" - }, - "PGK": { - "rate": 501.36, - "unit": "km" - }, - "PHP": { - "rate": 6917.8, - "unit": "km" - }, - "PKR": { - "rate": 32652.84, - "unit": "km" - }, - "PLN": { - "rate": 89, - "unit": "km" - }, - "PYG": { - "rate": 787561.7, - "unit": "km" - }, - "QAR": { - "rate": 424.92, - "unit": "km" - }, - "RON": { - "rate": 508.07, - "unit": "km" - }, - "RSD": { - "rate": 11713.4, - "unit": "km" - }, - "RUB": { - "rate": 9392.54, - "unit": "km" - }, - "RWF": { - "rate": 169805.16, - "unit": "km" - }, - "SAR": { - "rate": 437.58, - "unit": "km" - }, - "SBD": { - "rate": 948.61, - "unit": "km" - }, - "SCR": { - "rate": 1615.27, - "unit": "km" - }, - "SDG": { - "rate": 70123.16, - "unit": "km" - }, - "SEK": { - "rate": 250, - "unit": "km" - }, - "SGD": { - "rate": 149.43, - "unit": "km" - }, - "SHP": { - "rate": 86.44, - "unit": "km" - }, - "SLL": { - "rate": 2446668.74, - "unit": "km" - }, - "SLE": { - "rate": 2800.26, - "unit": "km" - }, - "SOS": { - "rate": 66591.46, - "unit": "km" - }, - "SRD": { - "rate": 4468.75, - "unit": "km" - }, - "STD": { - "rate": 2599784.99, - "unit": "km" - }, - "STN": { - "rate": 2440.86, - "unit": "km" - }, - "SVC": { - "rate": 1020.49, - "unit": "km" - }, - "SYP": { - "rate": 1517040.54, - "unit": "km" - }, - "SZL": { - "rate": 1910.2, - "unit": "km" - }, - "THB": { - "rate": 3641.51, - "unit": "km" - }, - "TJS": { - "rate": 1077.65, - "unit": "km" - }, - "TMT": { - "rate": 408.37, - "unit": "km" - }, - "TND": { - "rate": 336.58, - "unit": "km" - }, - "TOP": { - "rate": 280.93, - "unit": "km" - }, - "TRY": { - "rate": 5022.36, - "unit": "km" - }, - "TTD": { - "rate": 791.64, - "unit": "km" - }, - "TWD": { - "rate": 3676.16, - "unit": "km" - }, - "TZS": { - "rate": 288716.35, - "unit": "km" - }, - "UAH": { - "rate": 4966.74, - "unit": "km" - }, - "UGX": { - "rate": 422422.35, - "unit": "km" - }, - "USD": { - "rate": 72.5, - "unit": "mi" - }, - "UYU": { - "rate": 4544.27, - "unit": "km" - }, - "UZS": { - "rate": 1395513.79, - "unit": "km" - }, - "VEB": { - "rate": 735113.61, - "unit": "km" - }, - "VEF": { - "rate": 28992910.93, - "unit": "km" - }, - "VES": { - "rate": 35909.77, - "unit": "km" - }, - "VND": { - "rate": 3065619.41, - "unit": "km" - }, - "VUV": { - "rate": 14152.58, - "unit": "km" - }, - "WST": { - "rate": 322.95, - "unit": "km" - }, - "XAF": { - "rate": 65490.66, - "unit": "km" - }, - "XCD": { - "rate": 315.32, - "unit": "km" - }, - "XCG": { - "rate": 210.2, - "unit": "km" - }, - "XOF": { - "rate": 65490.66, - "unit": "km" - }, - "XPF": { - "rate": 11913.98, - "unit": "km" - }, - "YER": { - "rate": 27815.91, - "unit": "km" - }, - "ZAR": { - "rate": 476, - "unit": "km" - }, - "ZMK": { - "rate": 612915.63, - "unit": "km" - }, - "ZMW": { - "rate": 2440.41, - "unit": "km" - }, - "ZWG": { - "rate": 3023.59, - "unit": "km" - } - }`) as Record, - EXIT_SURVEY: { REASONS: { FEATURE_NOT_AVAILABLE: 'featureNotAvailable', diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 2e89e7a9d39d..b5be695b32e4 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -277,44 +277,30 @@ function getDistanceMerchant( return `${distanceInUnits} ${CONST.DISTANCE_MERCHANT_SEPARATOR} ${ratePerUnit}`; } -function ensureRateDefined(rate: number | undefined): asserts rate is number { - if (rate !== undefined) { - return; - } - throw new Error('All default P2P rates should have a rate defined'); -} - /** - * Returns the default P2P mileage rate, preferring the server-fetched rate (from Auth) - * over the hardcoded CURRENCY_TO_DEFAULT_MILEAGE_RATE constant. + * Returns the default P2P mileage rate from Auth (stored in Onyx). + * Falls back to USD defaults if the server-fetched rate hasn't loaded yet. */ +// `currency` is unused; kept for call-site compatibility while the rate comes from Onyx (or USD fallback). +// eslint-disable-next-line @typescript-eslint/no-unused-vars function getDefaultP2PMileageRate(currency: string): {rate: number; unit: Unit} { if (defaultP2PMileageRate) { return defaultP2PMileageRate; } - const fallbackRate = CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ?? CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE.USD; - ensureRateDefined(fallbackRate?.rate); - return {rate: fallbackRate.rate, unit: fallbackRate.unit}; + return {rate: 72.5, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES as Unit}; } /** - * Retrieves the rate and unit for a P2P distance expense for a given currency. - * - * Let's ensure this logic is consistent with the logic in the backend (Auth), since we're using the same method to calculate the rate value in distance requests created via Concierge. - * - * @param currency - * @returns The rate and unit in MileageRate object. + * Retrieves the rate and unit for a P2P distance expense. */ function getRateForP2P(currency: string, transaction: OnyxEntry): MileageRate { - const currencyWithExistingRate = defaultP2PMileageRate || CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ? currency : CONST.CURRENCY.USD; - const mileageRate = getDefaultP2PMileageRate(currencyWithExistingRate); - ensureRateDefined(mileageRate.rate); + const mileageRate = getDefaultP2PMileageRate(currency); // Ensure the rate is updated when the currency changes, otherwise use the stored rate const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? mileageRate.rate) : mileageRate.rate; return { ...mileageRate, - currency: currencyWithExistingRate, + currency, rate, }; } diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index e288f465634d..9450832fe083 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -1027,7 +1027,7 @@ function changeTransactionsReport({ // For distance requests we need to update its custom unit ID to `_FAKE_P2P_ID_` so it's no longer tied to the policy's rate which would cause the "Rate out of policy" violation to appear. // Let's also set the defaultP2PRate and update the distanceUnit, the quantity, the amount, the currency and the merchant to match the P2P rate. if (isDistanceRequest(transaction)) { - const currency = destinationCurrency && CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[destinationCurrency] ? destinationCurrency : CONST.CURRENCY.USD; + const currency = destinationCurrency ?? CONST.CURRENCY.USD; const {rate, unit} = DistanceRequestUtils.getDefaultP2PMileageRate(currency); const distance = parseFloat( DistanceRequestUtils.getRoundedDistanceInUnits( From 7d3b1d1bc1f1ed6c058ac8a1c659ef3662838609 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 26 Mar 2026 09:51:07 -0700 Subject: [PATCH 07/51] Remove currency param from GetDefaultP2PMileageRate calls Auth now determines the correct mileage rate from the authenticated user's personal policy currency, so callers no longer need to pass it. Made-with: Cursor --- .../API/parameters/GetDefaultP2PMileageRateParams.ts | 4 +--- src/libs/DistanceRequestUtils.ts | 6 ++---- src/libs/actions/IOU/index.ts | 2 +- src/libs/actions/Transaction.ts | 9 +++++---- 4 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts b/src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts index 72431cfae356..205b1ebf1ee8 100644 --- a/src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts +++ b/src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts @@ -1,5 +1,3 @@ -type GetDefaultP2PMileageRateParams = { - currency?: string; -}; +type GetDefaultP2PMileageRateParams = Record; export default GetDefaultP2PMileageRateParams; diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index b5be695b32e4..12c555de3cc0 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -281,9 +281,7 @@ function getDistanceMerchant( * Returns the default P2P mileage rate from Auth (stored in Onyx). * Falls back to USD defaults if the server-fetched rate hasn't loaded yet. */ -// `currency` is unused; kept for call-site compatibility while the rate comes from Onyx (or USD fallback). -// eslint-disable-next-line @typescript-eslint/no-unused-vars -function getDefaultP2PMileageRate(currency: string): {rate: number; unit: Unit} { +function getDefaultP2PMileageRate(): {rate: number; unit: Unit} { if (defaultP2PMileageRate) { return defaultP2PMileageRate; } @@ -294,7 +292,7 @@ function getDefaultP2PMileageRate(currency: string): {rate: number; unit: Unit} * Retrieves the rate and unit for a P2P distance expense. */ function getRateForP2P(currency: string, transaction: OnyxEntry): MileageRate { - const mileageRate = getDefaultP2PMileageRate(currency); + const mileageRate = getDefaultP2PMileageRate(); // Ensure the rate is updated when the currency changes, otherwise use the stored rate const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? mileageRate.rate) : mileageRate.rate; diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 580f505e23fe..1dbc935bc4fb 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -1347,7 +1347,7 @@ function initMoneyRequest({ comment.odometerEndImage = undefined; } - fetchDefaultP2PMileageRate(currency); + fetchDefaultP2PMileageRate(); } if (newIouRequestType === CONST.IOU.REQUEST_TYPE.PER_DIEM) { diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 9450832fe083..dae6d44886e9 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -793,11 +793,12 @@ function openDraftDistanceExpense() { } /** - * Fetches the default P2P mileage rate from Auth for the given currency. + * Fetches the default P2P mileage rate from Auth. + * Auth determines the rate from the user's personal policy currency. * The rate is stored in Onyx via the onyxData returned by Auth. */ -function fetchDefaultP2PMileageRate(currency: string) { - API.read(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, {currency}, {}); +function fetchDefaultP2PMileageRate() { + API.read(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, {}, {}); } /** @@ -1028,7 +1029,7 @@ function changeTransactionsReport({ // Let's also set the defaultP2PRate and update the distanceUnit, the quantity, the amount, the currency and the merchant to match the P2P rate. if (isDistanceRequest(transaction)) { const currency = destinationCurrency ?? CONST.CURRENCY.USD; - const {rate, unit} = DistanceRequestUtils.getDefaultP2PMileageRate(currency); + const {rate, unit} = DistanceRequestUtils.getDefaultP2PMileageRate(); const distance = parseFloat( DistanceRequestUtils.getRoundedDistanceInUnits( getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES), From c8c679929fab8b05f8d5a77c5b938e4c206354d0 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 26 Mar 2026 10:17:00 -0700 Subject: [PATCH 08/51] Refactor: Replace Onyx.connect with useOnyx for defaultP2PMileageRate Remove Onyx.connect from DistanceRequestUtils.ts to make utility functions pure. The defaultP2PMileageRate value is now passed as an optional parameter through getDefaultP2PMileageRate, getRateForP2P, and getRate. - React component callers use useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE) - Action files use getStoredDefaultP2PMileageRate() from Transaction.ts - TransactionUtils functions thread the parameter from their callers Made-with: Cursor --- .../DistanceRequest/DistanceRequestFooter.tsx | 3 +- .../useUpdateGpsNotification/index.ios.ts | 5 ++- .../MoneyRequestConfirmationList.tsx | 3 +- .../ReportActionItem/MoneyRequestView.tsx | 3 +- .../TransactionPreviewContent.tsx | 7 +++- src/components/TransactionItemRow/index.tsx | 8 +++- src/libs/DistanceRequestUtils.ts | 38 ++++++++++--------- src/libs/TransactionPreviewUtils.ts | 8 +++- src/libs/TransactionUtils/index.ts | 28 ++++++++++---- src/libs/actions/IOU/MoneyRequest.ts | 6 +-- src/libs/actions/IOU/Split.ts | 16 +++++--- src/libs/actions/IOU/index.ts | 12 +++++- src/libs/actions/Report/index.ts | 2 + src/libs/actions/SplitExpenses.ts | 3 +- src/libs/actions/Transaction.ts | 21 +++++++--- src/pages/iou/SplitExpenseEditPage.tsx | 3 +- src/pages/iou/SplitExpensePage.tsx | 3 +- .../request/step/IOURequestStepDistance.tsx | 5 ++- .../index.native.tsx | 3 +- .../step/IOURequestStepDistanceManual.tsx | 2 + .../step/IOURequestStepDistanceMap.tsx | 5 ++- .../step/IOURequestStepDistanceOdometer.tsx | 7 +++- 22 files changed, 130 insertions(+), 61 deletions(-) diff --git a/src/components/DistanceRequest/DistanceRequestFooter.tsx b/src/components/DistanceRequest/DistanceRequestFooter.tsx index 4e499a4d6839..adc4172fe782 100644 --- a/src/components/DistanceRequest/DistanceRequestFooter.tsx +++ b/src/components/DistanceRequest/DistanceRequestFooter.tsx @@ -47,13 +47,14 @@ function DistanceRequestFooter({waypoints, transaction, navigateToWaypointEditPa const activePolicy = usePolicy(activePolicyID); const personalPolicy = usePolicy(personalPolicyID); const [mapboxAccessToken] = useOnyx(ONYXKEYS.MAPBOX_ACCESS_TOKEN); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const numberOfWaypoints = Object.keys(waypoints ?? {}).length; const numberOfFilledWaypoints = Object.values(waypoints ?? {}).filter((waypoint) => waypoint?.address).length; const lastWaypointIndex = numberOfWaypoints - 1; const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy ?? activePolicy); const policyCurrency = (policy ?? activePolicy ?? personalPolicy)?.outputCurrency ?? CONST.CURRENCY.USD; - const mileageRate = isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction) : defaultMileageRate; + const mileageRate = isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction, defaultP2PMileageRate) : defaultMileageRate; const {unit} = mileageRate ?? {}; const getMarkerComponent = useCallback( diff --git a/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ios.ts b/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ios.ts index 15100b07ea23..71f01f17a0fa 100644 --- a/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ios.ts +++ b/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ios.ts @@ -38,11 +38,12 @@ function useUpdateGpsNotificationOnUnitChange() { const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const defaultExpensePolicy = useDefaultExpensePolicy(); const shouldUseDefaultExpensePolicy = shouldUseDefaultExpensePolicyUtil(CONST.IOU.TYPE.CREATE, defaultExpensePolicy, amountOwed, ownerBillingGraceEndPeriod); - const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : undefined}).unit; + const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : undefined, defaultP2PMileageRate}).unit; useEffect(() => { if (!shouldUpdateGpsNotificationUnit()) { @@ -50,7 +51,7 @@ function useUpdateGpsNotificationOnUnitChange() { } updateGpsTripNotificationUnit(translate, unit); - }, [unit, translate]); + }, [unit, translate, defaultP2PMileageRate]); } export default useUpdateGpsNotification; diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 6edffb9e7467..9115f4a4b01f 100644 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -298,6 +298,7 @@ function MoneyRequestConfirmationList({ }); const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${policyID}`); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const {getCurrencySymbol, getCurrencyDecimals} = useCurrencyListActions(); const {isBetaEnabled} = usePermissions(); const isNewManualExpenseFlowEnabled = isBetaEnabled(CONST.BETAS.NEW_MANUAL_EXPENSE_FLOW); @@ -363,7 +364,7 @@ function MoneyRequestConfirmationList({ const defaultRate = defaultMileageRate?.customUnitRateID; const lastSelectedRate = policy?.id ? (lastSelectedDistanceRates?.[policy.id] ?? defaultRate) : defaultRate; - const mileageRate = DistanceRequestUtils.getRate({transaction, policy, policyDraft}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy, policyDraft, defaultP2PMileageRate}); const rate = mileageRate.rate; const prevRate = usePrevious(rate); const unit = mileageRate.unit; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 1dc34d4459d4..829bd7ac542e 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -335,6 +335,7 @@ function MoneyRequestView({ const [transactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const hasMultipleSplits = useMemo( () => hasMultipleSplitChildren(allTransactions, allReports, transaction?.comment?.originalTransactionID), [allTransactions, allReports, transaction?.comment?.originalTransactionID], @@ -452,7 +453,7 @@ function MoneyRequestView({ let amountDescription = `${translate('iou.amount')}`; let dateDescription = `${translate('common.date')}`; - const {unit, rate, name: rateName} = DistanceRequestUtils.getRate({transaction: updatedTransaction ?? transaction, policy}); + const {unit, rate, name: rateName} = DistanceRequestUtils.getRate({transaction: updatedTransaction ?? transaction, policy, defaultP2PMileageRate}); const distance = getDistanceInMeters(transactionBackup ?? updatedTransaction ?? transaction, unit); const currency = transactionCurrency ?? CONST.CURRENCY.USD; const hasRequiredCompanyCardViolation = transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.COMPANY_CARD_REQUIRED); diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index 2fb5d3c106fe..b4d65a5a5cf3 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -74,6 +74,7 @@ function TransactionPreviewContent({ const {translate} = useLocalize(); const {environmentURL} = useEnvironment(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const isParentPolicyExpenseChat = isPolicyExpenseChat(chatReport); const transactionDetails = useMemo>( () => getTransactionDetails(transaction, undefined, policy, isParentPolicyExpenseChat) ?? {}, @@ -114,8 +115,9 @@ function TransactionPreviewContent({ currentUserEmail, currentUserAccountID, reportActions, + defaultP2PMileageRate, }), - [areThereDuplicates, transactionPreviewCommonArguments, isReportAPolicyExpenseChat, currentUserEmail, currentUserAccountID, reportActions], + [areThereDuplicates, transactionPreviewCommonArguments, isReportAPolicyExpenseChat, currentUserEmail, currentUserAccountID, reportActions, defaultP2PMileageRate], ); const {shouldShowRBR, shouldShowMerchant, shouldShowSplitShare, shouldShowTag, shouldShowCategory, shouldShowSkeleton, shouldShowDescription} = conditionals; @@ -156,8 +158,9 @@ function TransactionPreviewContent({ currentUserEmail, currentUserAccountID, originalTransaction, + defaultP2PMileageRate, }), - [transactionPreviewCommonArguments, shouldShowRBR, violationMessage, reportActions, currentUserEmail, currentUserAccountID, originalTransaction], + [transactionPreviewCommonArguments, shouldShowRBR, violationMessage, reportActions, currentUserEmail, currentUserAccountID, originalTransaction, defaultP2PMileageRate], ); const getTranslatedText = (item: TranslationPathOrText) => (item.translationPath ? translate(item.translationPath) : (item.text ?? '')); diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index 4ce29ff74e99..69c03bda2437 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -18,6 +18,7 @@ import type {SearchColumnType, TableColumnSize} from '@components/Search/types'; import Text from '@components/Text'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -46,6 +47,7 @@ import { } from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; +import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, Policy, Report, ReportAction, TransactionViolation} from '@src/types/onyx'; import type {SearchTransactionAction} from '@src/types/onyx/SearchResults'; import CategoryCell from './DataCells/CategoryCell'; @@ -203,6 +205,8 @@ function TransactionItemRow({ const expensicons = useMemoizedLazyExpensifyIcons(['ArrowRight']); const transactionThreadReportID = reportActions ? getIOUActionForTransactionID(reportActions, transactionItem.transactionID)?.childReportID : undefined; + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); + const isDateColumnWide = dateColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; const isSubmittedColumnWide = submittedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; const isApprovedColumnWide = approvedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; @@ -230,7 +234,7 @@ function TransactionItemRow({ } const policyParam = policy ?? transactionItem.policy; - const isCustomUnitOutOfPolicy = isUnreportedAndHasInvalidDistanceRateTransaction(transactionItem, policyParam); + const isCustomUnitOutOfPolicy = isUnreportedAndHasInvalidDistanceRateTransaction(transactionItem, policyParam, defaultP2PMileageRate); const hasFieldErrors = hasMissingSmartscanFields(transactionItem, report) || isCustomUnitOutOfPolicy; if (hasFieldErrors) { const amountMissing = isAmountMissing(transactionItem); @@ -248,7 +252,7 @@ function TransactionItemRow({ } return error; } - }, [transactionItem, translate, report, policy]); + }, [transactionItem, translate, report, policy, defaultP2PMileageRate]); const exchangeRateMessage = getExchangeRate(transactionItem); diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 6f928cec8f3a..43eacefff5a7 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -1,9 +1,7 @@ import type {OnyxEntry} from 'react-native-onyx'; -import Onyx from 'react-native-onyx'; import type {CurrencyListActionsContextType} from '@components/CurrencyListContextProvider'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import type {DefaultP2PMileageRate, LastSelectedDistanceRates, OnyxInputOrEntry, Transaction} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; @@ -14,14 +12,6 @@ import {replaceAllDigits} from './MoneyRequestUtils'; import {getDistanceRateCustomUnit, getDistanceRateCustomUnitRate, getPersonalPolicy, getUnitRateValue} from './PolicyUtils'; import {getCurrency, getRateID, isCustomUnitRateIDForP2P, isExpenseUnreported} from './TransactionUtils'; -let defaultP2PMileageRate: DefaultP2PMileageRate | undefined; -Onyx.connect({ - key: ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE, - callback: (value) => { - defaultP2PMileageRate = value ?? undefined; - }, -}); - type MileageRate = { customUnitRateID?: string; rate?: number; @@ -281,9 +271,9 @@ function getDistanceMerchant( * Returns the default P2P mileage rate from Auth (stored in Onyx). * Falls back to USD defaults if the server-fetched rate hasn't loaded yet. */ -function getDefaultP2PMileageRate(): {rate: number; unit: Unit} { - if (defaultP2PMileageRate) { - return defaultP2PMileageRate; +function getDefaultP2PMileageRate(storedRate?: DefaultP2PMileageRate | null): {rate: number; unit: Unit} { + if (storedRate) { + return storedRate; } return {rate: 72.5, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES as Unit}; } @@ -291,8 +281,8 @@ function getDefaultP2PMileageRate(): {rate: number; unit: Unit} { /** * Retrieves the rate and unit for a P2P distance expense. */ -function getRateForP2P(currency: string, transaction: OnyxEntry): MileageRate { - const mileageRate = getDefaultP2PMileageRate(); +function getRateForP2P(currency: string, transaction: OnyxEntry, defaultP2PMileageRate?: DefaultP2PMileageRate | null): MileageRate { + const mileageRate = getDefaultP2PMileageRate(defaultP2PMileageRate); // Ensure the rate is updated when the currency changes, otherwise use the stored rate const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? mileageRate.rate) : mileageRate.rate; @@ -415,11 +405,13 @@ function getRate({ policy, policyDraft, useTransactionDistanceUnit = true, + defaultP2PMileageRate, }: { transaction: OnyxEntry; policy: OnyxEntry; policyDraft?: OnyxEntry; useTransactionDistanceUnit?: boolean; + defaultP2PMileageRate?: DefaultP2PMileageRate | null; }): MileageRate { let mileageRates = getMileageRates(policy, true, transaction?.comment?.customUnit?.customUnitRateID); if (isEmptyObject(mileageRates) && policyDraft) { @@ -437,7 +429,7 @@ function getRate({ const customUnitRateID = getRateID(transaction); // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing const customMileageRate = (customUnitRateID && mileageRates?.[customUnitRateID]) || defaultMileageRate; - const mileageRate = isCustomUnitRateIDForP2P(transaction) ? getRateForP2P(currency, transaction) : customMileageRate; + const mileageRate = isCustomUnitRateIDForP2P(transaction) ? getRateForP2P(currency, transaction, defaultP2PMileageRate) : customMileageRate; const unit = getDistanceUnit(useTransactionDistanceUnit ? transaction : undefined, mileageRate); return { ...mileageRate, @@ -453,8 +445,18 @@ function getRate({ * For example, if an expense is '10 mi @ $1.00 / mi' and the rate is updated to '$1.00 / km', * then the updated distance unit should be 'km' from the updated rate, not 'mi' from the currently stored transaction distance unit. */ -function getUpdatedDistanceUnit({transaction, policy, policyDraft}: {transaction: OnyxEntry; policy: OnyxEntry; policyDraft?: OnyxEntry}) { - return getRate({transaction, policy, policyDraft, useTransactionDistanceUnit: false}).unit; +function getUpdatedDistanceUnit({ + transaction, + policy, + policyDraft, + defaultP2PMileageRate, +}: { + transaction: OnyxEntry; + policy: OnyxEntry; + policyDraft?: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate | null; +}) { + return getRate({transaction, policy, policyDraft, useTransactionDistanceUnit: false, defaultP2PMileageRate}).unit; } /** diff --git a/src/libs/TransactionPreviewUtils.ts b/src/libs/TransactionPreviewUtils.ts index ce01f0d59bf9..675ab64002a9 100644 --- a/src/libs/TransactionPreviewUtils.ts +++ b/src/libs/TransactionPreviewUtils.ts @@ -204,6 +204,7 @@ function getTransactionPreviewTextAndTranslationPaths({ currentUserEmail, currentUserAccountID, originalTransaction, + defaultP2PMileageRate, }: { iouReport: OnyxEntry; policy: OnyxEntry; @@ -218,6 +219,7 @@ function getTransactionPreviewTextAndTranslationPaths({ currentUserEmail: string; currentUserAccountID: number; originalTransaction?: OnyxEntry; + defaultP2PMileageRate?: OnyxTypes.DefaultP2PMileageRate | null; }) { const isFetchingWaypoints = isFetchingWaypointsFromServer(transaction); const isTransactionOnHold = isOnHold(transaction); @@ -293,7 +295,7 @@ function getTransactionPreviewTextAndTranslationPaths({ let previewHeaderText: TranslationPathOrText[] = [{translationPath: getExpenseTypeTranslationKey(getTransactionType(transaction))}]; if (isDistanceRequest(transaction)) { - if (RBRMessage === undefined && isUnreportedAndHasInvalidDistanceRateTransaction(transaction, policy)) { + if (RBRMessage === undefined && isUnreportedAndHasInvalidDistanceRateTransaction(transaction, policy, defaultP2PMileageRate)) { RBRMessage = {translationPath: 'violations.customUnitOutOfPolicy'}; } } else if (isTransactionScanning) { @@ -371,6 +373,7 @@ function createTransactionPreviewConditionals({ currentUserEmail, currentUserAccountID, reportActions, + defaultP2PMileageRate, }: { iouReport: OnyxEntry; policy: OnyxEntry; @@ -384,6 +387,7 @@ function createTransactionPreviewConditionals({ currentUserEmail: string; currentUserAccountID: number; reportActions?: OnyxTypes.ReportActions; + defaultP2PMileageRate?: OnyxTypes.DefaultP2PMileageRate | null; }) { const {amount: requestAmount, comment: requestComment, merchant, tag, category} = transactionDetails; @@ -412,7 +416,7 @@ function createTransactionPreviewConditionals({ const shouldShowCategory = !!categoryForDisplay && isReportAPolicyExpenseChat; const hasAnyViolations = - isUnreportedAndHasInvalidDistanceRateTransaction(transaction, policy) || + isUnreportedAndHasInvalidDistanceRateTransaction(transaction, policy, defaultP2PMileageRate) || // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing hasViolationsOfTypeNotice || hasWarningTypeViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport ?? undefined, policy) || diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 42e23e5cd9ee..f87c90f362ed 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -54,6 +54,7 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type { Card, + DefaultP2PMileageRate, OnyxInputOrEntry, Policy, PolicyCategories, @@ -686,6 +687,7 @@ function getUpdatedTransaction({ shouldUpdateReceiptState = true, policy = undefined, isSplitTransaction = false, + defaultP2PMileageRate = undefined, }: { transaction: Transaction; transactionChanges: TransactionChanges; @@ -693,6 +695,7 @@ function getUpdatedTransaction({ shouldUpdateReceiptState?: boolean; policy?: OnyxEntry; isSplitTransaction?: boolean; + defaultP2PMileageRate?: DefaultP2PMileageRate | null; }): Transaction { const isUnReportedExpense = transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID; @@ -740,7 +743,7 @@ function getUpdatedTransaction({ // eslint-disable-next-line @typescript-eslint/no-deprecated updatedTransaction.modifiedMerchant = translateLocal('iou.fieldPending'); } else { - const mileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy}); + const mileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy, defaultP2PMileageRate}); const {unit, rate} = mileageRate; const distanceInMeters = getDistanceInMeters(transaction, unit); @@ -781,7 +784,7 @@ function getUpdatedTransaction({ const existingDistanceUnit = transaction?.comment?.customUnit?.distanceUnit; // Get the new distance unit from the rate's unit - const newDistanceUnit = DistanceRequestUtils.getUpdatedDistanceUnit({transaction: updatedTransaction, policy}); + const newDistanceUnit = DistanceRequestUtils.getUpdatedDistanceUnit({transaction: updatedTransaction, policy, defaultP2PMileageRate}); lodashSet(updatedTransaction, 'comment.customUnit.distanceUnit', newDistanceUnit); // If the distanceUnit is set and the rate is changed to one that has a different unit, convert the distance to the new unit. @@ -796,7 +799,12 @@ function getUpdatedTransaction({ // When the waypoints are being fetched from the server, we have no information about the distance, and cannot recalculate the updated amount. // Otherwise, recalculate the fields based on the new rate. - const updatedMileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy, useTransactionDistanceUnit: false}); + const updatedMileageRate = DistanceRequestUtils.getRate({ + transaction: updatedTransaction, + policy, + useTransactionDistanceUnit: false, + defaultP2PMileageRate, + }); const {unit, rate} = updatedMileageRate; const distanceInMeters = getDistanceInMeters(updatedTransaction, unit); @@ -881,7 +889,12 @@ function getUpdatedTransaction({ lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance); shouldStopSmartscan = true; - const updatedMileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy, useTransactionDistanceUnit: false}); + const updatedMileageRate = DistanceRequestUtils.getRate({ + transaction: updatedTransaction, + policy, + useTransactionDistanceUnit: false, + defaultP2PMileageRate, + }); const {unit, rate} = updatedMileageRate; const distanceInMeters = getDistanceInMeters(updatedTransaction, unit); @@ -1136,9 +1149,9 @@ function isFetchingWaypointsFromServer(transaction: OnyxInputOrEntry, policy: OnyxEntry) { +function isUnreportedAndHasInvalidDistanceRateTransaction(transaction: OnyxInputOrEntry, policy: OnyxEntry, defaultP2PMileageRate?: DefaultP2PMileageRate | null) { if (transaction && isDistanceRequest(transaction)) { - const {rate} = DistanceRequestUtils.getRate({transaction, policy}); + const {rate} = DistanceRequestUtils.getRate({transaction, policy, defaultP2PMileageRate}); const isUnreportedExpense = !transaction.reportID || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID || String(transaction.reportID) === CONST.REPORT.SPLIT_REPORT_ID; if (isUnreportedExpense && !rate) { @@ -2824,6 +2837,7 @@ function recalculateUnreportedTransactionDetails( destinationCurrency: string | undefined, translateParam: LocaleContextProps['translate'], toLocaleDigitParam: LocaleContextProps['toLocaleDigit'], + defaultP2PMileageRate?: DefaultP2PMileageRate | null, ) { // If the transaction is on hold, we need to unhold it because unreported transactions (on selfDM) should never remain on hold. const comment: NullishDeep = { @@ -2836,7 +2850,7 @@ function recalculateUnreportedTransactionDetails( // For distance requests we need to update its custom unit ID to `_FAKE_P2P_ID_` so it's no longer tied to the policy's rate which would cause the "Rate out of policy" violation to appear. // Let's also set the defaultP2PRate and update the distanceUnit, the quantity, the amount, the currency and the merchant to match the P2P rate. if (isDistanceRequest(transaction)) { - const {rate, unit} = DistanceRequestUtils.getDefaultP2PMileageRate(); + const {rate, unit} = DistanceRequestUtils.getDefaultP2PMileageRate(defaultP2PMileageRate); const currency = destinationCurrency ?? CONST.CURRENCY.USD; const distance = parseFloat( DistanceRequestUtils.getRoundedDistanceInUnits(getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES), unit), diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 20a45b7c6ae0..8cf8fd5b03b3 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -14,7 +14,7 @@ import shouldUseDefaultExpensePolicy from '@libs/shouldUseDefaultExpensePolicy'; import {cancelSpan} from '@libs/telemetry/activeSpans'; import {getValidWaypoints} from '@libs/TransactionUtils'; import type {ReceiptFile} from '@pages/iou/request/step/IOURequestStepScan/types'; -import {setTransactionReport} from '@userActions/Transaction'; +import {getStoredDefaultP2PMileageRate, setTransactionReport} from '@userActions/Transaction'; import type {IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; @@ -643,7 +643,7 @@ function handleMoneyRequestStepDistanceNavigation({ let merchant = translate('iou.fieldPending'); if (isManualDistance && distance !== undefined && unit) { const distanceInMeters = DistanceRequestUtils.convertToDistanceInMeters(distance, unit); - const mileageRate = DistanceRequestUtils.getRate({transaction, policy}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy, defaultP2PMileageRate: getStoredDefaultP2PMileageRate()}); amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, mileageRate?.rate ?? 0); merchant = DistanceRequestUtils.getDistanceMerchant( true, @@ -767,7 +767,7 @@ function handleMoneyRequestStepDistanceNavigation({ // and because of this this report is converted to selfDM here if (isSelfDMReport && distance !== undefined && unit) { const personalCurrency = personalOutputCurrency ?? CONST.CURRENCY.USD; - const personalDistanceUnit = DistanceRequestUtils.getRateForP2P(personalCurrency, transaction).unit; + const personalDistanceUnit = DistanceRequestUtils.getRateForP2P(personalCurrency, transaction, getStoredDefaultP2PMileageRate()).unit; const distanceInMeters = DistanceRequestUtils.convertToDistanceInMeters(distance, unit); const distanceUsingPersonalDistanceUnit = roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, personalDistanceUnit)); setMoneyRequestDistance(transactionID, distanceUsingPersonalDistanceUnit, true, personalDistanceUnit); diff --git a/src/libs/actions/IOU/Split.ts b/src/libs/actions/IOU/Split.ts index 8fb23c4d8644..c4cdc60ee6f8 100644 --- a/src/libs/actions/IOU/Split.ts +++ b/src/libs/actions/IOU/Split.ts @@ -57,6 +57,7 @@ import { } from '@libs/TransactionUtils'; import {buildOptimisticPolicyRecentlyUsedTags} from '@userActions/Policy/Tag'; import {notifyNewAction, setDeleteTransactionNavigateBackUrl} from '@userActions/Report'; +import {getStoredDefaultP2PMileageRate} from '@userActions/Transaction'; import {removeDraftSplitTransaction, removeDraftTransaction} from '@userActions/TransactionEdit'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; @@ -1870,6 +1871,7 @@ function setDraftSplitTransaction( shouldUpdateReceiptState: false, policy, isSplitTransaction: true, + defaultP2PMileageRate: getStoredDefaultP2PMileageRate(), }) : null; @@ -2121,7 +2123,7 @@ function addSplitExpenseField( } : undefined; - const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate: getStoredDefaultP2PMileageRate()}); const {unit, rate} = mileageRate; if (rate && rate > 0 && customUnit) { @@ -2195,7 +2197,7 @@ function evenlyDistributeSplitExpenseAmounts(draftTransaction: OnyxEntry { @@ -2262,7 +2264,7 @@ function resetSplitExpensesByDateRange( const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); - const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate: getStoredDefaultP2PMileageRate()}); const {unit, rate} = mileageRate; // Create split expenses for each date with proportional amounts @@ -2379,7 +2381,11 @@ function updateSplitExpenseField( // Recalculate amount for distance transactions when rate or distance changes if (isDistanceRequest && originalTransaction) { - const mileageRate = DistanceRequestUtils.getRate({transaction: splitExpenseDraftTransaction, policy: policy ?? undefined}); + const mileageRate = DistanceRequestUtils.getRate({ + transaction: splitExpenseDraftTransaction, + policy: policy ?? undefined, + defaultP2PMileageRate: getStoredDefaultP2PMileageRate(), + }); const {unit, rate} = mileageRate; if (rate && rate > 0) { @@ -2446,7 +2452,7 @@ function updateSplitExpenseAmountField(draftTransaction: OnyxEntry, policy?: OnyxEntr const splitMerchants: Array = [undefined, undefined]; if (isDistanceRequest(transaction)) { - const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate: getStoredDefaultP2PMileageRate()}); const {unit, rate} = mileageRate; if (rate && rate > 0 && transaction?.comment?.customUnit) { diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index afb317f51223..508fc8fb0999 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -53,6 +53,7 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type { + DefaultP2PMileageRate, PersonalDetails, Policy, PolicyCategories, @@ -102,6 +103,18 @@ Onyx.connect({ callback: (val) => (allTransactionViolations = val ?? []), }); +let storedDefaultP2PMileageRate: DefaultP2PMileageRate | undefined; +Onyx.connect({ + key: ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE, + callback: (value) => { + storedDefaultP2PMileageRate = value ?? undefined; + }, +}); + +function getStoredDefaultP2PMileageRate(): DefaultP2PMileageRate | undefined { + return storedDefaultP2PMileageRate; +} + /** * @deprecated This function uses Onyx.connect and should be replaced with useOnyx for reactive data access. * TODO: remove `getPolicyTagsData` from this file (https://github.com/Expensify/App/issues/72720) @@ -831,11 +844,6 @@ function openDraftDistanceExpense() { API.read(READ_COMMANDS.OPEN_DRAFT_DISTANCE_EXPENSE, null, onyxData); } -/** - * Fetches the default P2P mileage rate from Auth. - * Auth determines the rate from the user's personal policy currency. - * The rate is stored in Onyx via the onyxData returned by Auth. - */ function fetchDefaultP2PMileageRate() { API.read(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, {}, {}); } @@ -1055,7 +1063,7 @@ function changeTransactionsReport({ }; const {comment, modifiedAmount, modifiedCurrency, modifiedMerchant} = isUnreported - ? recalculateUnreportedTransactionDetails(transaction, destinationCurrency, translate, toLocaleDigit) + ? recalculateUnreportedTransactionDetails(transaction, destinationCurrency, translate, toLocaleDigit, storedDefaultP2PMileageRate) : {}; // 1. Optimistically change the reportID on the passed transactions @@ -1694,4 +1702,5 @@ export { setTransactionReport, mergeTransactionIdsHighlightOnSearchRoute, getDuplicateTransactionDetails, + getStoredDefaultP2PMileageRate, }; diff --git a/src/pages/iou/SplitExpenseEditPage.tsx b/src/pages/iou/SplitExpenseEditPage.tsx index 8262405dbd7e..cc372988751e 100644 --- a/src/pages/iou/SplitExpenseEditPage.tsx +++ b/src/pages/iou/SplitExpenseEditPage.tsx @@ -75,6 +75,7 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) { const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${currentReport?.policyID}`); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${currentReport?.policyID}`); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const {login, accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); const fetchData = useCallback(() => { @@ -129,7 +130,7 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) { const isDistance = isDistanceRequest(splitExpenseDraftTransaction); const isManualDistance = isManualDistanceRequest(splitExpenseDraftTransaction); const isOdometerDistance = isOdometerDistanceRequest(splitExpenseDraftTransaction); - const {unit, rate, name: rateName} = DistanceRequestUtils.getRate({transaction: splitExpenseDraftTransaction, policy: currentPolicy}); + const {unit, rate, name: rateName} = DistanceRequestUtils.getRate({transaction: splitExpenseDraftTransaction, policy: currentPolicy, defaultP2PMileageRate}); const distance = getDistanceInMeters(splitExpenseDraftTransaction, unit); const currentAmount = useMemo(() => { if (isDistance && distance && rate) { diff --git a/src/pages/iou/SplitExpensePage.tsx b/src/pages/iou/SplitExpensePage.tsx index bd1b728298f1..c0397f9611da 100644 --- a/src/pages/iou/SplitExpensePage.tsx +++ b/src/pages/iou/SplitExpensePage.tsx @@ -162,6 +162,7 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const icons = useMemoizedLazyExpensifyIcons(['ArrowsLeftRight', 'Plus'] as const); const {isBetaEnabled} = usePermissions(); @@ -181,7 +182,7 @@ function SplitExpensePage({route}: SplitExpensePageProps) { if (isSplitDistance) { const currentRateID = splitExpense?.customUnit?.customUnitRateID ?? String(CONST.DEFAULT_NUMBER_ID); const rates = DistanceRequestUtils.getMileageRates(currentPolicy, false, currentRateID); - const {rate} = DistanceRequestUtils.getRate({transaction: splitTransaction, policy: currentPolicy}); + const {rate} = DistanceRequestUtils.getRate({transaction: splitTransaction, policy: currentPolicy, defaultP2PMileageRate}); if (!rates[currentRateID] || !rate) { isUnitRateIDOutOfPolicy = true; } diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index c6d61391339a..f1a7822accd5 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -100,6 +100,7 @@ function IOURequestStepDistance({ const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); const [optimisticWaypoints, setOptimisticWaypoints] = useState(null); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES); @@ -196,7 +197,7 @@ function IOURequestStepDistance({ const mileageRates = DistanceRequestUtils.getMileageRates(IOUpolicy); const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(IOUpolicy); const mileageRate: MileageRate | undefined = isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction) + ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction, defaultP2PMileageRate) : // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (customUnitRateID && mileageRates?.[customUnitRateID]) || defaultMileageRate; @@ -211,7 +212,7 @@ function IOURequestStepDistance({ setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []); } }, - [report, allReports, transaction, transactionID, isSplitRequest, policy?.outputCurrency, reportID, customUnitRateID, personalPolicy?.outputCurrency], + [report, allReports, transaction, transactionID, isSplitRequest, policy?.outputCurrency, reportID, customUnitRateID, personalPolicy?.outputCurrency, defaultP2PMileageRate], ); // For quick button actions, we'll skip the confirmation page unless the report is archived or this is a workspace diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index aeae2af754c2..2623d03c7ef6 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -48,6 +48,7 @@ function IOURequestStepDistanceGPS({ const {isBetaEnabled} = usePermissions(); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`); const [gpsDraftDetails] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`); @@ -81,7 +82,7 @@ function IOURequestStepDistanceGPS({ const shouldUseDefaultExpensePolicy = shouldUseDefaultExpensePolicyUtil(iouType, defaultExpensePolicy, amountOwed, ownerBillingGraceEndPeriod); const customUnitRateID = getRateID(transaction); - const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy}).unit; + const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy, defaultP2PMileageRate}).unit; const shouldSkipConfirmation = !skipConfirmation || !report?.reportID ? false : !(isArchived || isPolicyExpenseChatUtils(report)); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx index cfe245d593e5..7097d656de0c 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx @@ -86,6 +86,7 @@ function IOURequestStepDistanceManual({ const {policyForMovingExpenses} = usePolicyForMovingExpenses(); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const reportAttributesDerived = useReportAttributes(); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); @@ -122,6 +123,7 @@ function IOURequestStepDistanceManual({ transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy, useTransactionDistanceUnit: false, + defaultP2PMileageRate, }); const unit = mileageRate.unit; const rate = mileageRate.rate ?? 0; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx index 752fdecd9f4d..14c2e4581e58 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx @@ -98,6 +98,7 @@ function IOURequestStepDistanceMap({ const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [optimisticWaypoints, setOptimisticWaypoints] = useState(null); @@ -193,7 +194,7 @@ function IOURequestStepDistanceMap({ const mileageRates = DistanceRequestUtils.getMileageRates(IOUpolicy); const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(IOUpolicy); const mileageRate: MileageRate | undefined = isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction) + ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction, defaultP2PMileageRate) : // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (customUnitRateID && mileageRates?.[customUnitRateID]) || defaultMileageRate; @@ -208,7 +209,7 @@ function IOURequestStepDistanceMap({ setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []); } }, - [report, allReports, transaction, transactionID, isSplitRequest, policy?.outputCurrency, reportID, customUnitRateID, personalPolicy?.outputCurrency], + [report, allReports, transaction, transactionID, isSplitRequest, policy?.outputCurrency, reportID, customUnitRateID, personalPolicy?.outputCurrency, defaultP2PMileageRate], ); // For quick button actions, we'll skip the confirmation page unless the report is archived or this is a workspace diff --git a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx index 3c753d0b72a0..617fea9690ae 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx @@ -97,6 +97,7 @@ function IOURequestStepDistanceOdometer({ const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`); const isArchived = isArchivedReport(reportNameValuePairs); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const reportAttributesDerived = useReportAttributes(); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); @@ -139,7 +140,11 @@ function IOURequestStepDistanceOdometer({ ); const customUnitRateID = getRateID(transaction); - const mileageRate = DistanceRequestUtils.getRate({transaction: currentTransaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy}); + const mileageRate = DistanceRequestUtils.getRate({ + transaction: currentTransaction, + policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy, + defaultP2PMileageRate, + }); const unit = mileageRate.unit; const rate = mileageRate.rate ?? 0; From c4b82e9c7b20d21932133ae989546007e01c3cfb Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Thu, 26 Mar 2026 17:51:45 +0000 Subject: [PATCH 09/51] Fix: break circular dependency causing test failures Move the storedDefaultP2PMileageRate Onyx subscription and getStoredDefaultP2PMileageRate getter into a new DefaultP2PMileageRateStore module to break the circular dependency chain: Report/index.ts -> Transaction.ts -> IOU/index.ts -> Report/index.ts. Report/index.ts now imports directly from the store, while Transaction.ts re-exports for backward compatibility. Co-authored-by: Neil Marcellini --- src/libs/actions/DefaultP2PMileageRateStore.ts | 17 +++++++++++++++++ src/libs/actions/Report/index.ts | 2 +- src/libs/actions/Transaction.ts | 15 ++------------- 3 files changed, 20 insertions(+), 14 deletions(-) create mode 100644 src/libs/actions/DefaultP2PMileageRateStore.ts diff --git a/src/libs/actions/DefaultP2PMileageRateStore.ts b/src/libs/actions/DefaultP2PMileageRateStore.ts new file mode 100644 index 000000000000..277184fc2e75 --- /dev/null +++ b/src/libs/actions/DefaultP2PMileageRateStore.ts @@ -0,0 +1,17 @@ +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; + +let storedDefaultP2PMileageRate: DefaultP2PMileageRate | undefined; +Onyx.connect({ + key: ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE, + callback: (value) => { + storedDefaultP2PMileageRate = value ?? undefined; + }, +}); + +function getStoredDefaultP2PMileageRate(): DefaultP2PMileageRate | undefined { + return storedDefaultP2PMileageRate; +} + +export {getStoredDefaultP2PMileageRate}; diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 59bd61191a11..a45cb1a4d782 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -179,7 +179,7 @@ import { resolveOpenReportDuplicationConflictAction, } from '@userActions/RequestConflictUtils'; import {isAnonymousUser} from '@userActions/Session'; -import {getStoredDefaultP2PMileageRate} from '@userActions/Transaction'; +import {getStoredDefaultP2PMileageRate} from '@userActions/DefaultP2PMileageRateStore'; import {onServerDataReady} from '@userActions/Welcome'; import {getOnboardingMessages} from '@userActions/Welcome/OnboardingFlow'; import type {OnboardingCompanySize, OnboardingMessage} from '@userActions/Welcome/OnboardingFlow'; diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 508fc8fb0999..10bd8d272f53 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -53,7 +53,6 @@ import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type { - DefaultP2PMileageRate, PersonalDetails, Policy, PolicyCategories, @@ -71,6 +70,7 @@ import type {OnyxData} from '@src/types/onyx/Request'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import type {Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import type TransactionState from '@src/types/utils/TransactionStateType'; +import {getStoredDefaultP2PMileageRate} from './DefaultP2PMileageRateStore'; import {getPolicyTags} from './IOU/index'; let allReports: OnyxCollection = {}; @@ -103,17 +103,6 @@ Onyx.connect({ callback: (val) => (allTransactionViolations = val ?? []), }); -let storedDefaultP2PMileageRate: DefaultP2PMileageRate | undefined; -Onyx.connect({ - key: ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE, - callback: (value) => { - storedDefaultP2PMileageRate = value ?? undefined; - }, -}); - -function getStoredDefaultP2PMileageRate(): DefaultP2PMileageRate | undefined { - return storedDefaultP2PMileageRate; -} /** * @deprecated This function uses Onyx.connect and should be replaced with useOnyx for reactive data access. @@ -1063,7 +1052,7 @@ function changeTransactionsReport({ }; const {comment, modifiedAmount, modifiedCurrency, modifiedMerchant} = isUnreported - ? recalculateUnreportedTransactionDetails(transaction, destinationCurrency, translate, toLocaleDigit, storedDefaultP2PMileageRate) + ? recalculateUnreportedTransactionDetails(transaction, destinationCurrency, translate, toLocaleDigit, getStoredDefaultP2PMileageRate()) : {}; // 1. Optimistically change the reportID on the passed transactions From 74048ddca287e55aeb1e24a2dd1f11602353f9d5 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 27 Mar 2026 13:14:42 -0700 Subject: [PATCH 10/51] Revert unintentional Mobile-Expensify submodule change and remove extra blank line - Reset Mobile-Expensify submodule pointer back to main - Remove extra blank line in Transaction.ts Made-with: Cursor --- src/libs/actions/Transaction.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 10bd8d272f53..222d440e4440 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -103,7 +103,6 @@ Onyx.connect({ callback: (val) => (allTransactionViolations = val ?? []), }); - /** * @deprecated This function uses Onyx.connect and should be replaced with useOnyx for reactive data access. * TODO: remove `getPolicyTagsData` from this file (https://github.com/Expensify/App/issues/72720) From 816b22e1f03928304215849f680b468b3d23ce2c Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 27 Mar 2026 13:54:21 -0700 Subject: [PATCH 11/51] Rename fetchDefaultP2PMileageRate to getDefaultP2PMileageRate Made-with: Cursor --- src/libs/actions/IOU/index.ts | 4 ++-- src/libs/actions/Transaction.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index cf0e3c68b5ca..4b9e7f123653 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -237,7 +237,7 @@ import {buildOptimisticPolicyRecentlyUsedTags} from '@userActions/Policy/Tag'; import type {GuidedSetupData} from '@userActions/Report'; import {buildInviteToRoomOnyxData, completeOnboarding, notifyNewAction, optimisticReportLastData} from '@userActions/Report'; import { - fetchDefaultP2PMileageRate, + getDefaultP2PMileageRate, getStoredDefaultP2PMileageRate, mergeTransactionIdsHighlightOnSearchRoute, sanitizeWaypointsForAPI, @@ -1351,7 +1351,7 @@ function initMoneyRequest({ comment.odometerEndImage = undefined; } - fetchDefaultP2PMileageRate(); + getDefaultP2PMileageRate(); } if (newIouRequestType === CONST.IOU.REQUEST_TYPE.PER_DIEM) { diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 222d440e4440..a58582188bac 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -832,7 +832,7 @@ function openDraftDistanceExpense() { API.read(READ_COMMANDS.OPEN_DRAFT_DISTANCE_EXPENSE, null, onyxData); } -function fetchDefaultP2PMileageRate() { +function getDefaultP2PMileageRate() { API.read(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, {}, {}); } @@ -1681,7 +1681,7 @@ export { setReviewDuplicatesKey, abandonReviewDuplicateTransactions, openDraftDistanceExpense, - fetchDefaultP2PMileageRate, + getDefaultP2PMileageRate, sanitizeWaypointsForAPI, stringifyWaypointsForAPI, getLastModifiedExpense, From 2d0ba4071b86df2c3357cc517d08534098eb703c Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 27 Mar 2026 13:59:36 -0700 Subject: [PATCH 12/51] Update Mobile-Expensify submodule to match main Resets the submodule pointer to match origin/main, removing the unintentional drift that showed up in the PR diff. Made-with: Cursor --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index d0bcd8ba5700..04b7b1038e36 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit d0bcd8ba5700a5044c7d7a83d5eaff417743e899 +Subproject commit 04b7b1038e3642e7c697c54d16a1608ed32c2d37 From c6f4595e5a7e1b581c6b5c6fd93fb35db00d1ae3 Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Fri, 27 Mar 2026 21:02:12 +0000 Subject: [PATCH 13/51] Fix: sort import order for DefaultP2PMileageRateStore Co-authored-by: Neil Marcellini --- src/libs/actions/Report/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index a886aacfee0d..f07edcb32428 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -165,6 +165,7 @@ import addTrailingForwardSlash from '@libs/UrlUtils'; import Visibility from '@libs/Visibility'; import {cacheAttachment, removeCachedAttachment} from '@userActions/Attachment'; import {clearByKey} from '@userActions/CachedPDFPaths'; +import {getStoredDefaultP2PMileageRate} from '@userActions/DefaultP2PMileageRateStore'; import {setDownload} from '@userActions/Download'; import {close} from '@userActions/Modal'; import navigateFromNotification from '@userActions/navigateFromNotification'; @@ -179,7 +180,6 @@ import { resolveOpenReportDuplicationConflictAction, } from '@userActions/RequestConflictUtils'; import {isAnonymousUser} from '@userActions/Session'; -import {getStoredDefaultP2PMileageRate} from '@userActions/DefaultP2PMileageRateStore'; import {onServerDataReady} from '@userActions/Welcome'; import {getOnboardingMessages} from '@userActions/Welcome/OnboardingFlow'; import type {OnboardingCompanySize, OnboardingMessage} from '@userActions/Welcome/OnboardingFlow'; From 017e001c6cc4f71571ef82bb64546f643e440584 Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Fri, 27 Mar 2026 21:07:43 +0000 Subject: [PATCH 14/51] Fix: use default export for DefaultP2PMileageRateStore single export Co-authored-by: Neil Marcellini --- src/libs/actions/DefaultP2PMileageRateStore.ts | 2 +- src/libs/actions/Report/index.ts | 2 +- src/libs/actions/Transaction.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/actions/DefaultP2PMileageRateStore.ts b/src/libs/actions/DefaultP2PMileageRateStore.ts index 277184fc2e75..76f7a273b41a 100644 --- a/src/libs/actions/DefaultP2PMileageRateStore.ts +++ b/src/libs/actions/DefaultP2PMileageRateStore.ts @@ -14,4 +14,4 @@ function getStoredDefaultP2PMileageRate(): DefaultP2PMileageRate | undefined { return storedDefaultP2PMileageRate; } -export {getStoredDefaultP2PMileageRate}; +export default getStoredDefaultP2PMileageRate; diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index f07edcb32428..afe82c2ca453 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -165,7 +165,7 @@ import addTrailingForwardSlash from '@libs/UrlUtils'; import Visibility from '@libs/Visibility'; import {cacheAttachment, removeCachedAttachment} from '@userActions/Attachment'; import {clearByKey} from '@userActions/CachedPDFPaths'; -import {getStoredDefaultP2PMileageRate} from '@userActions/DefaultP2PMileageRateStore'; +import getStoredDefaultP2PMileageRate from '@userActions/DefaultP2PMileageRateStore'; import {setDownload} from '@userActions/Download'; import {close} from '@userActions/Modal'; import navigateFromNotification from '@userActions/navigateFromNotification'; diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index feef02a05eb8..c88ad73b1086 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -72,7 +72,7 @@ import type {OnyxData} from '@src/types/onyx/Request'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import type {Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import type TransactionState from '@src/types/utils/TransactionStateType'; -import {getStoredDefaultP2PMileageRate} from './DefaultP2PMileageRateStore'; +import getStoredDefaultP2PMileageRate from './DefaultP2PMileageRateStore'; import {getPolicyTags} from './IOU/index'; let allReports: OnyxCollection = {}; From 01db696846c851e9abeae39dbd220d4435d982af Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 27 Mar 2026 14:30:22 -0700 Subject: [PATCH 15/51] Remove DefaultP2PMileageRateStore, thread param from useOnyx Delete the separate Onyx store module and instead pass defaultP2PMileageRate as an explicit parameter from components that read it via useOnyx, following the data-binding philosophy that action methods should be pure functions with all data passed as parameters. Affected action files: IOU/index.ts, IOU/Split.ts, Transaction.ts, Report/index.ts, SplitExpenses.ts, IOU/MoneyRequest.ts, Search.ts. Made-with: Cursor --- Mobile-Expensify | 2 +- src/components/MoneyReportHeader.tsx | 4 +- .../MoneyRequestHeaderSecondaryActions.tsx | 3 +- .../ReportActionItem/MoneyRequestView.tsx | 2 +- src/hooks/useSearchBulkActions.ts | 7 +- src/hooks/useSelectedTransactionsActions.ts | 3 +- src/hooks/useUndeleteTransactions.ts | 2 + .../actions/DefaultP2PMileageRateStore.ts | 17 ---- src/libs/actions/IOU/MoneyRequest.ts | 9 ++- src/libs/actions/IOU/Split.ts | 33 +++++--- src/libs/actions/IOU/index.ts | 77 +++++++++++++++---- src/libs/actions/Report/index.ts | 6 +- src/libs/actions/Search.ts | 4 + src/libs/actions/SplitExpenses.ts | 6 +- src/libs/actions/Transaction.ts | 7 +- .../Search/SearchTransactionsChangeReport.tsx | 4 + .../PopoverReportActionContextMenu.tsx | 3 + .../iou/SplitExpenseCreateDateRagePage.tsx | 3 +- src/pages/iou/SplitExpenseEditPage.tsx | 2 +- src/pages/iou/SplitExpensePage.tsx | 8 +- .../iou/request/step/IOURequestEditReport.tsx | 3 + .../request/step/IOURequestStepDistance.tsx | 4 + .../index.native.tsx | 1 + .../step/IOURequestStepDistanceManual.tsx | 4 +- .../step/IOURequestStepDistanceMap.tsx | 5 +- .../step/IOURequestStepDistanceOdometer.tsx | 2 + .../iou/request/step/IOURequestStepReport.tsx | 3 + 27 files changed, 156 insertions(+), 68 deletions(-) delete mode 100644 src/libs/actions/DefaultP2PMileageRateStore.ts diff --git a/Mobile-Expensify b/Mobile-Expensify index 04b7b1038e36..d0bcd8ba5700 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 04b7b1038e3642e7c697c54d16a1608ed32c2d37 +Subproject commit d0bcd8ba5700a5044c7d7a83d5eaff417743e899 diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 639447f65af1..977402390339 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -260,6 +260,7 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS); const [selfDMReportID] = useOnyx(ONYXKEYS.SELF_DM_REPORT_ID); const [selfDMReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReportID}`); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const personalPolicy = usePersonalPolicy(); const expensifyIcons = useMemoizedLazyExpensifyIcons([ @@ -1867,7 +1868,7 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa return; } - initSplitExpense(currentTransaction, policy); + initSplitExpense(currentTransaction, policy, defaultP2PMileageRate); }, }, [CONST.REPORT.SECONDARY_ACTIONS.MERGE]: { @@ -2090,6 +2091,7 @@ function MoneyReportHeader({reportID: reportIDProp, shouldDisplayBackButton = fa translate, toLocaleDigit, hash: currentSearchHash, + defaultP2PMileageRate, }); }); }); diff --git a/src/components/MoneyRequestHeaderSecondaryActions.tsx b/src/components/MoneyRequestHeaderSecondaryActions.tsx index 00ce74c84306..61b07b9b3107 100644 --- a/src/components/MoneyRequestHeaderSecondaryActions.tsx +++ b/src/components/MoneyRequestHeaderSecondaryActions.tsx @@ -135,6 +135,7 @@ function MoneyRequestHeaderSecondaryActions({reportID, onBackButtonPress}: Money const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); const [isSelfTourViewed = false] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); const [betas] = useOnyx(ONYXKEYS.BETAS); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); // Custom hooks const defaultExpensePolicy = useDefaultExpensePolicy(); @@ -322,7 +323,7 @@ function MoneyRequestHeaderSecondaryActions({reportID, onBackButtonPress}: Money icon: expensifyIcons.ArrowSplit, value: CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.SPLIT, onSelected: () => { - initSplitExpense(transaction, policy); + initSplitExpense(transaction, policy, defaultP2PMileageRate); }, }, [CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.MERGE]: { diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 829bd7ac542e..7650685f89f8 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -882,7 +882,7 @@ function MoneyRequestView({ } if (shouldShowSplitIndicator && isSplitAvailable) { - initSplitExpense(transaction, policy); + initSplitExpense(transaction, policy, defaultP2PMileageRate); return; } diff --git a/src/hooks/useSearchBulkActions.ts b/src/hooks/useSearchBulkActions.ts index 38fa9f5119d8..f90b20704265 100644 --- a/src/hooks/useSearchBulkActions.ts +++ b/src/hooks/useSearchBulkActions.ts @@ -102,6 +102,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS); const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const personalPolicy = usePersonalPolicy(); const [userBillingGraceEndPeriods] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); const undeleteTransactions = useUndeleteTransactions(); @@ -460,6 +461,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { translate, toLocaleDigit, hash, + defaultP2PMileageRate, }); } } else { @@ -480,6 +482,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { toLocaleDigit, transactions, allReportNameValuePairs, + defaultP2PMileageRate, }); } clearSelectedTransactions(); @@ -507,6 +510,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { isExpenseReportType, selectedReportIDs, allReportNameValuePairs, + defaultP2PMileageRate, ]); const onBulkPaySelected = useCallback( @@ -1118,7 +1122,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { icon: expensifyIcons.ArrowSplit, value: CONST.SEARCH.BULK_ACTION_TYPES.SPLIT, onSelected: () => { - initSplitExpense(firstTransaction, firstTransactionPolicy); + initSplitExpense(firstTransaction, firstTransactionPolicy, defaultP2PMileageRate); }, }); } @@ -1223,6 +1227,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { styles.textWrap, userBillingGraceEndPeriods, currentSearchKey, + defaultP2PMileageRate, ]); const handleOfflineModalClose = useCallback(() => { diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index a3acb09805fa..0897534011fb 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -83,6 +83,7 @@ function useSelectedTransactionsActions({ const [integrationsExportTemplates] = useOnyx(ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES); const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS); const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const expensifyIcons = useMemoizedLazyExpensifyIcons(['Stopwatch', 'Trashcan', 'ArrowRight', 'Table', 'DocumentMerge', 'Export', 'ArrowCollapse', 'ArrowSplit', 'ThumbsDown']); const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(selectedTransactionIDs); @@ -382,7 +383,7 @@ function useSelectedTransactionsActions({ icon: expensifyIcons.ArrowSplit, value: SPLIT, onSelected: () => { - initSplitExpense(firstTransaction, policy); + initSplitExpense(firstTransaction, policy, defaultP2PMileageRate); }, }); } diff --git a/src/hooks/useUndeleteTransactions.ts b/src/hooks/useUndeleteTransactions.ts index bc4e8452920e..9e2f464a5e46 100644 --- a/src/hooks/useUndeleteTransactions.ts +++ b/src/hooks/useUndeleteTransactions.ts @@ -15,6 +15,7 @@ function useUndeleteTransactions() { const {translate, toLocaleDigit} = useLocalize(); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${personalPolicyID}`); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); return (transactionIDs: string[]) => { changeTransactionsReport({ @@ -26,6 +27,7 @@ function useUndeleteTransactions() { allTransactions, translate, toLocaleDigit, + defaultP2PMileageRate, }); }; } diff --git a/src/libs/actions/DefaultP2PMileageRateStore.ts b/src/libs/actions/DefaultP2PMileageRateStore.ts deleted file mode 100644 index 76f7a273b41a..000000000000 --- a/src/libs/actions/DefaultP2PMileageRateStore.ts +++ /dev/null @@ -1,17 +0,0 @@ -import Onyx from 'react-native-onyx'; -import ONYXKEYS from '@src/ONYXKEYS'; -import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; - -let storedDefaultP2PMileageRate: DefaultP2PMileageRate | undefined; -Onyx.connect({ - key: ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE, - callback: (value) => { - storedDefaultP2PMileageRate = value ?? undefined; - }, -}); - -function getStoredDefaultP2PMileageRate(): DefaultP2PMileageRate | undefined { - return storedDefaultP2PMileageRate; -} - -export default getStoredDefaultP2PMileageRate; diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 8cf8fd5b03b3..cf3bdcd76244 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -14,7 +14,7 @@ import shouldUseDefaultExpensePolicy from '@libs/shouldUseDefaultExpensePolicy'; import {cancelSpan} from '@libs/telemetry/activeSpans'; import {getValidWaypoints} from '@libs/TransactionUtils'; import type {ReceiptFile} from '@pages/iou/request/step/IOURequestStepScan/types'; -import {getStoredDefaultP2PMileageRate, setTransactionReport} from '@userActions/Transaction'; +import {setTransactionReport} from '@userActions/Transaction'; import type {IOUType} from '@src/CONST'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; @@ -34,6 +34,7 @@ import type { Transaction, TransactionViolation, } from '@src/types/onyx'; +import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {ReportAttributes, ReportAttributesDerivedValue} from '@src/types/onyx/DerivedValues'; import type {Participant} from '@src/types/onyx/IOU'; import type {Unit} from '@src/types/onyx/Policy'; @@ -175,6 +176,7 @@ type MoneyRequestStepDistanceNavigationParams = { isSelfTourViewed: boolean; amountOwed: OnyxEntry; ownerBillingGraceEndPeriod?: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }; function createTransaction({ @@ -594,6 +596,7 @@ function handleMoneyRequestStepDistanceNavigation({ isSelfTourViewed, amountOwed, ownerBillingGraceEndPeriod, + defaultP2PMileageRate, }: MoneyRequestStepDistanceNavigationParams) { const isManualDistance = manualDistance !== undefined; const isOdometerDistance = odometerDistance !== undefined; @@ -643,7 +646,7 @@ function handleMoneyRequestStepDistanceNavigation({ let merchant = translate('iou.fieldPending'); if (isManualDistance && distance !== undefined && unit) { const distanceInMeters = DistanceRequestUtils.convertToDistanceInMeters(distance, unit); - const mileageRate = DistanceRequestUtils.getRate({transaction, policy, defaultP2PMileageRate: getStoredDefaultP2PMileageRate()}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy, defaultP2PMileageRate}); amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, mileageRate?.rate ?? 0); merchant = DistanceRequestUtils.getDistanceMerchant( true, @@ -767,7 +770,7 @@ function handleMoneyRequestStepDistanceNavigation({ // and because of this this report is converted to selfDM here if (isSelfDMReport && distance !== undefined && unit) { const personalCurrency = personalOutputCurrency ?? CONST.CURRENCY.USD; - const personalDistanceUnit = DistanceRequestUtils.getRateForP2P(personalCurrency, transaction, getStoredDefaultP2PMileageRate()).unit; + const personalDistanceUnit = DistanceRequestUtils.getRateForP2P(personalCurrency, transaction, defaultP2PMileageRate).unit; const distanceInMeters = DistanceRequestUtils.convertToDistanceInMeters(distance, unit); const distanceUsingPersonalDistanceUnit = roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, personalDistanceUnit)); setMoneyRequestDistance(transactionID, distanceUsingPersonalDistanceUnit, true, personalDistanceUnit); diff --git a/src/libs/actions/IOU/Split.ts b/src/libs/actions/IOU/Split.ts index c4cdc60ee6f8..37128853f985 100644 --- a/src/libs/actions/IOU/Split.ts +++ b/src/libs/actions/IOU/Split.ts @@ -57,7 +57,6 @@ import { } from '@libs/TransactionUtils'; import {buildOptimisticPolicyRecentlyUsedTags} from '@userActions/Policy/Tag'; import {notifyNewAction, setDeleteTransactionNavigateBackUrl} from '@userActions/Report'; -import {getStoredDefaultP2PMileageRate} from '@userActions/Transaction'; import {removeDraftSplitTransaction, removeDraftTransaction} from '@userActions/TransactionEdit'; import CONST from '@src/CONST'; import IntlStore from '@src/languages/IntlStore'; @@ -67,6 +66,7 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; +import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Attendee, Participant, Split, SplitExpense} from '@src/types/onyx/IOU'; import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails'; import type {Unit} from '@src/types/onyx/Policy'; @@ -1853,6 +1853,7 @@ function setDraftSplitTransaction( splitTransactionDraft: OnyxEntry, transactionChanges: TransactionChanges = {}, policy?: OnyxEntry, + defaultP2PMileageRate?: DefaultP2PMileageRate, ) { if (!transactionID) { return undefined; @@ -1871,7 +1872,7 @@ function setDraftSplitTransaction( shouldUpdateReceiptState: false, policy, isSplitTransaction: true, - defaultP2PMileageRate: getStoredDefaultP2PMileageRate(), + defaultP2PMileageRate, }) : null; @@ -2103,6 +2104,7 @@ function addSplitExpenseField( draftTransaction: OnyxEntry, transactionReport: OnyxEntry, policy?: OnyxEntry, + defaultP2PMileageRate?: DefaultP2PMileageRate, ) { if (!transaction || !draftTransaction) { return; @@ -2123,7 +2125,7 @@ function addSplitExpenseField( } : undefined; - const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate: getStoredDefaultP2PMileageRate()}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate}); const {unit, rate} = mileageRate; if (rate && rate > 0 && customUnit) { @@ -2174,7 +2176,12 @@ function addSplitExpenseField( * - Works entirely on the provided `draftTransaction` to avoid direct Onyx reads. * - Uses `calculateAmount` utility to handle currency subunits and rounding consistently with existing logic. */ -function evenlyDistributeSplitExpenseAmounts(draftTransaction: OnyxEntry, transaction?: OnyxEntry, policy?: OnyxEntry) { +function evenlyDistributeSplitExpenseAmounts( + draftTransaction: OnyxEntry, + transaction?: OnyxEntry, + policy?: OnyxEntry, + defaultP2PMileageRate?: DefaultP2PMileageRate, +) { if (!draftTransaction) { return; } @@ -2197,7 +2204,7 @@ function evenlyDistributeSplitExpenseAmounts(draftTransaction: OnyxEntry { @@ -2247,6 +2254,7 @@ function resetSplitExpensesByDateRange( startDate: string, endDate: string, policy?: OnyxEntry, + defaultP2PMileageRate?: DefaultP2PMileageRate, ) { if (!transaction || !startDate || !endDate) { return; @@ -2264,7 +2272,7 @@ function resetSplitExpensesByDateRange( const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); - const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate: getStoredDefaultP2PMileageRate()}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate}); const {unit, rate} = mileageRate; // Create split expenses for each date with proportional amounts @@ -2339,6 +2347,7 @@ function updateSplitExpenseField( splitExpenseTransactionID: string, originalTransaction: OnyxEntry, policy?: OnyxEntry, + defaultP2PMileageRate?: DefaultP2PMileageRate, ) { if (!splitExpenseDraftTransaction || !splitExpenseTransactionID || !originalTransactionDraft) { return; @@ -2384,7 +2393,7 @@ function updateSplitExpenseField( const mileageRate = DistanceRequestUtils.getRate({ transaction: splitExpenseDraftTransaction, policy: policy ?? undefined, - defaultP2PMileageRate: getStoredDefaultP2PMileageRate(), + defaultP2PMileageRate, }); const {unit, rate} = mileageRate; @@ -2429,7 +2438,13 @@ function updateSplitExpenseField( }); } -function updateSplitExpenseAmountField(draftTransaction: OnyxEntry, currentItemTransactionID: string, amount: number, policy?: OnyxEntry) { +function updateSplitExpenseAmountField( + draftTransaction: OnyxEntry, + currentItemTransactionID: string, + amount: number, + policy?: OnyxEntry, + defaultP2PMileageRate?: DefaultP2PMileageRate, +) { if (!draftTransaction?.transactionID || !currentItemTransactionID || Number.isNaN(amount)) { return; } @@ -2452,7 +2467,7 @@ function updateSplitExpenseAmountField(draftTransaction: OnyxEntry, policy: OnyxEntry) { +function setCustomUnitRateID( + transactionID: string, + customUnitRateID: string | undefined, + transaction: OnyxEntry, + policy: OnyxEntry, + defaultP2PMileageRate?: DefaultP2PMileageRate, +) { const isFakeP2PRate = customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID; let newDistanceUnit: Unit | undefined; @@ -1609,7 +1610,7 @@ function setCustomUnitRateID(transactionID: string, customUnitRateID: string | u if (customUnitRateID && transaction) { const distanceRate = isFakeP2PRate - ? DistanceRequestUtils.getRate({transaction, useTransactionDistanceUnit: false, policy, defaultP2PMileageRate: getStoredDefaultP2PMileageRate()}) + ? DistanceRequestUtils.getRate({transaction, useTransactionDistanceUnit: false, policy, defaultP2PMileageRate}) : DistanceRequestUtils.getRateByCustomUnitRateID({policy, customUnitRateID}); const transactionDistanceUnit = transaction.comment?.customUnit?.distanceUnit; @@ -4065,6 +4066,7 @@ type GetUpdateMoneyRequestParamsType = { policyRecentlyUsedCurrencies?: string[]; iouReportNextStep: OnyxEntry; isSplitTransaction?: boolean; + defaultP2PMileageRate?: DefaultP2PMileageRate; }; type UpdateMoneyRequestDataKeys = @@ -4102,6 +4104,7 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U policyRecentlyUsedCurrencies, iouReportNextStep, isSplitTransaction, + defaultP2PMileageRate, } = params; const optimisticData: Array< OnyxUpdate< @@ -4148,7 +4151,7 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U isFromExpenseReport, isSplitTransaction, policy, - defaultP2PMileageRate: getStoredDefaultP2PMileageRate(), + defaultP2PMileageRate, }) : undefined; @@ -4683,6 +4686,7 @@ function getUpdateTrackExpenseParams( transactionChanges: TransactionChanges, policy: OnyxEntry, shouldBuildOptimisticModifiedExpenseReportAction = true, + defaultP2PMileageRate?: DefaultP2PMileageRate, ): UpdateMoneyRequestData< typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.COLLECTION.TRANSACTION | typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT > { @@ -4705,7 +4709,7 @@ function getUpdateTrackExpenseParams( transactionChanges, isFromExpenseReport: false, policy, - defaultP2PMileageRate: getStoredDefaultP2PMileageRate(), + defaultP2PMileageRate, }) : null; const transactionDetails = getTransactionDetails(updatedTransaction); @@ -4866,6 +4870,7 @@ type UpdateMoneyRequestDateParams = { currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }; /** Updates the created date of an expense */ @@ -4883,6 +4888,7 @@ function updateMoneyRequestDate({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, + defaultP2PMileageRate, }: UpdateMoneyRequestDateParams) { const transactionChanges: TransactionChanges = { created: value, @@ -4890,7 +4896,7 @@ function updateMoneyRequestDate({ 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(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -4904,6 +4910,7 @@ function updateMoneyRequestDate({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); removeTransactionFromDuplicateTransactionViolation(data.onyxData, transactionID, transactions, transactionViolations); } @@ -4924,6 +4931,7 @@ function updateMoneyRequestBillable({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, + defaultP2PMileageRate, }: { transactionID: string | undefined; transactionThreadReport: OnyxEntry; @@ -4936,6 +4944,7 @@ function updateMoneyRequestBillable({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }) { if (!transactionID || !transactionThreadReport?.reportID) { return; @@ -4955,6 +4964,7 @@ function updateMoneyRequestBillable({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_BILLABLE, params, onyxData); } @@ -4971,6 +4981,7 @@ function updateMoneyRequestReimbursable({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, + defaultP2PMileageRate, }: { transactionID: string | undefined; transactionThreadReport: OnyxEntry; @@ -4983,6 +4994,7 @@ function updateMoneyRequestReimbursable({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }) { if (!transactionID || !transactionThreadReport?.reportID) { return; @@ -5002,6 +5014,7 @@ function updateMoneyRequestReimbursable({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_REIMBURSABLE, params, onyxData); } @@ -5019,6 +5032,7 @@ function updateMoneyRequestMerchant({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, + defaultP2PMileageRate, }: { transactionID: string; transactionThreadReport: OnyxEntry; @@ -5031,6 +5045,7 @@ function updateMoneyRequestMerchant({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const transactionChanges: TransactionChanges = { merchant: value, @@ -5038,7 +5053,7 @@ function updateMoneyRequestMerchant({ 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(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -5052,6 +5067,7 @@ function updateMoneyRequestMerchant({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); } const {params, onyxData} = data; @@ -5072,6 +5088,7 @@ function updateMoneyRequestAttendees({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, + defaultP2PMileageRate, }: { transactionID: string; transactionThreadReport: OnyxEntry; @@ -5085,6 +5102,7 @@ function updateMoneyRequestAttendees({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const transactionChanges: TransactionChanges = { attendees, @@ -5102,6 +5120,7 @@ function updateMoneyRequestAttendees({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); const {params, onyxData} = data; API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_ATTENDEES, params, onyxData); @@ -5121,6 +5140,7 @@ type UpdateMoneyRequestTagParams = { isASAPSubmitBetaEnabled: boolean; hash?: number; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }; /** Updates the tag of an expense */ @@ -5138,6 +5158,7 @@ function updateMoneyRequestTag({ isASAPSubmitBetaEnabled, hash, parentReportNextStep, + defaultP2PMileageRate, }: UpdateMoneyRequestTagParams) { const transactionChanges: TransactionChanges = { tag, @@ -5156,6 +5177,7 @@ function updateMoneyRequestTag({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAG, params, onyxData); } @@ -5173,6 +5195,7 @@ function updateMoneyRequestTaxAmount({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, + defaultP2PMileageRate, }: { transactionID: string; transactionThreadReport: OnyxEntry; @@ -5185,6 +5208,7 @@ function updateMoneyRequestTaxAmount({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const transactionChanges = { taxAmount, @@ -5201,6 +5225,7 @@ function updateMoneyRequestTaxAmount({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAX_AMOUNT, params, onyxData); } @@ -5219,6 +5244,7 @@ type UpdateMoneyRequestTaxRateParams = { currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }; /** Updates the created tax rate of an expense */ @@ -5236,6 +5262,7 @@ function updateMoneyRequestTaxRate({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, + defaultP2PMileageRate, }: UpdateMoneyRequestTaxRateParams) { const transactionChanges = { taxCode, @@ -5254,6 +5281,7 @@ function updateMoneyRequestTaxRate({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAX_RATE, params, onyxData); @@ -5277,6 +5305,7 @@ type UpdateMoneyRequestDistanceParams = { odometerStart?: number; odometerEnd?: number; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }; /** Updates the waypoints of a distance expense */ @@ -5298,6 +5327,7 @@ function updateMoneyRequestDistance({ odometerStart, odometerEnd, parentReportNextStep, + defaultP2PMileageRate, }: UpdateMoneyRequestDistanceParams) { const transactionChanges: TransactionChanges = { // Don't sanitize waypoints here - keep all fields for Onyx optimistic data (e.g., keyForList) @@ -5311,7 +5341,7 @@ function updateMoneyRequestDistance({ 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(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -5325,6 +5355,7 @@ function updateMoneyRequestDistance({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); } const {params, onyxData} = data; @@ -5396,6 +5427,7 @@ function updateMoneyRequestCategory({ isASAPSubmitBetaEnabled, hash, parentReportNextStep, + defaultP2PMileageRate, }: { transactionID: string; transactionThreadReport: OnyxEntry; @@ -5410,6 +5442,7 @@ function updateMoneyRequestCategory({ isASAPSubmitBetaEnabled: boolean; hash?: number; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const transactionChanges: TransactionChanges = { category, @@ -5429,6 +5462,7 @@ function updateMoneyRequestCategory({ isASAPSubmitBetaEnabled, hash, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_CATEGORY, params, onyxData); } @@ -5446,6 +5480,7 @@ function updateMoneyRequestDescription({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, + defaultP2PMileageRate, }: { transactionID: string; transactionThreadReport: OnyxEntry; @@ -5458,6 +5493,7 @@ function updateMoneyRequestDescription({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const parsedComment = getParsedComment(comment); const transactionChanges: TransactionChanges = { @@ -5466,7 +5502,7 @@ function updateMoneyRequestDescription({ 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(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -5480,6 +5516,7 @@ function updateMoneyRequestDescription({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); } const {params, onyxData} = data; @@ -5502,6 +5539,7 @@ function updateMoneyRequestDistanceRate({ updatedTaxAmount, updatedTaxCode, parentReportNextStep, + defaultP2PMileageRate, }: { transactionID: string; transactionThreadReport: OnyxEntry; @@ -5516,6 +5554,7 @@ function updateMoneyRequestDistanceRate({ updatedTaxAmount?: number; updatedTaxCode?: string; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const transactionChanges: TransactionChanges = { customUnitRateID: rateID, @@ -5537,7 +5576,7 @@ 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(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -5551,6 +5590,7 @@ function updateMoneyRequestDistanceRate({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); } const {params, onyxData} = data; @@ -7908,6 +7948,7 @@ type UpdateMoneyRequestAmountAndCurrencyParams = { isASAPSubmitBetaEnabled: boolean; policyRecentlyUsedCurrencies: string[]; parentReportNextStep: OnyxEntry; + defaultP2PMileageRate?: DefaultP2PMileageRate; }; /** Updates the amount and currency fields of an expense */ @@ -7931,6 +7972,7 @@ function updateMoneyRequestAmountAndCurrency({ isASAPSubmitBetaEnabled, policyRecentlyUsedCurrencies, parentReportNextStep, + defaultP2PMileageRate, }: UpdateMoneyRequestAmountAndCurrencyParams) { const transactionChanges = { amount, @@ -7943,7 +7985,7 @@ function updateMoneyRequestAmountAndCurrency({ 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(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -7959,6 +8001,7 @@ function updateMoneyRequestAmountAndCurrency({ isASAPSubmitBetaEnabled, policyRecentlyUsedCurrencies, iouReportNextStep: parentReportNextStep, + defaultP2PMileageRate, }); removeTransactionFromDuplicateTransactionViolation(data.onyxData, transactionID, transactions, transactionViolations); } diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index afe82c2ca453..fcb8e124ba87 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -165,7 +165,6 @@ import addTrailingForwardSlash from '@libs/UrlUtils'; import Visibility from '@libs/Visibility'; import {cacheAttachment, removeCachedAttachment} from '@userActions/Attachment'; import {clearByKey} from '@userActions/CachedPDFPaths'; -import getStoredDefaultP2PMileageRate from '@userActions/DefaultP2PMileageRateStore'; import {setDownload} from '@userActions/Download'; import {close} from '@userActions/Modal'; import navigateFromNotification from '@userActions/navigateFromNotification'; @@ -215,6 +214,7 @@ import type { TransactionViolations, VisibleReportActionsDerivedValue, } from '@src/types/onyx'; +import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Decision} from '@src/types/onyx/OriginalMessage'; import type {CurrentUserPersonalDetails, Timezone} from '@src/types/onyx/PersonalDetails'; import type {ConnectionName} from '@src/types/onyx/Policy'; @@ -5617,6 +5617,7 @@ type DeleteAppReportProps = { translate: LocaleContextProps['translate']; toLocaleDigit: LocaleContextProps['toLocaleDigit']; hash?: number; + defaultP2PMileageRate?: DefaultP2PMileageRate; }; /** Deletes a report and un-reports all transactions on the report along with its reportActions, any linked reports and any linked IOU report actions. */ @@ -5632,6 +5633,7 @@ function deleteAppReport({ translate, toLocaleDigit, hash, + defaultP2PMileageRate, }: DeleteAppReportProps) { if (!report?.reportID) { Log.warn('[Report] deleteAppReport called with no reportID'); @@ -5767,7 +5769,7 @@ function deleteAppReport({ personalPolicy?.outputCurrency, translate, toLocaleDigit, - getStoredDefaultP2PMileageRate(), + defaultP2PMileageRate, ); optimisticData.push( diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index 11b4e1b8a46c..7d75abe98d36 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -68,6 +68,7 @@ import type { Transaction, TransactionViolations, } from '@src/types/onyx'; +import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {PaymentInformation} from '@src/types/onyx/LastPaymentMethod'; import type {ConnectionName} from '@src/types/onyx/Policy'; import type {OnyxData} from '@src/types/onyx/Request'; @@ -125,6 +126,7 @@ type BulkDeleteReportsParams = { toLocaleDigit: LocaleContextProps['toLocaleDigit']; transactions?: OnyxCollection; allReportNameValuePairs: OnyxCollection; + defaultP2PMileageRate?: DefaultP2PMileageRate; }; function handleActionButtonPress({ @@ -877,6 +879,7 @@ function bulkDeleteReports({ toLocaleDigit, transactions, allReportNameValuePairs, + defaultP2PMileageRate, }: BulkDeleteReportsParams) { const transactionIDList: string[] = []; const reportIDList: string[] = []; @@ -952,6 +955,7 @@ function bulkDeleteReports({ personalPolicy, translate, toLocaleDigit, + defaultP2PMileageRate, }); } } diff --git a/src/libs/actions/SplitExpenses.ts b/src/libs/actions/SplitExpenses.ts index 405014aef64a..ffdf58814fb4 100644 --- a/src/libs/actions/SplitExpenses.ts +++ b/src/libs/actions/SplitExpenses.ts @@ -7,11 +7,11 @@ import Navigation from '@libs/Navigation/Navigation'; import {rand64} from '@libs/NumberUtils'; import {getTransactionDetails, isOpenReport} from '@libs/ReportUtils'; import {buildOptimisticTransaction, getChildTransactions, getOriginalTransactionWithSplitInfo, isDistanceRequest} from '@libs/TransactionUtils'; -import {getStoredDefaultP2PMileageRate} from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report, Transaction} from '@src/types/onyx'; +import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Attendee} from '@src/types/onyx/IOU'; import type {TransactionCustomUnit} from '@src/types/onyx/Transaction'; import {initSplitExpenseItemData, updateSplitExpenseDistanceFromAmount} from './IOU/Split'; @@ -39,7 +39,7 @@ Onyx.connectWithoutView({ /** * Create a draft transaction to set up split expense details for the split expense flow */ -function initSplitExpense(transaction: OnyxEntry, policy?: OnyxEntry): void { +function initSplitExpense(transaction: OnyxEntry, policy?: OnyxEntry, defaultP2PMileageRate?: DefaultP2PMileageRate): void { if (!transaction) { return; } @@ -95,7 +95,7 @@ function initSplitExpense(transaction: OnyxEntry, policy?: OnyxEntr const splitMerchants: Array = [undefined, undefined]; if (isDistanceRequest(transaction)) { - const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate: getStoredDefaultP2PMileageRate()}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate}); const {unit, rate} = mileageRate; if (rate && rate > 0 && transaction?.comment?.customUnit) { diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index c88ad73b1086..460301c54b53 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -67,12 +67,12 @@ import type { TransactionViolation, TransactionViolations, } from '@src/types/onyx'; +import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {OriginalMessageIOU, OriginalMessageModifiedExpense} from '@src/types/onyx/OriginalMessage'; import type {OnyxData} from '@src/types/onyx/Request'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; import type {Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import type TransactionState from '@src/types/utils/TransactionStateType'; -import getStoredDefaultP2PMileageRate from './DefaultP2PMileageRateStore'; import {getPolicyTags} from './IOU/index'; let allReports: OnyxCollection = {}; @@ -861,6 +861,7 @@ type ChangeTransactionsReportProps = { allTransactions: OnyxCollection; translate: LocaleContextProps['translate']; toLocaleDigit: LocaleContextProps['toLocaleDigit']; + defaultP2PMileageRate?: DefaultP2PMileageRate; }; function changeTransactionsReport({ @@ -875,6 +876,7 @@ function changeTransactionsReport({ allTransactions, translate, toLocaleDigit, + defaultP2PMileageRate, }: ChangeTransactionsReportProps) { const reportID = newReport?.reportID ?? CONST.REPORT.UNREPORTED_REPORT_ID; @@ -1061,7 +1063,7 @@ function changeTransactionsReport({ }; const {comment, modifiedAmount, modifiedCurrency, modifiedMerchant} = isUnreported - ? recalculateUnreportedTransactionDetails(transaction, destinationCurrency, translate, toLocaleDigit, getStoredDefaultP2PMileageRate()) + ? recalculateUnreportedTransactionDetails(transaction, destinationCurrency, translate, toLocaleDigit, defaultP2PMileageRate) : {}; // 1. Optimistically update the transaction with full data and changed fields. @@ -1700,5 +1702,4 @@ export { setTransactionReport, mergeTransactionIdsHighlightOnSearchRoute, getDuplicateTransactionDetails, - getStoredDefaultP2PMileageRate, }; diff --git a/src/pages/Search/SearchTransactionsChangeReport.tsx b/src/pages/Search/SearchTransactionsChangeReport.tsx index 2a3bfcd23e31..83b24d8e69d1 100644 --- a/src/pages/Search/SearchTransactionsChangeReport.tsx +++ b/src/pages/Search/SearchTransactionsChangeReport.tsx @@ -52,6 +52,7 @@ function SearchTransactionsChangeReport() { const [userBillingGraceEndPeriods] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); const [ownerBillingGraceEndPeriod] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [betas] = useOnyx(ONYXKEYS.BETAS); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const hasPerDiemTransactions = useHasPerDiemTransactions(selectedTransactionsKeys); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const {isBetaEnabled} = usePermissions(); @@ -119,6 +120,7 @@ function SearchTransactionsChangeReport() { allTransactions: transactions, translate, toLocaleDigit, + defaultP2PMileageRate, }); clearSelectedTransactions(); }); @@ -163,6 +165,7 @@ function SearchTransactionsChangeReport() { allTransactions: transactions, translate, toLocaleDigit, + defaultP2PMileageRate, }); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { @@ -185,6 +188,7 @@ function SearchTransactionsChangeReport() { allTransactions: transactions, translate, toLocaleDigit, + defaultP2PMileageRate, }); clearSelectedTransactions(); Navigation.goBack(); diff --git a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx index 349a77f4d4df..0d2c01ba2e15 100644 --- a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -332,6 +332,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro const [selfDMReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReportID}`); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const {currentSearchHash} = useSearchStateContext(); const {deleteTransactions} = useDeleteTransactions({ report, @@ -386,6 +387,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro translate, toLocaleDigit, hash: currentSearchHash, + defaultP2PMileageRate, }); } else if (reportAction) { // eslint-disable-next-line @typescript-eslint/no-deprecated @@ -427,6 +429,7 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro translate, toLocaleDigit, visibleReportActionsData, + defaultP2PMileageRate, ]); const hideDeleteModal = () => { diff --git a/src/pages/iou/SplitExpenseCreateDateRagePage.tsx b/src/pages/iou/SplitExpenseCreateDateRagePage.tsx index 101032e47b1a..bf419b48b129 100644 --- a/src/pages/iou/SplitExpenseCreateDateRagePage.tsx +++ b/src/pages/iou/SplitExpenseCreateDateRagePage.tsx @@ -51,9 +51,10 @@ function SplitExpenseCreateDateRagePage({route}: SplitExpenseCreateDateRagePageP ? policy : currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(currentReport?.policyID)}`]; const {login, accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const updateDate = (value: FormOnyxValues) => { - resetSplitExpensesByDateRange(transaction, currentReport, value[INPUT_IDS.START_DATE], value[INPUT_IDS.END_DATE], currentPolicy); + resetSplitExpensesByDateRange(transaction, currentReport, value[INPUT_IDS.START_DATE], value[INPUT_IDS.END_DATE], currentPolicy, defaultP2PMileageRate); Navigation.goBack(backTo); }; diff --git a/src/pages/iou/SplitExpenseEditPage.tsx b/src/pages/iou/SplitExpenseEditPage.tsx index cc372988751e..c3a84295c940 100644 --- a/src/pages/iou/SplitExpenseEditPage.tsx +++ b/src/pages/iou/SplitExpenseEditPage.tsx @@ -370,7 +370,7 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) { style={[styles.w100]} text={translate('common.save')} onPress={() => { - updateSplitExpenseField(splitExpenseDraftTransaction, originalTransactionDraft, splitExpenseTransactionID, transaction, currentPolicy); + updateSplitExpenseField(splitExpenseDraftTransaction, originalTransactionDraft, splitExpenseTransactionID, transaction, currentPolicy, defaultP2PMileageRate); Navigation.goBack(backTo); }} pressOnEnter diff --git a/src/pages/iou/SplitExpensePage.tsx b/src/pages/iou/SplitExpensePage.tsx index c0397f9611da..31e0113b8fef 100644 --- a/src/pages/iou/SplitExpensePage.tsx +++ b/src/pages/iou/SplitExpensePage.tsx @@ -206,14 +206,14 @@ function SplitExpensePage({route}: SplitExpensePageProps) { if (draftTransaction?.errors) { clearSplitTransactionDraftErrors(transactionID); } - addSplitExpenseField(transaction, draftTransaction, transactionReport, currentPolicy); + addSplitExpenseField(transaction, draftTransaction, transactionReport, currentPolicy, defaultP2PMileageRate); }; const onMakeSplitsEven = () => { if (!draftTransaction) { return; } - evenlyDistributeSplitExpenseAmounts(draftTransaction, transaction, currentPolicy); + evenlyDistributeSplitExpenseAmounts(draftTransaction, transaction, currentPolicy, defaultP2PMileageRate); }; const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(expenseReport?.policyID)}`); @@ -327,10 +327,10 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const onSplitExpenseValueChange = (id: string, value: number, mode: ValueOf) => { if (mode === CONST.TAB.SPLIT.AMOUNT || mode === CONST.TAB.SPLIT.DATE) { const amountInCents = convertToBackendAmount(value); - updateSplitExpenseAmountField(draftTransaction, id, amountInCents, currentPolicy); + updateSplitExpenseAmountField(draftTransaction, id, amountInCents, currentPolicy, defaultP2PMileageRate); } else { const amountInCents = calculateSplitAmountFromPercentage(transactionDetailsAmount, value); - updateSplitExpenseAmountField(draftTransaction, id, amountInCents, currentPolicy); + updateSplitExpenseAmountField(draftTransaction, id, amountInCents, currentPolicy, defaultP2PMileageRate); } }; diff --git a/src/pages/iou/request/step/IOURequestEditReport.tsx b/src/pages/iou/request/step/IOURequestEditReport.tsx index 55d8edb82e89..05196b1d1837 100644 --- a/src/pages/iou/request/step/IOURequestEditReport.tsx +++ b/src/pages/iou/request/step/IOURequestEditReport.tsx @@ -64,6 +64,7 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { const policyForMovingExpenses = policyForMovingExpensesID ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyForMovingExpensesID}`] : undefined; const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [betas] = useOnyx(ONYXKEYS.BETAS); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const selectReport = (item: TransactionGroupListItem, report?: OnyxEntry) => { if (transactionIDs.length === 0 || item.value === reportID) { Navigation.dismissToSuperWideRHP(); @@ -85,6 +86,7 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { allTransactions, translate, toLocaleDigit, + defaultP2PMileageRate, }); turnOffMobileSelectionMode(); clearSelectedTransactions(true); @@ -106,6 +108,7 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { allTransactions, translate, toLocaleDigit, + defaultP2PMileageRate, }); if (shouldTurnOffSelectionMode) { turnOffMobileSelectionMode(); diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index f1a7822accd5..ab185d577040 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -351,6 +351,7 @@ function IOURequestStepDistance({ isSelfTourViewed: !!isSelfTourViewed, amountOwed, ownerBillingGraceEndPeriod, + defaultP2PMileageRate, }); }, [ iouType, @@ -389,6 +390,7 @@ function IOURequestStepDistance({ isSelfTourViewed, amountOwed, ownerBillingGraceEndPeriod, + defaultP2PMileageRate, ]); const getError = () => { @@ -460,6 +462,7 @@ function IOURequestStepDistance({ originalSplitTransactionDraft, {waypoints: currentTransaction?.comment?.waypoints, routes: currentTransaction?.routes}, policy, + defaultP2PMileageRate, ); navigateBack(); return; @@ -527,6 +530,7 @@ function IOURequestStepDistance({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, + defaultP2PMileageRate, ]); const renderItem = useCallback( diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index 2623d03c7ef6..887927e60e7f 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -134,6 +134,7 @@ function IOURequestStepDistanceGPS({ isSelfTourViewed: !!isSelfTourViewed, amountOwed, ownerBillingGraceEndPeriod, + defaultP2PMileageRate, }); }; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx index 7097d656de0c..0f6dd21aaa6a 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx @@ -180,7 +180,7 @@ function IOURequestStepDistanceManual({ if (action === CONST.IOU.ACTION.EDIT) { // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplit && transaction) { - setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {distance: distanceAsFloat}, policy); + setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {distance: distanceAsFloat}, policy, defaultP2PMileageRate); Navigation.goBack(backTo); return; } @@ -252,6 +252,7 @@ function IOURequestStepDistanceManual({ isSelfTourViewed: !!isSelfTourViewed, amountOwed, ownerBillingGraceEndPeriod, + defaultP2PMileageRate, }); }, [ @@ -300,6 +301,7 @@ function IOURequestStepDistanceManual({ isSelfTourViewed, amountOwed, ownerBillingGraceEndPeriod, + defaultP2PMileageRate, ], ); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx index 14c2e4581e58..d404e40345f0 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx @@ -344,6 +344,7 @@ function IOURequestStepDistanceMap({ isSelfTourViewed: !!isSelfTourViewed, amountOwed, ownerBillingGraceEndPeriod, + defaultP2PMileageRate, }); }, [ iouType, @@ -382,6 +383,7 @@ function IOURequestStepDistanceMap({ isSelfTourViewed, amountOwed, ownerBillingGraceEndPeriod, + defaultP2PMileageRate, ]); const getError = () => { @@ -448,7 +450,7 @@ function IOURequestStepDistanceMap({ if (isEditing) { // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplit && transaction) { - setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {waypoints}, policy); + setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {waypoints}, policy, defaultP2PMileageRate); navigateBack(); return; } @@ -512,6 +514,7 @@ function IOURequestStepDistanceMap({ isASAPSubmitBetaEnabled, parentReportNextStep, recentWaypoints, + defaultP2PMileageRate, ]); const renderItem = useCallback( diff --git a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx index 617fea9690ae..1799b827f2ea 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx @@ -398,6 +398,7 @@ function IOURequestStepDistanceOdometer({ odometerEnd: end, }, policy, + defaultP2PMileageRate, ); Navigation.goBack(); return; @@ -482,6 +483,7 @@ function IOURequestStepDistanceOdometer({ isSelfTourViewed: !!isSelfTourViewed, amountOwed, ownerBillingGraceEndPeriod, + defaultP2PMileageRate, }); }; diff --git a/src/pages/iou/request/step/IOURequestStepReport.tsx b/src/pages/iou/request/step/IOURequestStepReport.tsx index f912a0625110..fa6d79474cc4 100644 --- a/src/pages/iou/request/step/IOURequestStepReport.tsx +++ b/src/pages/iou/request/step/IOURequestStepReport.tsx @@ -94,6 +94,7 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { const perDiemOriginalPolicy = getPolicyByCustomUnitID(transaction, allPolicies); const [transactions] = useOptimisticDraftTransactions(transaction); const [betas] = useOnyx(ONYXKEYS.BETAS); + const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const handleGoBack = () => { if (isEditing) { Navigation.dismissToSuperWideRHP(); @@ -187,6 +188,7 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { allTransactions, translate, toLocaleDigit, + defaultP2PMileageRate, }); removeTransaction(transaction.transactionID); } @@ -233,6 +235,7 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { allTransactions, translate, toLocaleDigit, + defaultP2PMileageRate, }); removeTransaction(transaction.transactionID); }); From 138b22d009b2edd4cba13b768f6fe2156ba176fd Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 30 Mar 2026 06:53:24 -0700 Subject: [PATCH 16/51] Remove defaultP2PMileageRate from non-distance updateMoneyRequest functions Only updateMoneyRequestDistance and updateMoneyRequestDistanceRate actually need the mileage rate. Remove it from the other 11 updateMoneyRequest* functions where it was being threaded through unnecessarily. Made-with: Cursor --- src/libs/actions/IOU/index.ts | 41 ++++------------------------------- 1 file changed, 4 insertions(+), 37 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index d0dff1e32ea4..d14660f4fa70 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -4870,7 +4870,6 @@ type UpdateMoneyRequestDateParams = { currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }; /** Updates the created date of an expense */ @@ -4888,7 +4887,6 @@ function updateMoneyRequestDate({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, - defaultP2PMileageRate, }: UpdateMoneyRequestDateParams) { const transactionChanges: TransactionChanges = { created: value, @@ -4896,7 +4894,7 @@ function updateMoneyRequestDate({ let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { - data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); + data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -4910,7 +4908,6 @@ function updateMoneyRequestDate({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); removeTransactionFromDuplicateTransactionViolation(data.onyxData, transactionID, transactions, transactionViolations); } @@ -4931,7 +4928,6 @@ function updateMoneyRequestBillable({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, - defaultP2PMileageRate, }: { transactionID: string | undefined; transactionThreadReport: OnyxEntry; @@ -4944,7 +4940,6 @@ function updateMoneyRequestBillable({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }) { if (!transactionID || !transactionThreadReport?.reportID) { return; @@ -4964,7 +4959,6 @@ function updateMoneyRequestBillable({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_BILLABLE, params, onyxData); } @@ -4981,7 +4975,6 @@ function updateMoneyRequestReimbursable({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, - defaultP2PMileageRate, }: { transactionID: string | undefined; transactionThreadReport: OnyxEntry; @@ -4994,7 +4987,6 @@ function updateMoneyRequestReimbursable({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }) { if (!transactionID || !transactionThreadReport?.reportID) { return; @@ -5014,7 +5006,6 @@ function updateMoneyRequestReimbursable({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_REIMBURSABLE, params, onyxData); } @@ -5032,7 +5023,6 @@ function updateMoneyRequestMerchant({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, - defaultP2PMileageRate, }: { transactionID: string; transactionThreadReport: OnyxEntry; @@ -5045,7 +5035,6 @@ function updateMoneyRequestMerchant({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const transactionChanges: TransactionChanges = { merchant: value, @@ -5053,7 +5042,7 @@ function updateMoneyRequestMerchant({ let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { - data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); + data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -5067,7 +5056,6 @@ function updateMoneyRequestMerchant({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); } const {params, onyxData} = data; @@ -5088,7 +5076,6 @@ function updateMoneyRequestAttendees({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, - defaultP2PMileageRate, }: { transactionID: string; transactionThreadReport: OnyxEntry; @@ -5102,7 +5089,6 @@ function updateMoneyRequestAttendees({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const transactionChanges: TransactionChanges = { attendees, @@ -5120,7 +5106,6 @@ function updateMoneyRequestAttendees({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); const {params, onyxData} = data; API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_ATTENDEES, params, onyxData); @@ -5140,7 +5125,6 @@ type UpdateMoneyRequestTagParams = { isASAPSubmitBetaEnabled: boolean; hash?: number; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }; /** Updates the tag of an expense */ @@ -5158,7 +5142,6 @@ function updateMoneyRequestTag({ isASAPSubmitBetaEnabled, hash, parentReportNextStep, - defaultP2PMileageRate, }: UpdateMoneyRequestTagParams) { const transactionChanges: TransactionChanges = { tag, @@ -5177,7 +5160,6 @@ function updateMoneyRequestTag({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAG, params, onyxData); } @@ -5195,7 +5177,6 @@ function updateMoneyRequestTaxAmount({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, - defaultP2PMileageRate, }: { transactionID: string; transactionThreadReport: OnyxEntry; @@ -5208,7 +5189,6 @@ function updateMoneyRequestTaxAmount({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const transactionChanges = { taxAmount, @@ -5225,7 +5205,6 @@ function updateMoneyRequestTaxAmount({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAX_AMOUNT, params, onyxData); } @@ -5244,7 +5223,6 @@ type UpdateMoneyRequestTaxRateParams = { currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }; /** Updates the created tax rate of an expense */ @@ -5262,7 +5240,6 @@ function updateMoneyRequestTaxRate({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, - defaultP2PMileageRate, }: UpdateMoneyRequestTaxRateParams) { const transactionChanges = { taxCode, @@ -5281,7 +5258,6 @@ function updateMoneyRequestTaxRate({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_TAX_RATE, params, onyxData); @@ -5427,7 +5403,6 @@ function updateMoneyRequestCategory({ isASAPSubmitBetaEnabled, hash, parentReportNextStep, - defaultP2PMileageRate, }: { transactionID: string; transactionThreadReport: OnyxEntry; @@ -5442,7 +5417,6 @@ function updateMoneyRequestCategory({ isASAPSubmitBetaEnabled: boolean; hash?: number; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const transactionChanges: TransactionChanges = { category, @@ -5462,7 +5436,6 @@ function updateMoneyRequestCategory({ isASAPSubmitBetaEnabled, hash, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_CATEGORY, params, onyxData); } @@ -5480,7 +5453,6 @@ function updateMoneyRequestDescription({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, - defaultP2PMileageRate, }: { transactionID: string; transactionThreadReport: OnyxEntry; @@ -5493,7 +5465,6 @@ function updateMoneyRequestDescription({ currentUserEmailParam: string; isASAPSubmitBetaEnabled: boolean; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const parsedComment = getParsedComment(comment); const transactionChanges: TransactionChanges = { @@ -5502,7 +5473,7 @@ function updateMoneyRequestDescription({ let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { - data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); + data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -5516,7 +5487,6 @@ function updateMoneyRequestDescription({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); } const {params, onyxData} = data; @@ -7948,7 +7918,6 @@ type UpdateMoneyRequestAmountAndCurrencyParams = { isASAPSubmitBetaEnabled: boolean; policyRecentlyUsedCurrencies: string[]; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }; /** Updates the amount and currency fields of an expense */ @@ -7972,7 +7941,6 @@ function updateMoneyRequestAmountAndCurrency({ isASAPSubmitBetaEnabled, policyRecentlyUsedCurrencies, parentReportNextStep, - defaultP2PMileageRate, }: UpdateMoneyRequestAmountAndCurrencyParams) { const transactionChanges = { amount, @@ -7985,7 +7953,7 @@ function updateMoneyRequestAmountAndCurrency({ let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { - data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); + data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -8001,7 +7969,6 @@ function updateMoneyRequestAmountAndCurrency({ isASAPSubmitBetaEnabled, policyRecentlyUsedCurrencies, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); removeTransactionFromDuplicateTransactionViolation(data.onyxData, transactionID, transactions, transactionViolations); } From 48723e3dd92e78b0d606a78a5560b1c9c1bb53bb Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 30 Mar 2026 07:12:30 -0700 Subject: [PATCH 17/51] Remove redundant true arg from non-distance getUpdateTrackExpenseParams calls The shouldBuildOptimisticModifiedExpenseReportAction parameter defaults to true, so only pass it explicitly when followed by defaultP2PMileageRate (the distance cases). Made-with: Cursor --- src/libs/actions/IOU/index.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index d14660f4fa70..e83c2ae6fc5a 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -4894,7 +4894,7 @@ function updateMoneyRequestDate({ let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { - data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true); + data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -5042,7 +5042,7 @@ function updateMoneyRequestMerchant({ let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { - data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true); + data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -5473,7 +5473,7 @@ function updateMoneyRequestDescription({ let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { - data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true); + data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy); } else { data = getUpdateMoneyRequestParams({ transactionID, @@ -7953,7 +7953,7 @@ function updateMoneyRequestAmountAndCurrency({ let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { - data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true); + data = getUpdateTrackExpenseParams(transactionID, transactionThreadReport?.reportID, transactionChanges, policy); } else { data = getUpdateMoneyRequestParams({ transactionID, From 4fc3083154c94633028ddfc6e32584921dd0f4fe Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 30 Mar 2026 07:26:40 -0700 Subject: [PATCH 18/51] Remove extra param --- src/libs/actions/Transaction.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 460301c54b53..df7cdf039ad3 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -835,7 +835,7 @@ function openDraftDistanceExpense() { } function getDefaultP2PMileageRate() { - API.read(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, {}, {}); + API.read(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, {}); } /** From f3e0771e0841e72d30d7f18dff6e9009495002a2 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 30 Mar 2026 07:33:10 -0700 Subject: [PATCH 19/51] Remove getDefaultP2PMileageRate fallback function The rate is always fetched from Auth when a distance expense is created, so the hardcoded USD fallback is unnecessary. Callers now use the stored rate directly. Made-with: Cursor --- src/libs/DistanceRequestUtils.ts | 19 ++----------------- src/libs/TransactionUtils/index.ts | 3 ++- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 43eacefff5a7..81ac00c91600 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -267,27 +267,13 @@ function getDistanceMerchant( return `${distanceInUnits} ${CONST.DISTANCE_MERCHANT_SEPARATOR} ${ratePerUnit}`; } -/** - * Returns the default P2P mileage rate from Auth (stored in Onyx). - * Falls back to USD defaults if the server-fetched rate hasn't loaded yet. - */ -function getDefaultP2PMileageRate(storedRate?: DefaultP2PMileageRate | null): {rate: number; unit: Unit} { - if (storedRate) { - return storedRate; - } - return {rate: 72.5, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES as Unit}; -} - /** * Retrieves the rate and unit for a P2P distance expense. */ function getRateForP2P(currency: string, transaction: OnyxEntry, defaultP2PMileageRate?: DefaultP2PMileageRate | null): MileageRate { - const mileageRate = getDefaultP2PMileageRate(defaultP2PMileageRate); - - // Ensure the rate is updated when the currency changes, otherwise use the stored rate - const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? mileageRate.rate) : mileageRate.rate; + const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? defaultP2PMileageRate?.rate) : defaultP2PMileageRate?.rate; return { - ...mileageRate, + unit: defaultP2PMileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, currency, rate, }; @@ -513,7 +499,6 @@ export default { getDistanceForDisplay, getRoundedDistanceInUnits, getRateForP2P, - getDefaultP2PMileageRate, getCustomUnitRateID, convertToDistanceInMeters, getTaxableAmount, diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index f3537cec7c2b..1593f9f9fe24 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -2834,7 +2834,8 @@ function recalculateUnreportedTransactionDetails( // For distance requests we need to update its custom unit ID to `_FAKE_P2P_ID_` so it's no longer tied to the policy's rate which would cause the "Rate out of policy" violation to appear. // Let's also set the defaultP2PRate and update the distanceUnit, the quantity, the amount, the currency and the merchant to match the P2P rate. if (isDistanceRequest(transaction)) { - const {rate, unit} = DistanceRequestUtils.getDefaultP2PMileageRate(defaultP2PMileageRate); + const rate = defaultP2PMileageRate?.rate; + const unit = defaultP2PMileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; const currency = destinationCurrency ?? CONST.CURRENCY.USD; const distance = parseFloat( DistanceRequestUtils.getRoundedDistanceInUnits(getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES), unit), From b8e01f75a9a04f8e828479a376beb5eff9e471b0 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 7 Apr 2026 14:45:21 -0700 Subject: [PATCH 20/51] Suppress pre-existing react-hooks/set-state-in-effect warnings in SplitExpensePage These warnings existed before this PR but are caught by the Changed Files ESLint check. The setState-in-effect pattern here is intentional for error message state management that responds to data changes. Made-with: Cursor --- src/pages/iou/SplitExpensePage.tsx | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/pages/iou/SplitExpensePage.tsx b/src/pages/iou/SplitExpensePage.tsx index 23fd08b6e0c7..39014223b322 100644 --- a/src/pages/iou/SplitExpensePage.tsx +++ b/src/pages/iou/SplitExpensePage.tsx @@ -144,6 +144,7 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const currencySymbol = getCurrencySymbol(transactionDetails.currency ?? '') ?? transactionDetails.currency ?? CONST.CURRENCY.USD; useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect setErrorMessage(''); }, [splitExpenses.length]); @@ -199,11 +200,13 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const errorString = getLatestErrorMessage(draftTransaction ?? {}); if (errorString) { + // eslint-disable-next-line react-hooks/set-state-in-effect setErrorMessage(errorString); } }, [draftTransaction, draftTransaction?.errors]); useEffect(() => { + // eslint-disable-next-line react-hooks/set-state-in-effect setErrorMessage(''); }, [sumOfSplitExpenses, splitExpenses]); From d78835a62ac4f31417dd8d04ed2272b679a84c7f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 7 Apr 2026 14:50:48 -0700 Subject: [PATCH 21/51] Fix Prettier and ESLint CI failures - Run Prettier on DistanceRequestUtils.ts and IOU/MoneyRequest.ts - Suppress pre-existing @typescript-eslint/no-deprecated errors for translateLocal fallbacks in TransactionUtils and Transaction action (matches existing pattern used throughout both files on main) Made-with: Cursor --- src/libs/DistanceRequestUtils.ts | 3 +-- src/libs/TransactionUtils/index.ts | 1 + src/libs/actions/IOU/MoneyRequest.ts | 3 +-- src/libs/actions/Transaction.ts | 1 + 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index e8476badfc58..6a8d49c9a8ea 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -415,8 +415,7 @@ function getRate({ const customUnitRateID = getRateID(transaction); const customMileageRate = (customUnitRateID && (mileageRates?.[customUnitRateID] ?? mileageRatesForMovingExpenses?.[customUnitRateID])) || (isUnreportedExpense ? undefined : defaultMileageRate); - const mileageRate = - isCustomUnitRateIDForP2P(transaction) || isFakeP2PRate ? getRateForP2P(policyCurrency, transaction, defaultP2PMileageRate) : customMileageRate; + const mileageRate = isCustomUnitRateIDForP2P(transaction) || isFakeP2PRate ? getRateForP2P(policyCurrency, transaction, defaultP2PMileageRate) : customMileageRate; const unit = getDistanceUnit(useTransactionDistanceUnit ? transaction : undefined, mileageRate); return { ...mileageRate, diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 0bc9f603f9cc..2b5dcd42cad2 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -2839,6 +2839,7 @@ function recalculateUnreportedTransactionDetails( let modifiedCurrency: string | undefined; let modifiedMerchant: string | undefined; + // eslint-disable-next-line @typescript-eslint/no-deprecated const translateFn = translateParam ?? translateLocal; const toLocaleDigitFn = toLocaleDigitParam ?? ((digit: string) => toLocaleDigit(IntlStore.getCurrentLocale(), digit)); diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 3c0febd82922..73f75087cac5 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -804,8 +804,7 @@ function handleMoneyRequestStepDistanceNavigation({ ? DistanceRequestUtils.getRateByCustomUnitRateID({customUnitRateID: rateID, policy: policyForMovingExpenses}) : undefined; const currency = ratePolicyForMovingExpenses?.currency ?? personalOutputCurrency ?? CONST.CURRENCY.USD; - const distanceUnit = - ratePolicyForMovingExpenses?.unit ?? DistanceRequestUtils.getRateForP2P(currency, transaction, defaultP2PMileageRate).unit; + const distanceUnit = ratePolicyForMovingExpenses?.unit ?? DistanceRequestUtils.getRateForP2P(currency, transaction, defaultP2PMileageRate).unit; const distanceInMeters = DistanceRequestUtils.convertToDistanceInMeters(distance, unit); const distanceInDistanceUnit = roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, distanceUnit)); setMoneyRequestDistance(transactionID, distanceInDistanceUnit, true, distanceUnit); diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 1e94530ed8c7..898261debc54 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -879,6 +879,7 @@ function changeTransactionsReport({ toLocaleDigit: toLocaleDigitParam, defaultP2PMileageRate, }: ChangeTransactionsReportProps) { + // eslint-disable-next-line @typescript-eslint/no-deprecated const translate = translateParam ?? translateLocal; const toLocaleDigit = toLocaleDigitParam ?? ((digit: string) => localeDigitForStandardDigit(IntlStore.getCurrentLocale(), digit)); const reportID = newReport?.reportID ?? CONST.REPORT.UNREPORTED_REPORT_ID; From ed9ee57bddc35ef215b0d34bada41e1ec70312ed Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 8 Apr 2026 15:07:24 -0700 Subject: [PATCH 22/51] Restore shouldShowBusinessBankAccountOptions to useMemo dependency array Accidentally dropped during merge conflict resolution. The value is used inside the memo so it must remain in the dependency array. Made-with: Cursor --- src/hooks/useSearchBulkActions.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/hooks/useSearchBulkActions.ts b/src/hooks/useSearchBulkActions.ts index eef394f047ab..3f8cb74d02e6 100644 --- a/src/hooks/useSearchBulkActions.ts +++ b/src/hooks/useSearchBulkActions.ts @@ -1301,6 +1301,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { amountOwed, allTransactions, isBetaEnabled, + shouldShowBusinessBankAccountOptions, ]); const handleOfflineModalClose = useCallback(() => { From cb08f953df6bde4f9a66bbcc8bd96977b2c01dcb Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Wed, 8 Apr 2026 22:31:27 +0000 Subject: [PATCH 23/51] Restore AddNewCardPage to use useState instead of useConfirmModal The merge with main incorrectly took main's version of this file, which references isModalVisible without defining it (since main replaced useState with useConfirmModal but left a stale reference). This restores the PR's version which properly uses useState. Co-authored-by: Neil Marcellini --- .../Wallet/PersonalCards/AddNewCardPage.tsx | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/pages/settings/Wallet/PersonalCards/AddNewCardPage.tsx b/src/pages/settings/Wallet/PersonalCards/AddNewCardPage.tsx index 835b9f4e9282..d9f3d7b39cba 100644 --- a/src/pages/settings/Wallet/PersonalCards/AddNewCardPage.tsx +++ b/src/pages/settings/Wallet/PersonalCards/AddNewCardPage.tsx @@ -1,8 +1,7 @@ -import React, {useEffect} from 'react'; +import React, {useEffect, useState} from 'react'; import {View} from 'react-native'; +import ConfirmModal from '@components/ConfirmModal'; import FullScreenLoadingIndicator from '@components/FullscreenLoadingIndicator'; -import {ModalActions} from '@components/Modal/Global/ModalContext'; -import useConfirmModal from '@hooks/useConfirmModal'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; @@ -26,7 +25,7 @@ function AddPersonalNewCardPage() { const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED); const [betas] = useOnyx(ONYXKEYS.BETAS); const {currentStep} = addNewPersonalCardFeed ?? {}; - const {showConfirmModal} = useConfirmModal(); + const [isModalVisible, setIsModalVisible] = useState(false); const {translate} = useLocalize(); const {accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); const isAddCardFeedLoading = isLoadingOnyxValue(addNewPersonalCardFeedMetadata); @@ -62,31 +61,31 @@ function AddPersonalNewCardPage() { CurrentStep = ; break; case CONST.PERSONAL_CARDS.STEP.PLAID_CONNECTION: - CurrentStep = ( - { - showConfirmModal({ - title: translate('workspace.companyCards.addNewCard.exitModal.title'), - success: true, - confirmText: translate('workspace.companyCards.addNewCard.exitModal.confirmText'), - cancelText: translate('workspace.companyCards.addNewCard.exitModal.cancelText'), - prompt: translate('workspace.companyCards.addNewCard.exitModal.prompt'), - }).then((result) => { - if (result.action !== ModalActions.CONFIRM) { - return; - } - navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, false, betas); - }); - }} - /> - ); + CurrentStep = setIsModalVisible(true)} />; break; default: CurrentStep = ; break; } - return {CurrentStep}; + return ( + <> + {CurrentStep} + setIsModalVisible(false)} + onConfirm={() => { + setIsModalVisible(false); + navigateToConciergeChat(conciergeReportID, introSelected, currentUserAccountID, false, betas); + }} + /> + + ); } export default AddPersonalNewCardPage; From f8ce1eccb3234221047c95fc248b6c3811785476 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 9 Apr 2026 08:20:29 -0700 Subject: [PATCH 24/51] Simplify policyForMovingExpenses pass-through in getRate call Optional params accept undefined, so the conditional spread is unnecessary. Made-with: Cursor --- src/libs/actions/IOU/MoneyRequest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index caf4e20415b4..4db8fc8ab5b0 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -663,7 +663,7 @@ function handleMoneyRequestStepDistanceNavigation({ const mileageRate = DistanceRequestUtils.getRate({ transaction, policy, - ...(policyForMovingExpenses && {policyForMovingExpenses}), + policyForMovingExpenses, defaultP2PMileageRate, }); amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, mileageRate?.rate ?? 0); From 58af665a5abb72d10d6d11f9f68af57cf0fc538d Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 9 Apr 2026 08:30:23 -0700 Subject: [PATCH 25/51] Make translate and toLocaleDigit required in changeTransactionsReport All callers already pass these parameters, so the optional fallbacks to deprecated translateLocal and IntlStore were dead code. Remove the fallbacks and the now-unused imports. Made-with: Cursor --- src/libs/actions/Transaction.ts | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 898261debc54..6dab41a3bfb8 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -15,8 +15,6 @@ import type { import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as CollectionUtils from '@libs/CollectionUtils'; import DateUtils from '@libs/DateUtils'; -import {toLocaleDigit as localeDigitForStandardDigit} from '@libs/LocaleDigitUtils'; -import {translateLocal} from '@libs/Localize'; import {buildNextStepNew, buildOptimisticNextStep} from '@libs/NextStepUtils'; import * as NumberUtils from '@libs/NumberUtils'; import {rand64} from '@libs/NumberUtils'; @@ -53,7 +51,6 @@ import { } from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import CONST from '@src/CONST'; -import IntlStore from '@src/languages/IntlStore'; import ONYXKEYS from '@src/ONYXKEYS'; import type { PersonalDetails, @@ -860,8 +857,8 @@ type ChangeTransactionsReportProps = { reportNextStep?: OnyxEntry; policyCategories?: OnyxEntry; allTransactions: OnyxCollection; - translate?: LocaleContextProps['translate']; - toLocaleDigit?: LocaleContextProps['toLocaleDigit']; + translate: LocaleContextProps['translate']; + toLocaleDigit: LocaleContextProps['toLocaleDigit']; defaultP2PMileageRate?: DefaultP2PMileageRate; }; @@ -875,13 +872,10 @@ function changeTransactionsReport({ reportNextStep, policyCategories, allTransactions, - translate: translateParam, - toLocaleDigit: toLocaleDigitParam, + translate, + toLocaleDigit, defaultP2PMileageRate, }: ChangeTransactionsReportProps) { - // eslint-disable-next-line @typescript-eslint/no-deprecated - const translate = translateParam ?? translateLocal; - const toLocaleDigit = toLocaleDigitParam ?? ((digit: string) => localeDigitForStandardDigit(IntlStore.getCurrentLocale(), digit)); const reportID = newReport?.reportID ?? CONST.REPORT.UNREPORTED_REPORT_ID; const transactions = transactionIDs.map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]).filter((t): t is NonNullable => t !== undefined); From c054ec13a0e4508f4b7e85d3902ff4aa9f25aca6 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 9 Apr 2026 14:51:35 -0700 Subject: [PATCH 26/51] Refactor isUnreportedAndHasInvalidDistanceRateTransaction to use early returns Made-with: Cursor --- src/libs/TransactionUtils/index.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 2b5dcd42cad2..56933c6b7375 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1137,16 +1137,14 @@ function isFetchingWaypointsFromServer(transaction: OnyxInputOrEntry, policy: OnyxEntry, defaultP2PMileageRate?: DefaultP2PMileageRate | null) { - if (transaction && isDistanceRequest(transaction)) { - const {rate} = DistanceRequestUtils.getRate({transaction, policy, defaultP2PMileageRate}); - const isUnreportedExpense = !transaction.reportID || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID || String(transaction.reportID) === CONST.REPORT.SPLIT_REPORT_ID; - - if (isUnreportedExpense && !rate) { - return true; - } + if (!transaction || !isDistanceRequest(transaction)) { + return false; } - return false; + const {rate} = DistanceRequestUtils.getRate({transaction, policy, defaultP2PMileageRate}); + const isUnreportedExpense = !transaction.reportID || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID || String(transaction.reportID) === CONST.REPORT.SPLIT_REPORT_ID; + + return isUnreportedExpense && !rate; } /** From 2e19313371f549ad1354e09784d09412d784d4be Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 9 Apr 2026 14:56:14 -0700 Subject: [PATCH 27/51] Remove translate/toLocaleDigit fallbacks from recalculateUnreportedTransactionDetails Pass translateLocal and toLocaleDigit explicitly at the deleteAppReport callsite instead of relying on fallback defaults inside the utility. Made-with: Cursor --- src/libs/TransactionUtils/index.ts | 9 ++------- src/libs/actions/Report/index.ts | 7 +++++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 56933c6b7375..dc58c5e8b0e5 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -2820,11 +2820,10 @@ function shouldReuseInitialTransaction( function recalculateUnreportedTransactionDetails( transaction?: OnyxEntry, destinationCurrency?: string | undefined, - translateParam?: LocaleContextProps['translate'], - toLocaleDigitParam?: LocaleContextProps['toLocaleDigit'], + translateFn?: LocaleContextProps['translate'], + toLocaleDigitFn?: LocaleContextProps['toLocaleDigit'], defaultP2PMileageRate?: DefaultP2PMileageRate | null, ) { - // If the transaction is on hold, we need to unhold it because unreported transactions (on selfDM) should never remain on hold. const comment: NullishDeep = { hold: null, }; @@ -2837,10 +2836,6 @@ function recalculateUnreportedTransactionDetails( let modifiedCurrency: string | undefined; let modifiedMerchant: string | undefined; - // eslint-disable-next-line @typescript-eslint/no-deprecated - const translateFn = translateParam ?? translateLocal; - const toLocaleDigitFn = toLocaleDigitParam ?? ((digit: string) => toLocaleDigit(IntlStore.getCurrentLocale(), digit)); - // For distance requests we need to update its custom unit ID to `_FAKE_P2P_ID_` so it's no longer tied to the policy's rate which would cause the "Rate out of policy" violation to appear. // Let's also set the defaultP2PRate and update the distanceUnit, the quantity, the amount, the currency and the merchant to match the P2P rate. if (isDistanceRequest(transaction)) { diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 8f23c3c52cab..40705a7be089 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -74,6 +74,8 @@ import {getMicroSecondOnyxErrorWithTranslationKey, getMicroSecondTranslationErro import fileDownload from '@libs/fileDownload'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import HttpUtils from '@libs/HttpUtils'; +import {toLocaleDigit} from '@libs/LocaleDigitUtils'; +import {translateLocal} from '@libs/Localize'; import Log from '@libs/Log'; import {isEmailPublicDomain} from '@libs/LoginUtils'; import {getMovedReportID} from '@libs/ModifiedExpenseMessage'; @@ -193,6 +195,7 @@ import type {OnboardingCompanySize, OnboardingMessage} from '@userActions/Welcom import CONFIG from '@src/CONFIG'; import type {OnboardingAccounting} from '@src/CONST'; import CONST from '@src/CONST'; +import IntlStore from '@src/languages/IntlStore'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/NewRoomForm'; @@ -5792,8 +5795,8 @@ function deleteAppReport({ const {comment, modifiedAmount, modifiedCurrency, modifiedMerchant} = recalculateUnreportedTransactionDetails( transaction, undefined, - undefined, - undefined, + translateLocal, + (digit: string) => toLocaleDigit(IntlStore.getCurrentLocale(), digit), defaultP2PMileageRate, ); From 19a8693366205aeb978d06a53df3de8e35ba49cc Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 9 Apr 2026 14:57:07 -0700 Subject: [PATCH 28/51] Use early return for non-distance requests in recalculateUnreportedTransactionDetails Made-with: Cursor --- src/libs/TransactionUtils/index.ts | 62 ++++++++++++++---------------- 1 file changed, 28 insertions(+), 34 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index dc58c5e8b0e5..dc454cadc43d 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -2828,45 +2828,39 @@ function recalculateUnreportedTransactionDetails( hold: null, }; - if (!transaction) { + if (!transaction || !isDistanceRequest(transaction)) { return {comment}; } - let modifiedAmount: number | undefined; - let modifiedCurrency: string | undefined; - let modifiedMerchant: string | undefined; - // For distance requests we need to update its custom unit ID to `_FAKE_P2P_ID_` so it's no longer tied to the policy's rate which would cause the "Rate out of policy" violation to appear. // Let's also set the defaultP2PRate and update the distanceUnit, the quantity, the amount, the currency and the merchant to match the P2P rate. - if (isDistanceRequest(transaction)) { - const rate = defaultP2PMileageRate?.rate; - const unit = defaultP2PMileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; - const currency = destinationCurrency ?? CONST.CURRENCY.USD; - const distance = parseFloat( - DistanceRequestUtils.getRoundedDistanceInUnits(getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES), unit), - ); - const distanceInMeters = DistanceRequestUtils.convertToDistanceInMeters(distance, unit); - comment.customUnit = { - customUnitID: CONST.CUSTOM_UNITS.FAKE_P2P_ID, - customUnitRateID: CONST.CUSTOM_UNITS.FAKE_P2P_ID, - defaultP2PRate: rate, - distanceUnit: unit, - quantity: distance, - }; - modifiedAmount = -DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, rate ?? 0); - modifiedCurrency = currency; - modifiedMerchant = DistanceRequestUtils.getDistanceMerchant( - true, - distanceInMeters, - unit, - rate, - currency, - translateFn, - toLocaleDigitFn, - getCurrencySymbol, - isManualDistanceRequest(transaction), - ); - } + const rate = defaultP2PMileageRate?.rate; + const unit = defaultP2PMileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; + const currency = destinationCurrency ?? CONST.CURRENCY.USD; + const distance = parseFloat( + DistanceRequestUtils.getRoundedDistanceInUnits(getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES), unit), + ); + const distanceInMeters = DistanceRequestUtils.convertToDistanceInMeters(distance, unit); + comment.customUnit = { + customUnitID: CONST.CUSTOM_UNITS.FAKE_P2P_ID, + customUnitRateID: CONST.CUSTOM_UNITS.FAKE_P2P_ID, + defaultP2PRate: rate, + distanceUnit: unit, + quantity: distance, + }; + const modifiedAmount = -DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, rate ?? 0); + const modifiedCurrency = currency; + const modifiedMerchant = DistanceRequestUtils.getDistanceMerchant( + true, + distanceInMeters, + unit, + rate, + currency, + translateFn, + toLocaleDigitFn, + getCurrencySymbol, + isManualDistanceRequest(transaction), + ); return {comment, modifiedAmount, modifiedCurrency, modifiedMerchant}; } From 00ebc428979bd9291e6d5fa9f339f3f8bda69f59 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 9 Apr 2026 15:05:24 -0700 Subject: [PATCH 29/51] Thread translate/toLocaleDigit from components into deleteAppReport Instead of importing IntlStore and creating toLocaleDigit inline in the action file, pass translate and toLocaleDigit from the component's useLocalize() hook through deleteAppReport and bulkDeleteReports. Made-with: Cursor --- src/components/MoneyReportHeader.tsx | 4 +++- src/hooks/useSearchBulkActions.ts | 6 +++++- src/libs/actions/Report/index.ts | 11 ++++++----- src/libs/actions/Search.ts | 8 +++++++- .../ContextMenu/PopoverReportActionContextMenu.tsx | 4 +++- 5 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 8d253b589f78..73c73e6b39c9 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -265,7 +265,7 @@ function MoneyReportHeaderContent({reportID: reportIDProp, shouldDisplayBackButt }); const draftTransactionIDs = Object.keys(transactionDrafts ?? {}); - const {translate, localeCompare} = useLocalize(); + const {translate, toLocaleDigit, localeCompare} = useLocalize(); const {isProduction} = useEnvironment(); const exportTemplates = useMemo( () => getExportTemplates(integrationsExportTemplates ?? [], csvExportLayouts ?? {}, translate, policy), @@ -1701,6 +1701,8 @@ function MoneyReportHeaderContent({reportID: reportIDProp, shouldDisplayBackButt bankAccountList, hash: currentSearchHash, defaultP2PMileageRate, + translate, + toLocaleDigit, }); }); }); diff --git a/src/hooks/useSearchBulkActions.ts b/src/hooks/useSearchBulkActions.ts index 3f8cb74d02e6..9aed01aca31c 100644 --- a/src/hooks/useSearchBulkActions.ts +++ b/src/hooks/useSearchBulkActions.ts @@ -96,7 +96,7 @@ function getRestrictedPolicyID( } function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { - const {translate, localeCompare, formatPhoneNumber} = useLocalize(); + const {translate, toLocaleDigit, localeCompare, formatPhoneNumber} = useLocalize(); const styles = useThemeStyles(); const theme = useTheme(); const {isOffline} = useNetwork(); @@ -493,6 +493,8 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { bankAccountList, hash, defaultP2PMileageRate, + translate, + toLocaleDigit, }); } } else { @@ -511,6 +513,8 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { transactions, allReportNameValuePairs, defaultP2PMileageRate, + translate, + toLocaleDigit, }); } clearSelectedTransactions(); diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 40705a7be089..ce4d18cd5f1a 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -74,8 +74,6 @@ import {getMicroSecondOnyxErrorWithTranslationKey, getMicroSecondTranslationErro import fileDownload from '@libs/fileDownload'; import getIsNarrowLayout from '@libs/getIsNarrowLayout'; import HttpUtils from '@libs/HttpUtils'; -import {toLocaleDigit} from '@libs/LocaleDigitUtils'; -import {translateLocal} from '@libs/Localize'; import Log from '@libs/Log'; import {isEmailPublicDomain} from '@libs/LoginUtils'; import {getMovedReportID} from '@libs/ModifiedExpenseMessage'; @@ -195,7 +193,6 @@ import type {OnboardingCompanySize, OnboardingMessage} from '@userActions/Welcom import CONFIG from '@src/CONFIG'; import type {OnboardingAccounting} from '@src/CONST'; import CONST from '@src/CONST'; -import IntlStore from '@src/languages/IntlStore'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import INPUT_IDS from '@src/types/form/NewRoomForm'; @@ -5649,6 +5646,8 @@ type DeleteAppReportProps = { bankAccountList: OnyxEntry; hash?: number; defaultP2PMileageRate?: DefaultP2PMileageRate; + translate?: LocaleContextProps['translate']; + toLocaleDigit?: LocaleContextProps['toLocaleDigit']; }; /** Deletes a report and un-reports all transactions on the report along with its reportActions, any linked reports and any linked IOU report actions. */ @@ -5662,6 +5661,8 @@ function deleteAppReport({ bankAccountList, hash, defaultP2PMileageRate, + translate, + toLocaleDigit, }: DeleteAppReportProps) { if (!report?.reportID) { Log.warn('[Report] deleteAppReport called with no reportID'); @@ -5795,8 +5796,8 @@ function deleteAppReport({ const {comment, modifiedAmount, modifiedCurrency, modifiedMerchant} = recalculateUnreportedTransactionDetails( transaction, undefined, - translateLocal, - (digit: string) => toLocaleDigit(IntlStore.getCurrentLocale(), digit), + translate, + toLocaleDigit, defaultP2PMileageRate, ); diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index f66ff2030912..e7bec6903e5f 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -5,7 +5,7 @@ import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import type {TupleToUnion, ValueOf} from 'type-fest'; import type {FormOnyxValues} from '@components/Form/types'; import type {ContinueActionParams, PaymentMethod, PaymentMethodType} from '@components/KYCWall/types'; -import type {LocalizedTranslate} from '@components/LocaleContextProvider'; +import type {LocaleContextProps, LocalizedTranslate} from '@components/LocaleContextProvider'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import type {TransactionListItemType, TransactionReportGroupListItemType} from '@components/Search/SearchList/ListItem/types'; import type {BankAccountMenuItem, BulkPaySelectionData, PaymentData, SearchQueryJSON, SelectedReports, SelectedTransactionInfo, SelectedTransactions} from '@components/Search/types'; @@ -127,6 +127,8 @@ type BulkDeleteReportsParams = { transactions?: OnyxCollection; allReportNameValuePairs: OnyxCollection; defaultP2PMileageRate?: DefaultP2PMileageRate; + translate?: LocaleContextProps['translate']; + toLocaleDigit?: LocaleContextProps['toLocaleDigit']; }; function handleActionButtonPress({ @@ -908,6 +910,8 @@ function bulkDeleteReports({ transactions, allReportNameValuePairs, defaultP2PMileageRate, + translate, + toLocaleDigit, }: BulkDeleteReportsParams) { const transactionIDList: string[] = []; const reportIDList: string[] = []; @@ -982,6 +986,8 @@ function bulkDeleteReports({ allTransactionViolations: transactionsViolations, bankAccountList, defaultP2PMileageRate, + translate, + toLocaleDigit, }); } } diff --git a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx index 425c48ac83fa..c7c0e900b605 100644 --- a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -47,7 +47,7 @@ type PopoverReportActionContextMenuProps = { }; function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuProps) { - const {translate} = useLocalize(); + const {translate, toLocaleDigit} = useLocalize(); const reportIDRef = useRef(undefined); const typeRef = useRef(undefined); const reportActionRef = useRef> | null>(null); @@ -383,6 +383,8 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro bankAccountList, hash: currentSearchHash, defaultP2PMileageRate, + translate, + toLocaleDigit, }); } else if (reportAction) { // eslint-disable-next-line @typescript-eslint/no-deprecated From f5cf7912aa51c366f9209cfd0c689ecf432adf91 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Thu, 9 Apr 2026 15:27:52 -0700 Subject: [PATCH 30/51] Remove isUnreportedAndHasInvalidDistanceRateTransaction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Violations are a workspace concept โ€” unreported P2P distance expenses in the Self DM should never show "rate out of policy" RBR indicators. Remove the function and all its callsites from TransactionPreviewUtils, TransactionPreviewContent, and TransactionItemRow. Made-with: Cursor --- .../TransactionPreviewContent.tsx | 7 ++----- src/components/TransactionItemRow/index.tsx | 11 ++--------- src/libs/TransactionPreviewUtils.ts | 12 +----------- src/libs/TransactionUtils/index.ts | 15 --------------- 4 files changed, 5 insertions(+), 40 deletions(-) diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index 341957ea8c7c..74b167b93d8f 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -74,7 +74,6 @@ function TransactionPreviewContent({ const {translate} = useLocalize(); const {environmentURL} = useEnvironment(); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const isParentPolicyExpenseChat = isPolicyExpenseChat(chatReport); const transactionDetails = useMemo>( () => getTransactionDetails(transaction, undefined, policy, isParentPolicyExpenseChat) ?? {}, @@ -115,9 +114,8 @@ function TransactionPreviewContent({ currentUserEmail, currentUserAccountID, reportActions, - defaultP2PMileageRate, }), - [areThereDuplicates, transactionPreviewCommonArguments, isReportAPolicyExpenseChat, currentUserEmail, currentUserAccountID, reportActions, defaultP2PMileageRate], + [areThereDuplicates, transactionPreviewCommonArguments, isReportAPolicyExpenseChat, currentUserEmail, currentUserAccountID, reportActions], ); const {shouldShowRBR, shouldShowMerchant, shouldShowSplitShare, shouldShowTag, shouldShowCategory, shouldShowSkeleton, shouldShowDescription} = conditionals; @@ -157,9 +155,8 @@ function TransactionPreviewContent({ currentUserEmail, currentUserAccountID, originalTransaction, - defaultP2PMileageRate, }), - [transactionPreviewCommonArguments, shouldShowRBR, violationMessage, reportActions, currentUserEmail, currentUserAccountID, originalTransaction, defaultP2PMileageRate], + [transactionPreviewCommonArguments, shouldShowRBR, violationMessage, reportActions, currentUserEmail, currentUserAccountID, originalTransaction], ); const getTranslatedText = (item: TranslationPathOrText) => (item.translationPath ? translate(item.translationPath) : (item.text ?? '')); diff --git a/src/components/TransactionItemRow/index.tsx b/src/components/TransactionItemRow/index.tsx index 7fe9cf251d45..9bb8b8b8697a 100644 --- a/src/components/TransactionItemRow/index.tsx +++ b/src/components/TransactionItemRow/index.tsx @@ -20,7 +20,6 @@ import Text from '@components/Text'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import {useMemoizedLazyExpensifyIcons} from '@hooks/useLazyAsset'; import useLocalize from '@hooks/useLocalize'; -import useOnyx from '@hooks/useOnyx'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; @@ -48,13 +47,11 @@ import { isMerchantMissing, isScanning, isTimeRequest, - isUnreportedAndHasInvalidDistanceRateTransaction, shouldShowAttendees as shouldShowAttendeesUtils, } from '@libs/TransactionUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import type {TranslationPaths} from '@src/languages/types'; -import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetails, Policy, Report, ReportAction, TransactionViolation} from '@src/types/onyx'; import type {SearchTransactionAction} from '@src/types/onyx/SearchResults'; import CategoryCell from './DataCells/CategoryCell'; @@ -213,8 +210,6 @@ function TransactionItemRow({ const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const transactionThreadReportID = reportActions ? getIOUActionForTransactionID(reportActions, transactionItem.transactionID)?.childReportID : undefined; - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); - const isDateColumnWide = dateColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; const isSubmittedColumnWide = submittedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; const isApprovedColumnWide = approvedColumnSize === CONST.SEARCH.TABLE_COLUMN_SIZES.WIDE; @@ -241,9 +236,7 @@ function TransactionItemRow({ return ''; } - const policyParam = policy ?? transactionItem.policy; - const isCustomUnitOutOfPolicy = isUnreportedAndHasInvalidDistanceRateTransaction(transactionItem, policyParam, defaultP2PMileageRate); - const hasFieldErrors = hasMissingSmartscanFields(transactionItem, report) || isCustomUnitOutOfPolicy; + const hasFieldErrors = hasMissingSmartscanFields(transactionItem, report); if (hasFieldErrors) { const amountMissing = isAmountMissing(transactionItem); const merchantMissing = isMerchantMissing(transactionItem); @@ -259,7 +252,7 @@ function TransactionItemRow({ return error; } - }, [transactionItem, translate, report, policy, defaultP2PMileageRate]); + }, [transactionItem, translate, report]); const exchangeRateMessage = getExchangeRate(transactionItem, report?.currency); diff --git a/src/libs/TransactionPreviewUtils.ts b/src/libs/TransactionPreviewUtils.ts index 3a0191cfb6ba..2f8da3aa0ebb 100644 --- a/src/libs/TransactionPreviewUtils.ts +++ b/src/libs/TransactionPreviewUtils.ts @@ -42,7 +42,6 @@ import { isOnHold, isPending, isScanning, - isUnreportedAndHasInvalidDistanceRateTransaction, } from './TransactionUtils'; import {isInvalidMerchantValue} from './ValidationUtils'; import {filterReceiptViolations} from './Violations/ViolationsUtils'; @@ -212,7 +211,6 @@ function getTransactionPreviewTextAndTranslationPaths({ currentUserEmail, currentUserAccountID, originalTransaction, - defaultP2PMileageRate, }: { iouReport: OnyxEntry; policy: OnyxEntry; @@ -227,7 +225,6 @@ function getTransactionPreviewTextAndTranslationPaths({ currentUserEmail: string; currentUserAccountID: number; originalTransaction?: OnyxEntry; - defaultP2PMileageRate?: OnyxTypes.DefaultP2PMileageRate | null; }) { const isFetchingWaypoints = isFetchingWaypointsFromServer(transaction); const isTransactionOnHold = isOnHold(transaction); @@ -305,11 +302,7 @@ function getTransactionPreviewTextAndTranslationPaths({ let previewHeaderText: TranslationPathOrText[] = [{translationPath: getExpenseTypeTranslationKey(getTransactionType(transaction))}]; - if (isDistanceRequest(transaction)) { - if (RBRMessage === undefined && isUnreportedAndHasInvalidDistanceRateTransaction(transaction, policy, defaultP2PMileageRate)) { - RBRMessage = {translationPath: 'violations.customUnitOutOfPolicy'}; - } - } else if (isTransactionScanning) { + if (isTransactionScanning) { previewHeaderText = [{translationPath: 'common.receipt'}]; } else if (isBillSplit) { previewHeaderText = [{translationPath: 'iou.split'}]; @@ -384,7 +377,6 @@ function createTransactionPreviewConditionals({ currentUserEmail, currentUserAccountID, reportActions, - defaultP2PMileageRate, }: { iouReport: OnyxEntry; policy: OnyxEntry; @@ -398,7 +390,6 @@ function createTransactionPreviewConditionals({ currentUserEmail: string; currentUserAccountID: number; reportActions?: OnyxTypes.ReportActions; - defaultP2PMileageRate?: OnyxTypes.DefaultP2PMileageRate | null; }) { const {amount: requestAmount, comment: requestComment, merchant, tag, category} = transactionDetails; @@ -427,7 +418,6 @@ function createTransactionPreviewConditionals({ const shouldShowCategory = !!categoryForDisplay && isReportAPolicyExpenseChat; const hasAnyViolations = - isUnreportedAndHasInvalidDistanceRateTransaction(transaction, policy, defaultP2PMileageRate) || !!hasViolationsOfTypeNotice || hasWarningTypeViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport ?? undefined, policy) || hasViolation(transaction, violations, currentUserEmail ?? '', currentUserAccountID, iouReport ?? undefined, policy, true) || diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index dc454cadc43d..09aa6a84c171 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1133,20 +1133,6 @@ function isFetchingWaypointsFromServer(transaction: OnyxInputOrEntry, policy: OnyxEntry, defaultP2PMileageRate?: DefaultP2PMileageRate | null) { - if (!transaction || !isDistanceRequest(transaction)) { - return false; - } - - const {rate} = DistanceRequestUtils.getRate({transaction, policy, defaultP2PMileageRate}); - const isUnreportedExpense = !transaction.reportID || transaction.reportID === CONST.REPORT.UNREPORTED_REPORT_ID || String(transaction.reportID) === CONST.REPORT.SPLIT_REPORT_ID; - - return isUnreportedExpense && !rate; -} - /** * Return the merchant field from the transaction, return the modifiedMerchant if present. */ @@ -3014,7 +3000,6 @@ export { isDistanceTypeRequest, recalculateUnreportedTransactionDetails, hasSmartScanFailedWithMissingFields, - isUnreportedAndHasInvalidDistanceRateTransaction, }; export type {TransactionChanges}; From 6de9120cdb64341a93e4489707eac4bd86a9a81d Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Thu, 9 Apr 2026 23:13:31 +0000 Subject: [PATCH 31/51] Fix typecheck: add translate/toLocaleDigit to changeTransactionsReport test calls The ChangeTransactionsReportProps type requires translate and toLocaleDigit properties, but test call sites were missing them. Also guard recalculateUnreportedTransactionDetails to return early when translateFn or toLocaleDigitFn are undefined (they are only needed for distance requests). Co-authored-by: Neil Marcellini --- src/libs/TransactionUtils/index.ts | 2 +- tests/actions/IOUTest.ts | 4 +++- tests/unit/TransactionTest.ts | 32 ++++++++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index a5d644e93f62..0d87f1495358 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -2811,7 +2811,7 @@ function recalculateUnreportedTransactionDetails( hold: null, }; - if (!transaction || !isDistanceRequest(transaction)) { + if (!transaction || !isDistanceRequest(transaction) || !translateFn || !toLocaleDigitFn) { return {comment}; } diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 736bc4a89629..21e9e28f1210 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -124,7 +124,7 @@ import createRandomTransaction from '../utils/collections/transaction'; import getOnyxValue from '../utils/getOnyxValue'; import PusherHelper from '../utils/PusherHelper'; import type {MockFetch} from '../utils/TestHelper'; -import {getGlobalFetchMock, getOnyxData, localeCompare, setPersonalDetails, signInWithTestUser, translateLocal} from '../utils/TestHelper'; +import {getGlobalFetchMock, getOnyxData, localeCompare, setPersonalDetails, signInWithTestUser, toLocaleDigit, translateLocal} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForBatchedUpdatesWithAct from '../utils/waitForBatchedUpdatesWithAct'; import waitForNetworkPromises from '../utils/waitForNetworkPromises'; @@ -12088,6 +12088,8 @@ describe('actions/IOU', () => { newReport: result.current.report, policy: mockPolicy, allTransactions, + translate: translateLocal, + toLocaleDigit, }); let updatedTransaction: OnyxEntry; diff --git a/tests/unit/TransactionTest.ts b/tests/unit/TransactionTest.ts index 89f6dc449ee5..d0585f1ddc2f 100644 --- a/tests/unit/TransactionTest.ts +++ b/tests/unit/TransactionTest.ts @@ -127,6 +127,8 @@ describe('Transaction', () => { newReport: report, policy: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const reportActions = await new Promise>((resolve) => { @@ -175,6 +177,8 @@ describe('Transaction', () => { newReport: report, policy: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const reportActions = await new Promise>((resolve) => { @@ -237,6 +241,8 @@ describe('Transaction', () => { policy: undefined, reportNextStep: mockReportNextStep, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -301,6 +307,8 @@ describe('Transaction', () => { policy: undefined, reportNextStep: mockReportNextStep, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -353,6 +361,8 @@ describe('Transaction', () => { policy: undefined, reportNextStep: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -417,6 +427,8 @@ describe('Transaction', () => { newReport: report, policy: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -473,6 +485,8 @@ describe('Transaction', () => { newReport: report, policy: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -526,6 +540,8 @@ describe('Transaction', () => { newReport: report, policy: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -585,6 +601,8 @@ describe('Transaction', () => { newReport: expenseReport, policy: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const report = await new Promise>((resolve) => { @@ -644,6 +662,8 @@ describe('Transaction', () => { newReport: expenseReport, policy: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const report = await new Promise>((resolve) => { @@ -710,6 +730,8 @@ describe('Transaction', () => { newReport: newExpenseReport, policy: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const report = await new Promise>((resolve) => { @@ -775,6 +797,8 @@ describe('Transaction', () => { newReport: newExpenseReport, policy: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const report = await new Promise>((resolve) => { @@ -833,6 +857,8 @@ describe('Transaction', () => { newReport: fakeReport, policy: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -893,6 +919,8 @@ describe('Transaction', () => { newReport: fakeReport, policy: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -947,6 +975,8 @@ describe('Transaction', () => { reportNextStep: undefined, policyCategories, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -1000,6 +1030,8 @@ describe('Transaction', () => { reportNextStep: undefined, policyCategories: undefined, allTransactions, + translate: TestHelper.translateLocal, + toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); From 56e75b2942a745eba19a45ab0e00390ff2d74740 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 15 Apr 2026 11:24:02 -0700 Subject: [PATCH 32/51] Trim unnecessary defaultP2PMileageRate from components that don't need it Remove defaultP2PMileageRate useOnyx hook and parameter from: - AddUnreportedExpenseFooter: always passes newReport to changeTransactionsReport, so the unreported recalculation path (which needs the rate) is never hit - NewReportWorkspaceSelectionPage: same pattern - always passes newReport Also re-apply PR changes lost during merge conflict resolution: - IOU/index.ts: restore getDefaultP2PMileageRate import/call and defaultP2PMileageRate on setCustomUnitRateID (reformatted by prettier) Made-with: Cursor --- src/components/AddUnreportedExpenseFooter.tsx | 2 -- src/libs/actions/IOU/index.ts | 2 +- src/pages/NewReportWorkspaceSelectionPage.tsx | 2 -- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/AddUnreportedExpenseFooter.tsx b/src/components/AddUnreportedExpenseFooter.tsx index 3c265cf76e1c..f5a4ea0cb319 100644 --- a/src/components/AddUnreportedExpenseFooter.tsx +++ b/src/components/AddUnreportedExpenseFooter.tsx @@ -58,7 +58,6 @@ function AddUnreportedExpenseFooter({selectedIds, report, reportToConfirm, repor const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); const [betas] = useOnyx(ONYXKEYS.BETAS); const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.chatReportID}`); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const handleConfirm = () => { if (selectedIds.size === 0) { @@ -96,7 +95,6 @@ function AddUnreportedExpenseFooter({selectedIds, report, reportToConfirm, repor allTransactions: selectedTransactions, translate, toLocaleDigit, - defaultP2PMileageRate, }); } }); diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 1dfc6a965b44..d7055e9bda27 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -102,10 +102,10 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; +import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Accountant, Attendee, Participant, Split} from '@src/types/onyx/IOU'; import type {ErrorFields, Errors, PendingAction, PendingFields} from '@src/types/onyx/OnyxCommon'; import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails'; -import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Unit} from '@src/types/onyx/Policy'; import type RecentlyUsedTags from '@src/types/onyx/RecentlyUsedTags'; import type {ReportNextStep} from '@src/types/onyx/Report'; diff --git a/src/pages/NewReportWorkspaceSelectionPage.tsx b/src/pages/NewReportWorkspaceSelectionPage.tsx index 1d783baab1bd..e249e4a7d7f0 100644 --- a/src/pages/NewReportWorkspaceSelectionPage.tsx +++ b/src/pages/NewReportWorkspaceSelectionPage.tsx @@ -76,7 +76,6 @@ function NewReportWorkspaceSelectionPage({route}: NewReportWorkspaceSelectionPag const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); const [policies, fetchStatus] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [isLoadingApp] = useOnyx(ONYXKEYS.IS_LOADING_APP); @@ -135,7 +134,6 @@ function NewReportWorkspaceSelectionPage({route}: NewReportWorkspaceSelectionPag allTransactions, translate, toLocaleDigit, - defaultP2PMileageRate, }); // eslint-disable-next-line rulesdir/no-default-id-values From 3a64479262d073e42f8ffddf6a2cfaf6caead205 Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Wed, 15 Apr 2026 18:30:47 +0000 Subject: [PATCH 33/51] Remove unused localeCompare import from IOUTest.ts Co-authored-by: Neil Marcellini --- tests/actions/IOUTest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 4dfda64c2598..6babc050f536 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -73,7 +73,7 @@ import {createRandomReport} from '../utils/collections/reports'; import createRandomTransaction from '../utils/collections/transaction'; import getOnyxValue from '../utils/getOnyxValue'; import type {MockFetch} from '../utils/TestHelper'; -import {getGlobalFetchMock, getOnyxData, localeCompare, setPersonalDetails, signInWithTestUser, toLocaleDigit, translateLocal} from '../utils/TestHelper'; +import {getGlobalFetchMock, getOnyxData, setPersonalDetails, signInWithTestUser, toLocaleDigit, translateLocal} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForNetworkPromises from '../utils/waitForNetworkPromises'; From 331a60294b4cae80d7f64ea0fdd7f38ac65ae9c3 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 15 Apr 2026 13:50:29 -0700 Subject: [PATCH 34/51] Simplify: fetch default P2P mileage rate into plain variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace the previous approach (threading defaultP2PMileageRate through components and action functions via useOnyx) with a minimal in-memory store. The rate is fetched from Auth on app init via a side-effect request and stored in a plain module variable in Transaction.ts. DistanceRequestUtils reads it through a simple getter function. No Onyx key, no useOnyx hooks, no Onyx.connect, no parameter threading. Removes the ~670-line hardcoded CURRENCY_TO_DEFAULT_MILEAGE_RATE constant. Falls back to USD 67ยข/mile if the rate hasn't been fetched yet. --- src/ONYXKEYS.ts | 4 -- src/components/AddUnreportedExpenseFooter.tsx | 4 +- .../DistanceRequest/DistanceRequestFooter.tsx | 3 +- .../useUpdateGpsNotification/index.ios.ts | 5 +- .../MoneyRequestConfirmationList.tsx | 2 - .../MoneyRequestHeaderSecondaryActions.tsx | 3 +- .../ReportActionItem/MoneyRequestView.tsx | 5 +- src/hooks/useSearchBulkActions.ts | 13 +--- src/hooks/useSelectedTransactionsActions.ts | 3 +- src/hooks/useUndeleteTransactions.ts | 4 -- .../GetDefaultP2PMileageRateParams.ts | 3 - src/libs/API/parameters/index.ts | 1 - src/libs/API/types.ts | 4 +- src/libs/DistanceRequestUtils.ts | 38 +++++------ src/libs/TransactionUtils/index.ts | 68 ++----------------- src/libs/actions/App.ts | 3 + src/libs/actions/IOU/MoneyRequest.ts | 12 +--- src/libs/actions/IOU/Split.ts | 35 ++-------- src/libs/actions/IOU/UpdateMoneyRequest.ts | 16 +---- src/libs/actions/IOU/index.ts | 15 +--- src/libs/actions/Report/index.ts | 26 +------ src/libs/actions/Search.ts | 12 +--- src/libs/actions/SplitExpenses.ts | 5 +- src/libs/actions/Transaction.ts | 44 ++++++------ src/pages/NewReportWorkspaceSelectionPage.tsx | 4 +- .../Search/SearchTransactionsChangeReport.tsx | 12 ---- .../PopoverReportActionContextMenu.tsx | 7 +- .../iou/SplitExpenseCreateDateRagePage.tsx | 3 +- src/pages/iou/SplitExpenseEditPage.tsx | 5 +- src/pages/iou/SplitExpensePage.tsx | 14 ++-- .../iou/request/step/IOURequestEditReport.tsx | 9 --- .../request/step/IOURequestStepDistance.tsx | 10 +-- .../index.native.tsx | 4 +- .../step/IOURequestStepDistanceManual.tsx | 8 +-- .../step/IOURequestStepDistanceMap.tsx | 11 +-- .../step/IOURequestStepDistanceOdometer.tsx | 10 +-- .../step/IOURequestStepDistanceRate.tsx | 2 - .../iou/request/step/IOURequestStepReport.tsx | 9 --- src/types/onyx/DefaultP2PMileageRate.ts | 10 ++- src/types/onyx/index.ts | 2 - tests/actions/IOUTest.ts | 4 +- tests/unit/TransactionTest.ts | 32 --------- 42 files changed, 103 insertions(+), 381 deletions(-) delete mode 100644 src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 1b5413cad408..71afedaef8ba 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -439,9 +439,6 @@ const ONYXKEYS = { // The access token to be used with the Mapbox library MAPBOX_ACCESS_TOKEN: 'mapboxAccessToken', - /** Default P2P mileage rate fetched from Auth */ - DEFAULT_P2P_MILEAGE_RATE: 'defaultP2PMileageRate', - // Max area supported for HTML element MAX_CANVAS_AREA: 'maxCanvasArea', @@ -1433,7 +1430,6 @@ type OnyxValuesMapping = { [ONYXKEYS.VERIFY_3DS_SUBSCRIPTION]: string; [ONYXKEYS.PREFERRED_THEME]: ValueOf; [ONYXKEYS.MAPBOX_ACCESS_TOKEN]: OnyxTypes.MapboxAccessToken; - [ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE]: OnyxTypes.DefaultP2PMileageRate; [ONYXKEYS.ONYX_UPDATES_FROM_SERVER]: OnyxTypes.AnyOnyxUpdatesFromServer; [ONYXKEYS.ONYX_UPDATES_LAST_UPDATE_ID_APPLIED_TO_CLIENT]: number; [ONYXKEYS.MAX_CANVAS_AREA]: number; diff --git a/src/components/AddUnreportedExpenseFooter.tsx b/src/components/AddUnreportedExpenseFooter.tsx index f5a4ea0cb319..80ff3519cc02 100644 --- a/src/components/AddUnreportedExpenseFooter.tsx +++ b/src/components/AddUnreportedExpenseFooter.tsx @@ -36,7 +36,7 @@ type AddUnreportedExpenseFooterProps = { }; function AddUnreportedExpenseFooter({selectedIds, report, reportToConfirm, reportNextStep, policy, policyCategories, errorMessage, setErrorMessage}: AddUnreportedExpenseFooterProps) { - const {translate, toLocaleDigit} = useLocalize(); + const {translate} = useLocalize(); const styles = useThemeStyles(); const {isBetaEnabled} = usePermissions(); const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); @@ -93,8 +93,6 @@ function AddUnreportedExpenseFooter({selectedIds, report, reportToConfirm, repor reportNextStep, policyCategories, allTransactions: selectedTransactions, - translate, - toLocaleDigit, }); } }); diff --git a/src/components/DistanceRequest/DistanceRequestFooter.tsx b/src/components/DistanceRequest/DistanceRequestFooter.tsx index 173a8b1f2a7f..0e12867877a0 100644 --- a/src/components/DistanceRequest/DistanceRequestFooter.tsx +++ b/src/components/DistanceRequest/DistanceRequestFooter.tsx @@ -51,14 +51,13 @@ function DistanceRequestFooter({waypoints, transaction, navigateToWaypointEditPa const activePolicy = usePolicy(activePolicyID); const personalPolicy = usePolicy(personalPolicyID); const [mapboxAccessToken] = useOnyx(ONYXKEYS.MAPBOX_ACCESS_TOKEN); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const numberOfWaypoints = Object.keys(waypoints ?? {}).length; const numberOfFilledWaypoints = Object.values(waypoints ?? {}).filter((waypoint) => waypoint?.address).length; const lastWaypointIndex = numberOfWaypoints - 1; const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy ?? activePolicy); const policyCurrency = (policy ?? activePolicy ?? personalPolicy)?.outputCurrency ?? CONST.CURRENCY.USD; - const mileageRate = isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction, defaultP2PMileageRate) : defaultMileageRate; + const mileageRate = isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction) : defaultMileageRate; const {unit} = mileageRate ?? {}; const getMarkerComponent = useCallback( diff --git a/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ios.ts b/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ios.ts index a96031415eb6..8ecafc70f9d2 100644 --- a/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ios.ts +++ b/src/components/GPSTripStateChecker/useUpdateGpsNotification/index.ios.ts @@ -39,12 +39,11 @@ function useUpdateGpsNotificationOnUnitChange() { const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const defaultExpensePolicy = useDefaultExpensePolicy(); const shouldUseDefaultExpensePolicy = shouldUseDefaultExpensePolicyUtil(CONST.IOU.TYPE.CREATE, defaultExpensePolicy, amountOwed, userBillingGracePeriodEnds, ownerBillingGracePeriodEnd); - const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : undefined, defaultP2PMileageRate}).unit; + const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : undefined}).unit; useEffect(() => { if (!shouldUpdateGpsNotificationUnit()) { @@ -52,7 +51,7 @@ function useUpdateGpsNotificationOnUnitChange() { } updateGpsTripNotificationUnit(translate, unit); - }, [unit, translate, defaultP2PMileageRate]); + }, [unit, translate]); } export default useUpdateGpsNotification; diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 1327f557698b..fe2a67ad21b7 100644 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -246,7 +246,6 @@ function MoneyRequestConfirmationList({ selector: mileageRateSelector, }); const [policyCategoriesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES_DRAFT}${policyID}`); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const {getCurrencySymbol, getCurrencyDecimals} = useCurrencyListActions(); const {isBetaEnabled} = usePermissions(); const isNewManualExpenseFlowEnabled = isBetaEnabled(CONST.BETAS.NEW_MANUAL_EXPENSE_FLOW); @@ -323,7 +322,6 @@ function MoneyRequestConfirmationList({ policy, ...(isMovingTransactionFromTrackExpense && {policyForMovingExpenses}), policyDraft, - defaultP2PMileageRate, }); const rate = mileageRate.rate; const prevRate = usePrevious(rate); diff --git a/src/components/MoneyRequestHeaderSecondaryActions.tsx b/src/components/MoneyRequestHeaderSecondaryActions.tsx index 497e325bec77..9e2b0e8cc3f0 100644 --- a/src/components/MoneyRequestHeaderSecondaryActions.tsx +++ b/src/components/MoneyRequestHeaderSecondaryActions.tsx @@ -140,7 +140,6 @@ function MoneyRequestHeaderSecondaryActions({reportID, onBackButtonPress}: Money const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); const [isSelfTourViewed = false] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {selector: hasSeenTourSelector}); const [betas] = useOnyx(ONYXKEYS.BETAS); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); // Custom hooks const defaultExpensePolicy = useDefaultExpensePolicy(); @@ -330,7 +329,7 @@ function MoneyRequestHeaderSecondaryActions({reportID, onBackButtonPress}: Money icon: expensifyIcons.ArrowSplit, value: CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.SPLIT, onSelected: () => { - initSplitExpense(transaction, policy, defaultP2PMileageRate); + initSplitExpense(transaction, policy); }, }, [CONST.REPORT.TRANSACTION_SECONDARY_ACTIONS.MERGE]: { diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 392c79c81249..084143492835 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -338,7 +338,6 @@ function MoneyRequestView({ const [transactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const hasMultipleSplits = useMemo( () => hasMultipleSplitChildren(allTransactions, allReports, transaction?.comment?.originalTransactionID), [allTransactions, allReports, transaction?.comment?.originalTransactionID], @@ -459,7 +458,7 @@ function MoneyRequestView({ let amountDescription = `${translate('iou.amount')}`; let dateDescription = `${translate('common.date')}`; - const {unit, rate, name: rateName} = DistanceRequestUtils.getRate({transaction: updatedTransaction ?? transaction, policy, defaultP2PMileageRate}); + const {unit, rate, name: rateName} = DistanceRequestUtils.getRate({transaction: updatedTransaction ?? transaction, policy}); const distance = getDistanceInMeters(transactionBackup ?? updatedTransaction ?? transaction, unit); const currency = transactionCurrency ?? CONST.CURRENCY.USD; const hasRequiredCompanyCardViolation = transactionViolations.some((violation) => violation.name === CONST.VIOLATIONS.COMPANY_CARD_REQUIRED); @@ -937,7 +936,7 @@ function MoneyRequestView({ } if (shouldShowSplitIndicator && isSplitAvailable) { - initSplitExpense(transaction, policy, defaultP2PMileageRate); + initSplitExpense(transaction, policy); return; } diff --git a/src/hooks/useSearchBulkActions.ts b/src/hooks/useSearchBulkActions.ts index f2a22e52d8b3..5acc43785078 100644 --- a/src/hooks/useSearchBulkActions.ts +++ b/src/hooks/useSearchBulkActions.ts @@ -196,7 +196,7 @@ function shouldShowBulkDuplicateOption({ } function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { - const {translate, toLocaleDigit, localeCompare, formatPhoneNumber} = useLocalize(); + const {translate, localeCompare, formatPhoneNumber} = useLocalize(); const styles = useThemeStyles(); const theme = useTheme(); const {isOffline} = useNetwork(); @@ -221,7 +221,6 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS); const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const [userBillingGracePeriodEnds] = useOnyx(ONYXKEYS.COLLECTION.SHARED_NVP_PRIVATE_USER_BILLING_GRACE_PERIOD_END); const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); const {isBetaEnabled} = usePermissions(); @@ -598,9 +597,6 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { allTransactionViolations, bankAccountList, hash, - defaultP2PMileageRate, - translate, - toLocaleDigit, }); } } else { @@ -618,9 +614,6 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { bankAccountList, transactions, allReportNameValuePairs, - defaultP2PMileageRate, - translate, - toLocaleDigit, }); } clearSelectedTransactions(); @@ -646,7 +639,6 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { isExpenseReportType, selectedReportIDs, allReportNameValuePairs, - defaultP2PMileageRate, ]); const onBulkPaySelected = useCallback( @@ -1348,7 +1340,7 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { icon: expensifyIcons.ArrowSplit, value: CONST.SEARCH.BULK_ACTION_TYPES.SPLIT, onSelected: () => { - initSplitExpense(firstTransaction, firstTransactionPolicy, defaultP2PMileageRate); + initSplitExpense(firstTransaction, firstTransactionPolicy); }, }); } @@ -1453,7 +1445,6 @@ function useSearchBulkActions({queryJSON}: UseSearchBulkActionsParams) { userBillingGracePeriodEnds, ownerBillingGracePeriodEnd, currentSearchKey, - defaultP2PMileageRate, getCurrencyDecimals, amountOwed, allTransactions, diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index 9d647adeb4d7..e57f22ec8988 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -89,7 +89,6 @@ function useSelectedTransactionsActions({ const [integrationsExportTemplates] = useOnyx(ONYXKEYS.NVP_INTEGRATION_SERVER_EXPORT_TEMPLATES); const [csvExportLayouts] = useOnyx(ONYXKEYS.NVP_CSV_EXPORT_LAYOUTS); const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const [allReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS); const {getCurrencyDecimals} = useCurrencyListActions(); @@ -470,7 +469,7 @@ function useSelectedTransactionsActions({ icon: expensifyIcons.ArrowSplit, value: SPLIT, onSelected: () => { - initSplitExpense(firstTransaction, policy, defaultP2PMileageRate); + initSplitExpense(firstTransaction, policy); }, }); } diff --git a/src/hooks/useUndeleteTransactions.ts b/src/hooks/useUndeleteTransactions.ts index 84863074cc00..d132d4ceb1c9 100644 --- a/src/hooks/useUndeleteTransactions.ts +++ b/src/hooks/useUndeleteTransactions.ts @@ -3,7 +3,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Transaction} from '@src/types/onyx'; import useCurrentUserPersonalDetails from './useCurrentUserPersonalDetails'; -import useLocalize from './useLocalize'; import useOnyx from './useOnyx'; import usePermissions from './usePermissions'; @@ -13,7 +12,6 @@ function useUndeleteTransactions() { const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); const [personalPolicyID] = useOnyx(ONYXKEYS.PERSONAL_POLICY_ID); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${personalPolicyID}`); - const {translate, toLocaleDigit} = useLocalize(); return (transactions: Transaction[]) => { const transactionIDs = transactions.map((transaction) => transaction.transactionID); @@ -26,8 +24,6 @@ function useUndeleteTransactions() { email: currentUserPersonalDetails.email ?? '', policy, allTransactions, - translate, - toLocaleDigit, }); }; } diff --git a/src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts b/src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts deleted file mode 100644 index 205b1ebf1ee8..000000000000 --- a/src/libs/API/parameters/GetDefaultP2PMileageRateParams.ts +++ /dev/null @@ -1,3 +0,0 @@ -type GetDefaultP2PMileageRateParams = Record; - -export default GetDefaultP2PMileageRateParams; diff --git a/src/libs/API/parameters/index.ts b/src/libs/API/parameters/index.ts index 91fab408cd77..d29a38b437da 100644 --- a/src/libs/API/parameters/index.ts +++ b/src/libs/API/parameters/index.ts @@ -496,7 +496,6 @@ export type {default as RevokeMultifactorAuthenticationCredentialsParams} from ' export type {default as TroubleshootMultifactorAuthenticationParams} from './TroubleshootMultifactorAuthenticationParams'; export type {default as RequestAuthenticationChallengeParams} from './RequestAuthenticationChallengeParams'; export type {default as GetTransactionsMatchingCodingRuleParams} from './GetTransactionsMatchingCodingRuleParams'; -export type {default as GetDefaultP2PMileageRateParams} from './GetDefaultP2PMileageRateParams'; export type {default as SetPolicyTimeTrackingDefaultRateParams} from './SetPolicyTimeTrackingDefaultRateParams'; export type {default as ToggleTwoFactorAuthRequiredForDomainParams} from './ToggleTwoFactorAuthRequiredForDomainParams'; export type {default as SetReportDetailsColumnsParams} from './SetReportDetailsColumnsParams'; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 73fb722c30bc..f5d7530132b4 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1255,7 +1255,6 @@ const READ_COMMANDS = { GET_SAML_SETTINGS: 'GetSAMLSettings', GET_DUPLICATE_TRANSACTION_DETAILS: 'GetDuplicateTransactionDetails', GET_TRANSACTIONS_MATCHING_CODING_RULE: 'GetTransactionsMatchingCodingRule', - GET_DEFAULT_P2P_MILEAGE_RATE: 'GetDefaultP2PMileageRate', GET_ASSIGNED_SUPPORT_DATA: 'GetAssignedSupportData', } as const; @@ -1349,7 +1348,6 @@ type ReadCommandParameters = { [READ_COMMANDS.OPEN_DOMAIN_INITIAL_PAGE]: Parameters.DomainParams; [READ_COMMANDS.GET_DUPLICATE_TRANSACTION_DETAILS]: Parameters.GetDuplicateTransactionDetailsParams; [READ_COMMANDS.GET_TRANSACTIONS_MATCHING_CODING_RULE]: Parameters.GetTransactionsMatchingCodingRuleParams; - [READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE]: Parameters.GetDefaultP2PMileageRateParams; [READ_COMMANDS.GET_ASSIGNED_SUPPORT_DATA]: null; }; @@ -1393,6 +1391,7 @@ const SIDE_EFFECT_REQUEST_COMMANDS = { LINK_CARD_FEED_TO_POLICY: 'LinkCardFeedToPolicy', REVEAL_CARD_PIN: 'RevealCardPIN', CHANGE_CARD_PIN: 'ChangeCardPIN', + GET_DEFAULT_P2P_MILEAGE_RATE: 'GetDefaultP2PMileageRate', } as const; type SideEffectRequestCommand = ValueOf; @@ -1432,6 +1431,7 @@ type SideEffectRequestCommandParameters = { [SIDE_EFFECT_REQUEST_COMMANDS.LINK_CARD_FEED_TO_POLICY]: Parameters.LinkCardToPolicyParams; [SIDE_EFFECT_REQUEST_COMMANDS.REVEAL_CARD_PIN]: Parameters.RevealCardPINParams; [SIDE_EFFECT_REQUEST_COMMANDS.CHANGE_CARD_PIN]: Parameters.ChangeCardPINParams; + [SIDE_EFFECT_REQUEST_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE]: null; }; type ApiRequestCommandParameters = WriteCommandParameters & ReadCommandParameters & SideEffectRequestCommandParameters; diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index e64d52b05dd8..50912c335935 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -2,10 +2,11 @@ import type {OnyxEntry} from 'react-native-onyx'; import type {CurrencyListActionsContextType} from '@components/CurrencyListContextProvider'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; -import type {DefaultP2PMileageRate, LastSelectedDistanceRates, OnyxInputOrEntry, Transaction} from '@src/types/onyx'; +import type {LastSelectedDistanceRates, OnyxInputOrEntry, Transaction} from '@src/types/onyx'; import type {Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; +import {getStoredDefaultP2PMileageRate} from './actions/Transaction'; import {replaceAllDigits} from './MoneyRequestUtils'; // This will be fixed as part of https://github.com/Expensify/App/issues/66397 // eslint-disable-next-line @typescript-eslint/no-deprecated @@ -268,14 +269,21 @@ function getDistanceMerchant( } /** - * Retrieves the rate and unit for a P2P distance expense. + * Retrieves the rate and unit for a P2P distance expense for a given currency. + * + * Let's ensure this logic is consistent with the logic in the backend (Auth), since we're using the same method to calculate the rate value in distance requests created via Concierge. + * + * @param currency + * @returns The rate and unit in MileageRate object. */ -function getRateForP2P(currency: string, transaction: OnyxEntry, defaultP2PMileageRate?: DefaultP2PMileageRate | null): MileageRate { - const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? defaultP2PMileageRate?.rate) : defaultP2PMileageRate?.rate; +function getRateForP2P(currency: string, transaction: OnyxEntry): MileageRate { + const defaultRate = getStoredDefaultP2PMileageRate(); + const p2pRate = defaultRate ?? {rate: 6700, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}; + const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? p2pRate.rate) : p2pRate.rate; return { - unit: defaultP2PMileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, - currency, rate, + unit: p2pRate.unit as Unit, + currency: defaultRate ? currency : CONST.CURRENCY.USD, }; } @@ -390,7 +398,6 @@ function getRate({ policy, policyDraft, useTransactionDistanceUnit = true, - defaultP2PMileageRate, policyForMovingExpenses, isFakeP2PRate, }: { @@ -399,7 +406,6 @@ function getRate({ policyDraft?: OnyxEntry; policyForMovingExpenses?: OnyxEntry; useTransactionDistanceUnit?: boolean; - defaultP2PMileageRate?: DefaultP2PMileageRate | null; isFakeP2PRate?: boolean; }): MileageRate { let mileageRates = getMileageRates(policy, true, transaction?.comment?.customUnit?.customUnitRateID); @@ -415,7 +421,7 @@ function getRate({ const customUnitRateID = getRateID(transaction); const customMileageRate = (customUnitRateID && (mileageRates?.[customUnitRateID] ?? mileageRatesForMovingExpenses?.[customUnitRateID])) || (isUnreportedExpense ? undefined : defaultMileageRate); - const mileageRate = isCustomUnitRateIDForP2P(transaction) || isFakeP2PRate ? getRateForP2P(policyCurrency, transaction, defaultP2PMileageRate) : customMileageRate; + const mileageRate = isCustomUnitRateIDForP2P(transaction) || isFakeP2PRate ? getRateForP2P(policyCurrency, transaction) : customMileageRate; const unit = getDistanceUnit(useTransactionDistanceUnit ? transaction : undefined, mileageRate); return { ...mileageRate, @@ -431,18 +437,8 @@ function getRate({ * For example, if an expense is '10 mi @ $1.00 / mi' and the rate is updated to '$1.00 / km', * then the updated distance unit should be 'km' from the updated rate, not 'mi' from the currently stored transaction distance unit. */ -function getUpdatedDistanceUnit({ - transaction, - policy, - policyDraft, - defaultP2PMileageRate, -}: { - transaction: OnyxEntry; - policy: OnyxEntry; - policyDraft?: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate | null; -}) { - return getRate({transaction, policy, policyDraft, useTransactionDistanceUnit: false, defaultP2PMileageRate}).unit; +function getUpdatedDistanceUnit({transaction, policy, policyDraft}: {transaction: OnyxEntry; policy: OnyxEntry; policyDraft?: OnyxEntry}) { + return getRate({transaction, policy, policyDraft, useTransactionDistanceUnit: false}).unit; } /** diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index ad6bd8e18200..6a994ee91cba 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -5,7 +5,6 @@ import lodashSet from 'lodash/set'; import type {NullishDeep, OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; -import type {LocaleContextProps} from '@components/LocaleContextProvider'; import type {Coordinate} from '@components/MapView/MapViewTypes'; import utils from '@components/MapView/utils'; import type {UnreportedExpenseListItemType} from '@components/Search/SearchList/ListItem/types'; @@ -56,7 +55,6 @@ import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; import type { Card, - DefaultP2PMileageRate, OnyxInputOrEntry, Policy, PolicyCategories, @@ -674,7 +672,6 @@ function getUpdatedTransaction({ shouldUpdateReceiptState = true, policy = undefined, isSplitTransaction = false, - defaultP2PMileageRate = undefined, }: { transaction: Transaction; transactionChanges: TransactionChanges; @@ -682,7 +679,6 @@ function getUpdatedTransaction({ shouldUpdateReceiptState?: boolean; policy?: OnyxEntry; isSplitTransaction?: boolean; - defaultP2PMileageRate?: DefaultP2PMileageRate | null; }): Transaction { const isUnReportedExpense = transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID; @@ -730,7 +726,7 @@ function getUpdatedTransaction({ // eslint-disable-next-line @typescript-eslint/no-deprecated updatedTransaction.modifiedMerchant = translateLocal('iou.fieldPending'); } else { - const mileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy, defaultP2PMileageRate}); + const mileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy}); const {unit, rate} = mileageRate; const distanceInMeters = getDistanceInMeters(transaction, unit); @@ -771,7 +767,7 @@ function getUpdatedTransaction({ const existingDistanceUnit = transaction?.comment?.customUnit?.distanceUnit; // Get the new distance unit from the rate's unit - const newDistanceUnit = DistanceRequestUtils.getUpdatedDistanceUnit({transaction: updatedTransaction, policy, defaultP2PMileageRate}); + const newDistanceUnit = DistanceRequestUtils.getUpdatedDistanceUnit({transaction: updatedTransaction, policy}); lodashSet(updatedTransaction, 'comment.customUnit.distanceUnit', newDistanceUnit); // If the distanceUnit is set and the rate is changed to one that has a different unit, convert the distance to the new unit. @@ -786,12 +782,7 @@ function getUpdatedTransaction({ // When the waypoints are being fetched from the server, we have no information about the distance, and cannot recalculate the updated amount. // Otherwise, recalculate the fields based on the new rate. - const updatedMileageRate = DistanceRequestUtils.getRate({ - transaction: updatedTransaction, - policy, - useTransactionDistanceUnit: false, - defaultP2PMileageRate, - }); + const updatedMileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy, useTransactionDistanceUnit: false}); const {unit, rate} = updatedMileageRate; const distanceInMeters = getDistanceInMeters(updatedTransaction, unit); @@ -876,12 +867,7 @@ function getUpdatedTransaction({ lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance); shouldStopSmartscan = true; - const updatedMileageRate = DistanceRequestUtils.getRate({ - transaction: updatedTransaction, - policy, - useTransactionDistanceUnit: false, - defaultP2PMileageRate, - }); + const updatedMileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy, useTransactionDistanceUnit: false}); const {unit, rate} = updatedMileageRate; const distanceInMeters = getDistanceInMeters(updatedTransaction, unit); @@ -2806,54 +2792,14 @@ function shouldReuseInitialTransaction( /** * A utility that ensures unreported transactions are unheld. - * For distance expenses, it also updates the `customUnit` and recalculates the transaction's `amount`, `merchant`, and `currency`. */ -function recalculateUnreportedTransactionDetails( - transaction?: OnyxEntry, - destinationCurrency?: string | undefined, - translateFn?: LocaleContextProps['translate'], - toLocaleDigitFn?: LocaleContextProps['toLocaleDigit'], - defaultP2PMileageRate?: DefaultP2PMileageRate | null, -) { +function recalculateUnreportedTransactionDetails() { + // If the transaction is on hold, we need to unhold it because unreported transactions (on selfDM) should never remain on hold. const comment: NullishDeep = { hold: null, }; - if (!transaction || !isDistanceRequest(transaction) || !translateFn || !toLocaleDigitFn) { - return {comment}; - } - - // For distance requests we need to update its custom unit ID to `_FAKE_P2P_ID_` so it's no longer tied to the policy's rate which would cause the "Rate out of policy" violation to appear. - // Let's also set the defaultP2PRate and update the distanceUnit, the quantity, the amount, the currency and the merchant to match the P2P rate. - const rate = defaultP2PMileageRate?.rate; - const unit = defaultP2PMileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; - const currency = destinationCurrency ?? CONST.CURRENCY.USD; - const distance = parseFloat( - DistanceRequestUtils.getRoundedDistanceInUnits(getDistanceInMeters(transaction, transaction?.comment?.customUnit?.distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES), unit), - ); - const distanceInMeters = DistanceRequestUtils.convertToDistanceInMeters(distance, unit); - comment.customUnit = { - customUnitID: CONST.CUSTOM_UNITS.FAKE_P2P_ID, - customUnitRateID: CONST.CUSTOM_UNITS.FAKE_P2P_ID, - defaultP2PRate: rate, - distanceUnit: unit, - quantity: distance, - }; - const modifiedAmount = -DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, rate ?? 0); - const modifiedCurrency = currency; - const modifiedMerchant = DistanceRequestUtils.getDistanceMerchant( - true, - distanceInMeters, - unit, - rate, - currency, - translateFn, - toLocaleDigitFn, - getCurrencySymbol, - isManualDistanceRequest(transaction), - ); - - return {comment, modifiedAmount, modifiedCurrency, modifiedMerchant}; + return {comment}; } /** diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index 82a90babd548..d1a489285362 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -28,6 +28,7 @@ import type {OnyxData} from '@src/types/onyx/Request'; import {setShouldForceOffline} from './Network'; import {getAll, rollbackOngoingRequest, save} from './PersistedRequests'; import {createDraftInitialWorkspace, createWorkspace, generatePolicyID, newGenerateDefaultWorkspaceName} from './Policy/Policy'; +import {getDefaultP2PMileageRate} from './Transaction'; type PolicyParamsForOpenOrReconnect = { policyIDList: string[]; @@ -441,6 +442,8 @@ function openApp(shouldKeepPublicRooms = false, allReportsWithDraftComments?: Re }); } + getDefaultP2PMileageRate(); + return getPolicyParamsForOpenOrReconnect() .then((policyParams: PolicyParamsForOpenOrReconnect) => { const params: OpenAppParams = {enablePriorityModeFilter: true, ...policyParams}; diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index b8824ee80942..d46ce210acb2 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -35,7 +35,6 @@ import type { Transaction, TransactionViolation, } from '@src/types/onyx'; -import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {ReportAttributes, ReportAttributesDerivedValue} from '@src/types/onyx/DerivedValues'; import type {Participant} from '@src/types/onyx/IOU'; import type {Unit} from '@src/types/onyx/Policy'; @@ -177,7 +176,6 @@ type MoneyRequestStepDistanceNavigationParams = { amountOwed: OnyxEntry; userBillingGracePeriodEnds: OnyxCollection; ownerBillingGracePeriodEnd?: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; conciergeReportID: string | undefined; }; @@ -610,7 +608,6 @@ function handleMoneyRequestStepDistanceNavigation({ amountOwed, userBillingGracePeriodEnds, ownerBillingGracePeriodEnd, - defaultP2PMileageRate, conciergeReportID, }: MoneyRequestStepDistanceNavigationParams) { const isManualDistance = manualDistance !== undefined; @@ -662,12 +659,7 @@ function handleMoneyRequestStepDistanceNavigation({ let merchant = translate('iou.fieldPending'); if (isManualDistance && distance !== undefined && unit) { const distanceInMeters = DistanceRequestUtils.convertToDistanceInMeters(distance, unit); - const mileageRate = DistanceRequestUtils.getRate({ - transaction, - policy, - policyForMovingExpenses, - defaultP2PMileageRate, - }); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy}); amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, mileageRate?.rate ?? 0); merchant = DistanceRequestUtils.getDistanceMerchant( true, @@ -808,7 +800,7 @@ function handleMoneyRequestStepDistanceNavigation({ ? DistanceRequestUtils.getRateByCustomUnitRateID({customUnitRateID: rateID, policy: policyForMovingExpenses}) : undefined; const currency = ratePolicyForMovingExpenses?.currency ?? personalOutputCurrency ?? CONST.CURRENCY.USD; - const distanceUnit = ratePolicyForMovingExpenses?.unit ?? DistanceRequestUtils.getRateForP2P(currency, transaction, defaultP2PMileageRate).unit; + const distanceUnit = ratePolicyForMovingExpenses?.unit ?? DistanceRequestUtils.getRateForP2P(currency, transaction).unit; const distanceInMeters = DistanceRequestUtils.convertToDistanceInMeters(distance, unit); const distanceInDistanceUnit = roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, distanceUnit)); setMoneyRequestDistance(transactionID, distanceInDistanceUnit, true, distanceUnit); diff --git a/src/libs/actions/IOU/Split.ts b/src/libs/actions/IOU/Split.ts index e969d8ee9f0f..ebd29e85a51f 100644 --- a/src/libs/actions/IOU/Split.ts +++ b/src/libs/actions/IOU/Split.ts @@ -82,7 +82,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; -import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Attendee, Participant, Split, SplitExpense} from '@src/types/onyx/IOU'; import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails'; import type {Unit} from '@src/types/onyx/Policy'; @@ -2368,7 +2367,6 @@ function setDraftSplitTransaction( splitTransactionDraft: OnyxEntry, transactionChanges: TransactionChanges = {}, policy?: OnyxEntry, - defaultP2PMileageRate?: DefaultP2PMileageRate, ) { if (!transactionID) { return undefined; @@ -2387,7 +2385,6 @@ function setDraftSplitTransaction( shouldUpdateReceiptState: false, policy, isSplitTransaction: true, - defaultP2PMileageRate, }) : null; @@ -2619,7 +2616,6 @@ function addSplitExpenseField( draftTransaction: OnyxEntry, transactionReport: OnyxEntry, policy?: OnyxEntry, - defaultP2PMileageRate?: DefaultP2PMileageRate, ) { if (!transaction || !draftTransaction) { return; @@ -2640,7 +2636,7 @@ function addSplitExpenseField( } : undefined; - const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined}); const {unit, rate} = mileageRate; if (rate && rate > 0 && customUnit) { @@ -2697,12 +2693,7 @@ function addSplitExpenseField( * - Works entirely on the provided `draftTransaction` to avoid direct Onyx reads. * - Uses `calculateAmount` utility to handle currency subunits and rounding consistently with existing logic. */ -function evenlyDistributeSplitExpenseAmounts( - draftTransaction: OnyxEntry, - transaction?: OnyxEntry, - policy?: OnyxEntry, - defaultP2PMileageRate?: DefaultP2PMileageRate, -) { +function evenlyDistributeSplitExpenseAmounts(draftTransaction: OnyxEntry, transaction?: OnyxEntry, policy?: OnyxEntry) { if (!draftTransaction) { return; } @@ -2725,7 +2716,7 @@ function evenlyDistributeSplitExpenseAmounts( const splitCount = splitExpenses.length; const lastIndex = splitCount - 1; - const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined}); const {unit, rate} = mileageRate; const updatedSplitExpenses = splitExpenses.map((splitExpense, index) => { @@ -2775,7 +2766,6 @@ function resetSplitExpensesByDateRange( startDate: string, endDate: string, policy?: OnyxEntry, - defaultP2PMileageRate?: DefaultP2PMileageRate, ) { if (!transaction || !startDate || !endDate) { return; @@ -2793,7 +2783,7 @@ function resetSplitExpensesByDateRange( const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); - const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined}); const {unit, rate} = mileageRate; // Create split expenses for each date with proportional amounts @@ -2868,7 +2858,6 @@ function updateSplitExpenseField( splitExpenseTransactionID: string, originalTransaction: OnyxEntry, policy?: OnyxEntry, - defaultP2PMileageRate?: DefaultP2PMileageRate, ) { if (!splitExpenseDraftTransaction || !splitExpenseTransactionID || !originalTransactionDraft) { return; @@ -2911,11 +2900,7 @@ function updateSplitExpenseField( // Recalculate amount for distance transactions when rate or distance changes if (isDistanceRequest && originalTransaction) { - const mileageRate = DistanceRequestUtils.getRate({ - transaction: splitExpenseDraftTransaction, - policy: policy ?? undefined, - defaultP2PMileageRate, - }); + const mileageRate = DistanceRequestUtils.getRate({transaction: splitExpenseDraftTransaction, policy: policy ?? undefined}); const {unit, rate} = mileageRate; if (rate && rate > 0) { @@ -2959,13 +2944,7 @@ function updateSplitExpenseField( }); } -function updateSplitExpenseAmountField( - draftTransaction: OnyxEntry, - currentItemTransactionID: string, - amount: number, - policy?: OnyxEntry, - defaultP2PMileageRate?: DefaultP2PMileageRate, -) { +function updateSplitExpenseAmountField(draftTransaction: OnyxEntry, currentItemTransactionID: string, amount: number, policy?: OnyxEntry) { if (!draftTransaction?.transactionID || !currentItemTransactionID || Number.isNaN(amount)) { return; } @@ -2988,7 +2967,7 @@ function updateSplitExpenseAmountField( // Update distance for distance transactions based on new amount and rate if (isDistanceRequest && originalTransaction && splitExpense.customUnit) { - const mileageRate = DistanceRequestUtils.getRate({transaction: originalTransaction, policy: policy ?? undefined, defaultP2PMileageRate}); + const mileageRate = DistanceRequestUtils.getRate({transaction: originalTransaction, policy: policy ?? undefined}); const {rate: currentRate = 0} = DistanceRequestUtils.getRateByCustomUnitRateID({policy, customUnitRateID: splitExpense.customUnit?.customUnitRateID ?? String(CONST.DEFAULT_NUMBER_ID)}) ?? {}; const {unit, rate: mileageRateValue} = mileageRate; diff --git a/src/libs/actions/IOU/UpdateMoneyRequest.ts b/src/libs/actions/IOU/UpdateMoneyRequest.ts index 6d3eff6004d4..5ebc61c00b8a 100644 --- a/src/libs/actions/IOU/UpdateMoneyRequest.ts +++ b/src/libs/actions/IOU/UpdateMoneyRequest.ts @@ -41,7 +41,6 @@ import {stringifyWaypointsForAPI} from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type * as OnyxTypes from '@src/types/onyx'; -import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Attendee} from '@src/types/onyx/IOU'; import type RecentlyUsedTags from '@src/types/onyx/RecentlyUsedTags'; import type {Routes, TransactionChanges, WaypointCollection} from '@src/types/onyx/Transaction'; @@ -483,7 +482,6 @@ type UpdateMoneyRequestDistanceParams = { odometerStart?: number; odometerEnd?: number; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }; /** Updates the waypoints of a distance expense */ @@ -505,7 +503,6 @@ function updateMoneyRequestDistance({ odometerStart, odometerEnd, parentReportNextStep, - defaultP2PMileageRate, }: UpdateMoneyRequestDistanceParams) { const transactionChanges: TransactionChanges = { // Don't sanitize waypoints here - keep all fields for Onyx optimistic data (e.g., keyForList) @@ -519,7 +516,7 @@ function updateMoneyRequestDistance({ let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { - data = getUpdateTrackExpenseParams(transaction?.transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); + data = getUpdateTrackExpenseParams(transaction?.transactionID, transactionThreadReport?.reportID, transactionChanges, policy); } else { data = getUpdateMoneyRequestParams({ transactionID: transaction?.transactionID, @@ -533,7 +530,6 @@ function updateMoneyRequestDistance({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); } const {params, onyxData} = data; @@ -710,7 +706,6 @@ function updateMoneyRequestDistanceRate({ updatedTaxCode, updatedTaxValue, parentReportNextStep, - defaultP2PMileageRate, }: { transaction: OnyxEntry; transactionThreadReport: OnyxEntry; @@ -726,7 +721,6 @@ function updateMoneyRequestDistanceRate({ updatedTaxCode?: string; updatedTaxValue?: string; parentReportNextStep: OnyxEntry; - defaultP2PMileageRate?: DefaultP2PMileageRate; }) { const transactionChanges: TransactionChanges = { customUnitRateID: rateID, @@ -751,7 +745,7 @@ function updateMoneyRequestDistanceRate({ let data: UpdateMoneyRequestData; // eslint-disable-next-line @typescript-eslint/no-deprecated if (isTrackExpenseReport(transactionThreadReport) && isSelfDM(parentReport)) { - data = getUpdateTrackExpenseParams(transaction?.transactionID, transactionThreadReport?.reportID, transactionChanges, policy, true, defaultP2PMileageRate); + data = getUpdateTrackExpenseParams(transaction?.transactionID, transactionThreadReport?.reportID, transactionChanges, policy); } else { data = getUpdateMoneyRequestParams({ transactionID: transaction?.transactionID, @@ -765,7 +759,6 @@ function updateMoneyRequestDistanceRate({ currentUserEmailParam, isASAPSubmitBetaEnabled, iouReportNextStep: parentReportNextStep, - defaultP2PMileageRate, }); } const {params, onyxData} = data; @@ -873,7 +866,6 @@ type GetUpdateMoneyRequestParamsType = { policyRecentlyUsedCurrencies?: string[]; iouReportNextStep: OnyxEntry; isSplitTransaction?: boolean; - defaultP2PMileageRate?: DefaultP2PMileageRate; }; type UpdateMoneyRequestDataKeys = @@ -911,7 +903,6 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U policyRecentlyUsedCurrencies, iouReportNextStep, isSplitTransaction, - defaultP2PMileageRate, } = params; const optimisticData: Array< OnyxUpdate< @@ -958,7 +949,6 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U isFromExpenseReport, isSplitTransaction, policy, - defaultP2PMileageRate, }) : undefined; @@ -1456,7 +1446,6 @@ function getUpdateTrackExpenseParams( transactionChanges: TransactionChanges, policy: OnyxEntry, shouldBuildOptimisticModifiedExpenseReportAction = true, - defaultP2PMileageRate?: DefaultP2PMileageRate, ): UpdateMoneyRequestData< typeof ONYXKEYS.COLLECTION.REPORT_ACTIONS | typeof ONYXKEYS.COLLECTION.TRANSACTION | typeof ONYXKEYS.COLLECTION.REPORT | typeof ONYXKEYS.COLLECTION.TRANSACTION_DRAFT > { @@ -1479,7 +1468,6 @@ function getUpdateTrackExpenseParams( transactionChanges, isFromExpenseReport: false, policy, - defaultP2PMileageRate, }) : null; const transactionDetails = getTransactionDetails(updatedTransaction); diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index d7055e9bda27..0f25e9d1fcbb 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -93,7 +93,7 @@ import { import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import {buildOptimisticPolicyRecentlyUsedTags} from '@userActions/Policy/Tag'; import {notifyNewAction} from '@userActions/Report'; -import {getDefaultP2PMileageRate, mergeTransactionIdsHighlightOnSearchRoute, sanitizeWaypointsForAPI} from '@userActions/Transaction'; +import {mergeTransactionIdsHighlightOnSearchRoute, sanitizeWaypointsForAPI} from '@userActions/Transaction'; import {getRemoveDraftTransactionsByIDsData, removeDraftTransaction, removeDraftTransactionsByIDs} from '@userActions/TransactionEdit'; import type {IOUAction, IOUActionParams, OdometerImageType} from '@src/CONST'; import CONST from '@src/CONST'; @@ -102,7 +102,6 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; -import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Accountant, Attendee, Participant, Split} from '@src/types/onyx/IOU'; import type {ErrorFields, Errors, PendingAction, PendingFields} from '@src/types/onyx/OnyxCommon'; import type {CurrentUserPersonalDetails} from '@src/types/onyx/PersonalDetails'; @@ -894,8 +893,6 @@ function initMoneyRequest({ comment.odometerStartImage = undefined; comment.odometerEndImage = undefined; } - - getDefaultP2PMileageRate(); } if (newIouRequestType === CONST.IOU.REQUEST_TYPE.PER_DIEM) { @@ -1138,13 +1135,7 @@ function setMoneyRequestReportID(transactionID: string, reportID: string) { * if passed transaction previously had it to make sure that transaction does not have inconsistent * states (for example distanceUnit not matching distance unit of the new customUnitRateID) */ -function setCustomUnitRateID( - transactionID: string, - customUnitRateID: string | undefined, - transaction: OnyxEntry, - policy: OnyxEntry, - defaultP2PMileageRate?: DefaultP2PMileageRate, -) { +function setCustomUnitRateID(transactionID: string, customUnitRateID: string | undefined, transaction: OnyxEntry, policy: OnyxEntry) { const isFakeP2PRate = customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID; let newDistanceUnit: Unit | undefined; @@ -1152,7 +1143,7 @@ function setCustomUnitRateID( if (customUnitRateID && transaction) { const distanceRate = isFakeP2PRate - ? DistanceRequestUtils.getRate({transaction, useTransactionDistanceUnit: false, policy, defaultP2PMileageRate, isFakeP2PRate}) + ? DistanceRequestUtils.getRate({transaction: undefined, policy: undefined, useTransactionDistanceUnit: false, isFakeP2PRate}) : DistanceRequestUtils.getRateByCustomUnitRateID({policy, customUnitRateID}); const transactionDistanceUnit = transaction.comment?.customUnit?.distanceUnit; diff --git a/src/libs/actions/Report/index.ts b/src/libs/actions/Report/index.ts index 5827165650fc..1103ab39a411 100644 --- a/src/libs/actions/Report/index.ts +++ b/src/libs/actions/Report/index.ts @@ -223,7 +223,6 @@ import type { TransactionViolations, VisibleReportActionsDerivedValue, } from '@src/types/onyx'; -import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Decision} from '@src/types/onyx/OriginalMessage'; import type {CurrentUserPersonalDetails, Timezone} from '@src/types/onyx/PersonalDetails'; import type {ConnectionName} from '@src/types/onyx/Policy'; @@ -5663,9 +5662,6 @@ type DeleteAppReportProps = { allTransactionViolations: OnyxCollection; bankAccountList: OnyxEntry; hash?: number; - defaultP2PMileageRate?: DefaultP2PMileageRate; - translate?: LocaleContextProps['translate']; - toLocaleDigit?: LocaleContextProps['toLocaleDigit']; }; /** Deletes a report and un-reports all transactions on the report along with its reportActions, any linked reports and any linked IOU report actions. */ @@ -5678,9 +5674,6 @@ function deleteAppReport({ allTransactionViolations, bankAccountList, hash, - defaultP2PMileageRate, - translate, - toLocaleDigit, }: DeleteAppReportProps) { if (!report?.reportID) { Log.warn('[Report] deleteAppReport called with no reportID'); @@ -5811,25 +5804,13 @@ function deleteAppReport({ const transaction = reportTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; const transactionViolations = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`] ?? []; - const {comment, modifiedAmount, modifiedCurrency, modifiedMerchant} = recalculateUnreportedTransactionDetails( - transaction, - undefined, - translate, - toLocaleDigit, - defaultP2PMileageRate, - ); + const {comment} = recalculateUnreportedTransactionDetails(); optimisticData.push( { onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, - value: { - reportID: CONST.REPORT.UNREPORTED_REPORT_ID, - comment, - ...(modifiedAmount !== undefined && {modifiedAmount}), - ...(modifiedCurrency !== undefined && {modifiedCurrency}), - ...(modifiedMerchant !== undefined && {modifiedMerchant}), - }, + value: {reportID: CONST.REPORT.UNREPORTED_REPORT_ID, comment}, }, { onyxMethod: Onyx.METHOD.MERGE, @@ -5845,9 +5826,6 @@ function deleteAppReport({ value: { reportID: transaction?.reportID, comment: transaction?.comment, - modifiedAmount: transaction?.modifiedAmount, - modifiedCurrency: transaction?.modifiedCurrency, - modifiedMerchant: transaction?.modifiedMerchant, }, }, { diff --git a/src/libs/actions/Search.ts b/src/libs/actions/Search.ts index fb98df4ee8cc..2cfb5ff4223d 100644 --- a/src/libs/actions/Search.ts +++ b/src/libs/actions/Search.ts @@ -5,7 +5,7 @@ import type {OnyxCollection, OnyxEntry, OnyxUpdate} from 'react-native-onyx'; import type {TupleToUnion, ValueOf} from 'type-fest'; import type {FormOnyxValues} from '@components/Form/types'; import type {ContinueActionParams, PaymentMethod, PaymentMethodType} from '@components/KYCWall/types'; -import type {LocaleContextProps, LocalizedTranslate} from '@components/LocaleContextProvider'; +import type {LocalizedTranslate} from '@components/LocaleContextProvider'; import type {PopoverMenuItem} from '@components/PopoverMenu'; import type {TransactionListItemType, TransactionReportGroupListItemType} from '@components/Search/SearchList/ListItem/types'; import type {BankAccountMenuItem, BulkPaySelectionData, PaymentData, SearchQueryJSON, SelectedReports, SelectedTransactionInfo, SelectedTransactions} from '@components/Search/types'; @@ -71,7 +71,6 @@ import type { Transaction, TransactionViolations, } from '@src/types/onyx'; -import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {PaymentInformation} from '@src/types/onyx/LastPaymentMethod'; import type {ConnectionName} from '@src/types/onyx/Policy'; import type {OnyxData} from '@src/types/onyx/Request'; @@ -129,9 +128,6 @@ type BulkDeleteReportsParams = { bankAccountList: OnyxEntry; transactions?: OnyxCollection; allReportNameValuePairs: OnyxCollection; - defaultP2PMileageRate?: DefaultP2PMileageRate; - translate?: LocaleContextProps['translate']; - toLocaleDigit?: LocaleContextProps['toLocaleDigit']; }; function handleActionButtonPress({ @@ -916,9 +912,6 @@ function bulkDeleteReports({ bankAccountList, transactions, allReportNameValuePairs, - defaultP2PMileageRate, - translate, - toLocaleDigit, }: BulkDeleteReportsParams) { const transactionIDList: string[] = []; const reportIDList: string[] = []; @@ -992,9 +985,6 @@ function bulkDeleteReports({ reportTransactions, allTransactionViolations: transactionsViolations, bankAccountList, - defaultP2PMileageRate, - translate, - toLocaleDigit, }); } } diff --git a/src/libs/actions/SplitExpenses.ts b/src/libs/actions/SplitExpenses.ts index ffdf58814fb4..d641caa0064c 100644 --- a/src/libs/actions/SplitExpenses.ts +++ b/src/libs/actions/SplitExpenses.ts @@ -11,7 +11,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import type {Policy, Report, Transaction} from '@src/types/onyx'; -import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Attendee} from '@src/types/onyx/IOU'; import type {TransactionCustomUnit} from '@src/types/onyx/Transaction'; import {initSplitExpenseItemData, updateSplitExpenseDistanceFromAmount} from './IOU/Split'; @@ -39,7 +38,7 @@ Onyx.connectWithoutView({ /** * Create a draft transaction to set up split expense details for the split expense flow */ -function initSplitExpense(transaction: OnyxEntry, policy?: OnyxEntry, defaultP2PMileageRate?: DefaultP2PMileageRate): void { +function initSplitExpense(transaction: OnyxEntry, policy?: OnyxEntry): void { if (!transaction) { return; } @@ -95,7 +94,7 @@ function initSplitExpense(transaction: OnyxEntry, policy?: OnyxEntr const splitMerchants: Array = [undefined, undefined]; if (isDistanceRequest(transaction)) { - const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined, defaultP2PMileageRate}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy: policy ?? undefined}); const {unit, rate} = mileageRate; if (rate && rate > 0 && transaction?.comment?.customUnit) { diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 3033f96a8d19..b6f8e05d8f98 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -2,7 +2,6 @@ import {getUnixTime} from 'date-fns'; import lodashClone from 'lodash/clone'; import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxKey, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; -import type {LocaleContextProps} from '@components/LocaleContextProvider'; import * as API from '@libs/API'; import type { ChangeTransactionsReportParams, @@ -12,7 +11,7 @@ import type { MarkAsCashParams, TransactionThreadInfo, } from '@libs/API/parameters'; -import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; +import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as CollectionUtils from '@libs/CollectionUtils'; import DateUtils from '@libs/DateUtils'; import {buildNextStepNew, buildOptimisticNextStep} from '@libs/NextStepUtils'; @@ -834,10 +833,6 @@ function openDraftDistanceExpense() { API.read(READ_COMMANDS.OPEN_DRAFT_DISTANCE_EXPENSE, null, onyxData); } -function getDefaultP2PMileageRate() { - API.read(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, {}); -} - /** * Returns a client generated 16 character hexadecimal value for the transactionID */ @@ -859,9 +854,6 @@ type ChangeTransactionsReportProps = { reportNextStep?: OnyxEntry; policyCategories?: OnyxEntry; allTransactions: OnyxCollection; - translate: LocaleContextProps['translate']; - toLocaleDigit: LocaleContextProps['toLocaleDigit']; - defaultP2PMileageRate?: DefaultP2PMileageRate; }; function changeTransactionsReport({ @@ -874,9 +866,6 @@ function changeTransactionsReport({ reportNextStep, policyCategories, allTransactions, - translate, - toLocaleDigit, - defaultP2PMileageRate, }: ChangeTransactionsReportProps) { const reportID = newReport?.reportID ?? CONST.REPORT.UNREPORTED_REPORT_ID; @@ -1063,9 +1052,7 @@ function changeTransactionsReport({ }), }; - const {comment, modifiedAmount, modifiedCurrency, modifiedMerchant} = isUnreported - ? recalculateUnreportedTransactionDetails(transaction, destinationCurrency, translate, toLocaleDigit, defaultP2PMileageRate) - : {}; + const {comment} = isUnreported ? recalculateUnreportedTransactionDetails() : {}; // 1. Optimistically update the transaction with full data and changed fields. // Spreading the full transaction ensures the TRANSACTION collection has complete data @@ -1077,9 +1064,6 @@ function changeTransactionsReport({ ...transaction, reportID, comment, - ...(modifiedAmount !== undefined && {modifiedAmount}), - ...(modifiedCurrency !== undefined && {modifiedCurrency}), - ...(modifiedMerchant !== undefined && {modifiedMerchant}), ...(shouldClearAmount && {pendingAction: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(shouldClearAmount && {convertedAmount: null}), ...(oldIOUAction ? {linkedTrackedExpenseReportAction: newIOUAction} : {}), @@ -1101,9 +1085,6 @@ function changeTransactionsReport({ value: { reportID: transaction.reportID, comment: transaction.comment, - modifiedAmount: transaction.modifiedAmount, - modifiedCurrency: transaction.modifiedCurrency, - modifiedMerchant: transaction.modifiedMerchant, ...(shouldClearAmount && {pendingAction: transaction.pendingAction ?? null}), ...(shouldClearAmount && {convertedAmount: transaction.convertedAmount}), }, @@ -1675,6 +1656,24 @@ function changeTransactionsReport({ }); } +let storedDefaultP2PMileageRate: DefaultP2PMileageRate | undefined; + +function getDefaultP2PMileageRate() { + // eslint-disable-next-line rulesdir/no-api-side-effects-method + API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, null).then((response) => { + const updates = response?.onyxData as Array<{key: string; value: unknown}> | undefined; + const rateUpdate = updates?.find((update) => update.key === 'defaultP2PMileageRate'); + const value = rateUpdate?.value; + if (value && typeof value === 'object' && 'rate' in value && 'unit' in value) { + storedDefaultP2PMileageRate = value as DefaultP2PMileageRate; + } + }); +} + +function getStoredDefaultP2PMileageRate(): DefaultP2PMileageRate | undefined { + return storedDefaultP2PMileageRate; +} + function mergeTransactionIdsHighlightOnSearchRoute(type: SearchDataTypes, data: Record | null) { return Onyx.merge(ONYXKEYS.TRANSACTION_IDS_HIGHLIGHT_ON_SEARCH_ROUTE, {[type]: data}); } @@ -1704,13 +1703,14 @@ export { setReviewDuplicatesKey, abandonReviewDuplicateTransactions, openDraftDistanceExpense, - getDefaultP2PMileageRate, sanitizeWaypointsForAPI, stringifyWaypointsForAPI, getLastModifiedExpense, revert, changeTransactionsReport, setTransactionReport, + getDefaultP2PMileageRate, + getStoredDefaultP2PMileageRate, mergeTransactionIdsHighlightOnSearchRoute, getDuplicateTransactionDetails, }; diff --git a/src/pages/NewReportWorkspaceSelectionPage.tsx b/src/pages/NewReportWorkspaceSelectionPage.tsx index e249e4a7d7f0..8813717e577c 100644 --- a/src/pages/NewReportWorkspaceSelectionPage.tsx +++ b/src/pages/NewReportWorkspaceSelectionPage.tsx @@ -58,7 +58,7 @@ function NewReportWorkspaceSelectionPage({route}: NewReportWorkspaceSelectionPag const {clearSelectedTransactions} = useSearchActionsContext(); const styles = useThemeStyles(); const [searchTerm, debouncedSearchTerm, setSearchTerm] = useDebouncedState(''); - const {translate, toLocaleDigit, localeCompare} = useLocalize(); + const {translate, localeCompare} = useLocalize(); const {shouldUseNarrowLayout} = useResponsiveLayout(); const [allReportNextSteps] = useOnyx(ONYXKEYS.COLLECTION.NEXT_STEP); const isRHPOnReportInSearch = isRHPOnSearchMoneyRequestReportPage(); @@ -132,8 +132,6 @@ function NewReportWorkspaceSelectionPage({route}: NewReportWorkspaceSelectionPag reportNextStep, policyCategories: undefined, allTransactions, - translate, - toLocaleDigit, }); // eslint-disable-next-line rulesdir/no-default-id-values diff --git a/src/pages/Search/SearchTransactionsChangeReport.tsx b/src/pages/Search/SearchTransactionsChangeReport.tsx index e6a73aa1c309..02e233b21b23 100644 --- a/src/pages/Search/SearchTransactionsChangeReport.tsx +++ b/src/pages/Search/SearchTransactionsChangeReport.tsx @@ -6,7 +6,6 @@ import {useSearchActionsContext, useSearchStateContext} from '@components/Search import type {ListItem} from '@components/SelectionList/types'; import useConditionalCreateEmptyReportConfirmation from '@hooks/useConditionalCreateEmptyReportConfirmation'; import useHasPerDiemTransactions from '@hooks/useHasPerDiemTransactions'; -import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses'; @@ -28,7 +27,6 @@ type TransactionGroupListItem = ListItem & { }; function SearchTransactionsChangeReport() { - const {translate, toLocaleDigit} = useLocalize(); const {selectedTransactions} = useSearchStateContext(); const {clearSelectedTransactions} = useSearchActionsContext(); const selectedTransactionsKeys = useMemo(() => Object.keys(selectedTransactions), [selectedTransactions]); @@ -53,7 +51,6 @@ function SearchTransactionsChangeReport() { const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED); const [betas] = useOnyx(ONYXKEYS.BETAS); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const hasPerDiemTransactions = useHasPerDiemTransactions(selectedTransactionsKeys); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const {isBetaEnabled} = usePermissions(); @@ -119,9 +116,6 @@ function SearchTransactionsChangeReport() { reportNextStep, policyCategories: allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${policyForMovingExpensesID}`], allTransactions: transactions, - translate, - toLocaleDigit, - defaultP2PMileageRate, }); clearSelectedTransactions(); }); @@ -164,9 +158,6 @@ function SearchTransactionsChangeReport() { reportNextStep, policyCategories: allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${item.policyID}`], allTransactions: transactions, - translate, - toLocaleDigit, - defaultP2PMileageRate, }); // eslint-disable-next-line @typescript-eslint/no-deprecated InteractionManager.runAfterInteractions(() => { @@ -187,9 +178,6 @@ function SearchTransactionsChangeReport() { email: session?.email ?? '', policy: allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${personalPolicyID}`], allTransactions: transactions, - translate, - toLocaleDigit, - defaultP2PMileageRate, }); clearSelectedTransactions(); Navigation.goBack(); diff --git a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx index 87049043685a..c918824154a3 100644 --- a/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx +++ b/src/pages/inbox/report/ContextMenu/PopoverReportActionContextMenu.tsx @@ -47,7 +47,7 @@ type PopoverReportActionContextMenuProps = { }; function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuProps) { - const {translate, toLocaleDigit} = useLocalize(); + const {translate} = useLocalize(); const reportIDRef = useRef(undefined); const typeRef = useRef(undefined); const reportActionRef = useRef> | null>(null); @@ -330,7 +330,6 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro const [selfDMReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${selfDMReportID}`); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`); const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const {currentSearchHash} = useSearchStateContext(); const {deleteTransactions} = useDeleteTransactions({ report, @@ -383,9 +382,6 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro allTransactionViolations, bankAccountList, hash: currentSearchHash, - defaultP2PMileageRate, - translate, - toLocaleDigit, }); } else if (reportAction) { // eslint-disable-next-line @typescript-eslint/no-deprecated @@ -424,7 +420,6 @@ function PopoverReportActionContextMenu({ref}: PopoverReportActionContextMenuPro bankAccountList, isOriginalReportArchived, visibleReportActionsData, - defaultP2PMileageRate, ]); const hideDeleteModal = () => { diff --git a/src/pages/iou/SplitExpenseCreateDateRagePage.tsx b/src/pages/iou/SplitExpenseCreateDateRagePage.tsx index bf419b48b129..101032e47b1a 100644 --- a/src/pages/iou/SplitExpenseCreateDateRagePage.tsx +++ b/src/pages/iou/SplitExpenseCreateDateRagePage.tsx @@ -51,10 +51,9 @@ function SplitExpenseCreateDateRagePage({route}: SplitExpenseCreateDateRagePageP ? policy : currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${getNonEmptyStringOnyxID(currentReport?.policyID)}`]; const {login, accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const updateDate = (value: FormOnyxValues) => { - resetSplitExpensesByDateRange(transaction, currentReport, value[INPUT_IDS.START_DATE], value[INPUT_IDS.END_DATE], currentPolicy, defaultP2PMileageRate); + resetSplitExpensesByDateRange(transaction, currentReport, value[INPUT_IDS.START_DATE], value[INPUT_IDS.END_DATE], currentPolicy); Navigation.goBack(backTo); }; diff --git a/src/pages/iou/SplitExpenseEditPage.tsx b/src/pages/iou/SplitExpenseEditPage.tsx index c3a84295c940..8262405dbd7e 100644 --- a/src/pages/iou/SplitExpenseEditPage.tsx +++ b/src/pages/iou/SplitExpenseEditPage.tsx @@ -75,7 +75,6 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) { const [policyCategories] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${currentReport?.policyID}`); const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${currentReport?.policyID}`); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const {login, accountID: currentUserAccountID} = useCurrentUserPersonalDetails(); const fetchData = useCallback(() => { @@ -130,7 +129,7 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) { const isDistance = isDistanceRequest(splitExpenseDraftTransaction); const isManualDistance = isManualDistanceRequest(splitExpenseDraftTransaction); const isOdometerDistance = isOdometerDistanceRequest(splitExpenseDraftTransaction); - const {unit, rate, name: rateName} = DistanceRequestUtils.getRate({transaction: splitExpenseDraftTransaction, policy: currentPolicy, defaultP2PMileageRate}); + const {unit, rate, name: rateName} = DistanceRequestUtils.getRate({transaction: splitExpenseDraftTransaction, policy: currentPolicy}); const distance = getDistanceInMeters(splitExpenseDraftTransaction, unit); const currentAmount = useMemo(() => { if (isDistance && distance && rate) { @@ -370,7 +369,7 @@ function SplitExpenseEditPage({route}: SplitExpensePageProps) { style={[styles.w100]} text={translate('common.save')} onPress={() => { - updateSplitExpenseField(splitExpenseDraftTransaction, originalTransactionDraft, splitExpenseTransactionID, transaction, currentPolicy, defaultP2PMileageRate); + updateSplitExpenseField(splitExpenseDraftTransaction, originalTransactionDraft, splitExpenseTransactionID, transaction, currentPolicy); Navigation.goBack(backTo); }} pressOnEnter diff --git a/src/pages/iou/SplitExpensePage.tsx b/src/pages/iou/SplitExpensePage.tsx index 90cd2a4d7b34..b9e7a2b21177 100644 --- a/src/pages/iou/SplitExpensePage.tsx +++ b/src/pages/iou/SplitExpensePage.tsx @@ -139,7 +139,6 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const currencySymbol = getCurrencySymbol(transactionDetails.currency ?? '') ?? transactionDetails.currency ?? CONST.CURRENCY.USD; useEffect(() => { - // eslint-disable-next-line react-hooks/set-state-in-effect setErrorMessage(''); }, [splitExpenses.length]); @@ -163,7 +162,6 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const icons = useMemoizedLazyExpensifyIcons(['ArrowsLeftRight', 'Plus']); const {isBetaEnabled} = usePermissions(); @@ -183,7 +181,7 @@ function SplitExpensePage({route}: SplitExpensePageProps) { if (isSplitDistance) { const currentRateID = splitExpense?.customUnit?.customUnitRateID ?? String(CONST.DEFAULT_NUMBER_ID); const rates = DistanceRequestUtils.getMileageRates(currentPolicy, false, currentRateID); - const {rate} = DistanceRequestUtils.getRate({transaction: splitTransaction, policy: currentPolicy, defaultP2PMileageRate}); + const {rate} = DistanceRequestUtils.getRate({transaction: splitTransaction, policy: currentPolicy}); if (!rates[currentRateID] || !rate) { isUnitRateIDOutOfPolicy = true; } @@ -195,13 +193,11 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const errorString = getLatestErrorMessage(draftTransaction ?? {}); if (errorString) { - // eslint-disable-next-line react-hooks/set-state-in-effect setErrorMessage(errorString); } }, [draftTransaction, draftTransaction?.errors]); useEffect(() => { - // eslint-disable-next-line react-hooks/set-state-in-effect setErrorMessage(''); }, [sumOfSplitExpenses, splitExpenses]); @@ -209,14 +205,14 @@ function SplitExpensePage({route}: SplitExpensePageProps) { if (draftTransaction?.errors) { clearSplitTransactionDraftErrors(transactionID); } - addSplitExpenseField(transaction, draftTransaction, transactionReport, currentPolicy, defaultP2PMileageRate); + addSplitExpenseField(transaction, draftTransaction, transactionReport, currentPolicy); }; const onMakeSplitsEven = () => { if (!draftTransaction) { return; } - evenlyDistributeSplitExpenseAmounts(draftTransaction, transaction, currentPolicy, defaultP2PMileageRate); + evenlyDistributeSplitExpenseAmounts(draftTransaction, transaction, currentPolicy); }; const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${getNonEmptyStringOnyxID(expenseReport?.policyID)}`); @@ -316,10 +312,10 @@ function SplitExpensePage({route}: SplitExpensePageProps) { const onSplitExpenseValueChange = (id: string, value: number, mode: ValueOf) => { if (mode === CONST.TAB.SPLIT.AMOUNT || mode === CONST.TAB.SPLIT.DATE) { const amountInCents = convertToBackendAmount(value); - updateSplitExpenseAmountField(draftTransaction, id, amountInCents, currentPolicy, defaultP2PMileageRate); + updateSplitExpenseAmountField(draftTransaction, id, amountInCents, currentPolicy); } else { const amountInCents = calculateSplitAmountFromPercentage(transactionDetailsAmount, value); - updateSplitExpenseAmountField(draftTransaction, id, amountInCents, currentPolicy, defaultP2PMileageRate); + updateSplitExpenseAmountField(draftTransaction, id, amountInCents, currentPolicy); } }; diff --git a/src/pages/iou/request/step/IOURequestEditReport.tsx b/src/pages/iou/request/step/IOURequestEditReport.tsx index 9b4ca6478f54..ca2436b3bc37 100644 --- a/src/pages/iou/request/step/IOURequestEditReport.tsx +++ b/src/pages/iou/request/step/IOURequestEditReport.tsx @@ -5,7 +5,6 @@ import {useSearchActionsContext, useSearchStateContext} from '@components/Search import type {ListItem} from '@components/SelectionList/types'; import useConditionalCreateEmptyReportConfirmation from '@hooks/useConditionalCreateEmptyReportConfirmation'; import useHasPerDiemTransactions from '@hooks/useHasPerDiemTransactions'; -import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses'; @@ -33,7 +32,6 @@ type TransactionGroupListItem = ListItem & { type IOURequestEditReportProps = WithWritableReportOrNotFoundProps; function IOURequestEditReport({route}: IOURequestEditReportProps) { - const {translate, toLocaleDigit} = useLocalize(); const {backTo, reportID, action, shouldTurnOffSelectionMode, transactionID: transactionIDFromParams} = route.params; const {selectedTransactionIDs} = useSearchStateContext(); const transactionIDs = transactionIDFromParams ? [transactionIDFromParams] : selectedTransactionIDs; @@ -65,7 +63,6 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { const policyForMovingExpenses = policyForMovingExpensesID ? allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyForMovingExpensesID}`] : undefined; const [allTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const [betas] = useOnyx(ONYXKEYS.BETAS); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const selectReport = (item: TransactionGroupListItem, report?: OnyxEntry) => { if (transactionIDs.length === 0 || item.value === reportID) { Navigation.dismissToSuperWideRHP(); @@ -85,9 +82,6 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { reportNextStep, policyCategories: allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${item.policyID}`], allTransactions, - translate, - toLocaleDigit, - defaultP2PMileageRate, }); turnOffMobileSelectionMode(); clearSelectedTransactions(true); @@ -107,9 +101,6 @@ function IOURequestEditReport({route}: IOURequestEditReportProps) { email: session?.email ?? '', policy: allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${personalPolicyID}`], allTransactions, - translate, - toLocaleDigit, - defaultP2PMileageRate, }); if (shouldTurnOffSelectionMode) { turnOffMobileSelectionMode(); diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 074df9668e8c..aa54fddfa958 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -102,7 +102,6 @@ function IOURequestStepDistance({ const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); const [optimisticWaypoints, setOptimisticWaypoints] = useState(null); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES); @@ -193,7 +192,7 @@ function IOURequestStepDistance({ const mileageRates = DistanceRequestUtils.getMileageRates(policy); const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy); const mileageRate: MileageRate | undefined = isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction, defaultP2PMileageRate) + ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction) : // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (customUnitRateID && mileageRates?.[customUnitRateID]) || defaultMileageRate; @@ -208,7 +207,7 @@ function IOURequestStepDistance({ setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []); } }, - [policy, personalPolicy?.outputCurrency, transaction, customUnitRateID, transactionID, isSplitRequest, defaultP2PMileageRate], + [policy, personalPolicy?.outputCurrency, transaction, customUnitRateID, transactionID, isSplitRequest], ); // For quick button actions, we'll skip the confirmation page unless the report is archived or this is a workspace @@ -347,7 +346,6 @@ function IOURequestStepDistance({ amountOwed, userBillingGracePeriodEnds, ownerBillingGracePeriodEnd, - defaultP2PMileageRate, conciergeReportID, }); }, [ @@ -386,7 +384,6 @@ function IOURequestStepDistance({ amountOwed, userBillingGracePeriodEnds, ownerBillingGracePeriodEnd, - defaultP2PMileageRate, conciergeReportID, ]); @@ -459,7 +456,6 @@ function IOURequestStepDistance({ originalSplitTransactionDraft, {waypoints: currentTransaction?.comment?.waypoints, routes: currentTransaction?.routes}, policy, - defaultP2PMileageRate, ); navigateBack(); return; @@ -491,7 +487,6 @@ function IOURequestStepDistance({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, - defaultP2PMileageRate, }); } transactionWasSaved.current = true; @@ -527,7 +522,6 @@ function IOURequestStepDistance({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, - defaultP2PMileageRate, ]); const renderItem = useCallback( diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index a96ddd24dcff..d2a8dc9a45a9 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -62,7 +62,6 @@ function IOURequestStepDistanceGPS({ const mapRef = useRef(null); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const isArchived = useReportIsArchived(report?.reportID); const [gpsDraftDetails] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`); @@ -97,7 +96,7 @@ function IOURequestStepDistanceGPS({ const shouldUseDefaultExpensePolicy = shouldUseDefaultExpensePolicyUtil(iouType, defaultExpensePolicy, amountOwed, userBillingGracePeriodEnds, ownerBillingGracePeriodEnd); - const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy, defaultP2PMileageRate}).unit; + const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy}).unit; const shouldSkipConfirmation = !skipConfirmation || !report?.reportID ? false : !(isArchived || isPolicyExpenseChatUtils(report)); @@ -149,7 +148,6 @@ function IOURequestStepDistanceGPS({ amountOwed, userBillingGracePeriodEnds, ownerBillingGracePeriodEnd, - defaultP2PMileageRate, conciergeReportID, }); }; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx index 75be0d487362..4cf70800125e 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx @@ -89,7 +89,6 @@ function IOURequestStepDistanceManual({ const {policyForMovingExpenses} = usePolicyForMovingExpenses(); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const reportAttributesDerived = useReportAttributes(); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); @@ -126,7 +125,6 @@ function IOURequestStepDistanceManual({ transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy, useTransactionDistanceUnit: false, - defaultP2PMileageRate, }); const unit = mileageRate.unit; const rate = mileageRate.rate ?? 0; @@ -183,7 +181,7 @@ function IOURequestStepDistanceManual({ if (action === CONST.IOU.ACTION.EDIT) { // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplit && transaction) { - setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {distance: distanceAsFloat}, policy, defaultP2PMileageRate); + setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {distance: distanceAsFloat}, policy); Navigation.goBack(backTo); return; } @@ -211,7 +209,6 @@ function IOURequestStepDistanceManual({ isASAPSubmitBetaEnabled, parentReportNextStep, recentWaypoints, - defaultP2PMileageRate, }); } Navigation.goBack(backTo); @@ -256,7 +253,6 @@ function IOURequestStepDistanceManual({ amountOwed, userBillingGracePeriodEnds, ownerBillingGracePeriodEnd, - defaultP2PMileageRate, conciergeReportID, }); }, @@ -303,9 +299,7 @@ function IOURequestStepDistanceManual({ draftTransactionIDs, isSelfTourViewed, amountOwed, - userBillingGracePeriodEnds, ownerBillingGracePeriodEnd, - defaultP2PMileageRate, conciergeReportID, ], ); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx index 8838013055a7..a605b9fc8380 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx @@ -100,7 +100,6 @@ function IOURequestStepDistanceMap({ const [ownerBillingGracePeriodEnd] = useOnyx(ONYXKEYS.NVP_PRIVATE_OWNER_BILLING_GRACE_PERIOD_END); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [optimisticWaypoints, setOptimisticWaypoints] = useState(null); @@ -189,7 +188,7 @@ function IOURequestStepDistanceMap({ const mileageRates = DistanceRequestUtils.getMileageRates(policy); const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(policy); const mileageRate: MileageRate | undefined = isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction, defaultP2PMileageRate) + ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction) : // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing (customUnitRateID && mileageRates?.[customUnitRateID]) || defaultMileageRate; @@ -204,7 +203,7 @@ function IOURequestStepDistanceMap({ setSplitShares(transaction, amount, currency ?? '', participantAccountIDs ?? []); } }, - [policy, personalPolicy?.outputCurrency, transaction, customUnitRateID, transactionID, isSplitRequest, defaultP2PMileageRate], + [policy, personalPolicy?.outputCurrency, transaction, customUnitRateID, transactionID, isSplitRequest], ); // For quick button actions, we'll skip the confirmation page unless the report is archived or this is a workspace @@ -339,7 +338,6 @@ function IOURequestStepDistanceMap({ amountOwed, userBillingGracePeriodEnds, ownerBillingGracePeriodEnd, - defaultP2PMileageRate, conciergeReportID, }); }, [ @@ -378,7 +376,6 @@ function IOURequestStepDistanceMap({ amountOwed, userBillingGracePeriodEnds, ownerBillingGracePeriodEnd, - defaultP2PMileageRate, conciergeReportID, ]); @@ -446,7 +443,7 @@ function IOURequestStepDistanceMap({ if (isEditing) { // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplit && transaction) { - setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {waypoints}, policy, defaultP2PMileageRate); + setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {waypoints}, policy); navigateBack(); return; } @@ -477,7 +474,6 @@ function IOURequestStepDistanceMap({ currentUserEmailParam, isASAPSubmitBetaEnabled, parentReportNextStep, - defaultP2PMileageRate, }); } transactionWasSaved.current = true; @@ -511,7 +507,6 @@ function IOURequestStepDistanceMap({ isASAPSubmitBetaEnabled, parentReportNextStep, recentWaypoints, - defaultP2PMileageRate, ]); const renderItem = useCallback( diff --git a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx index 9c7ec2ac9726..ec2e9a46ec65 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx @@ -101,7 +101,6 @@ function IOURequestStepDistanceOdometer({ const isArchived = useReportIsArchived(report?.reportID); const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST); const reportAttributesDerived = useReportAttributes(); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); @@ -145,11 +144,7 @@ function IOURequestStepDistanceOdometer({ [iouType, defaultExpensePolicy, amountOwed, userBillingGracePeriodEnds, ownerBillingGracePeriodEnd], ); - const mileageRate = DistanceRequestUtils.getRate({ - transaction: currentTransaction, - policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy, - defaultP2PMileageRate, - }); + const mileageRate = DistanceRequestUtils.getRate({transaction: currentTransaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy}); const unit = mileageRate.unit; const rate = mileageRate.rate ?? 0; @@ -426,7 +421,6 @@ function IOURequestStepDistanceOdometer({ odometerEnd: end, }, policy, - defaultP2PMileageRate, ); Navigation.goBack(); return; @@ -457,7 +451,6 @@ function IOURequestStepDistanceOdometer({ isASAPSubmitBetaEnabled: false, parentReportNextStep, recentWaypoints, - defaultP2PMileageRate, }); } Navigation.goBack(); @@ -522,7 +515,6 @@ function IOURequestStepDistanceOdometer({ amountOwed, userBillingGracePeriodEnds, ownerBillingGracePeriodEnd, - defaultP2PMileageRate, conciergeReportID, }); }; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index bf7ccfc33c05..3ff9a2f0a605 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -64,7 +64,6 @@ function IOURequestStepDistanceRate({ /* eslint-enable @typescript-eslint/prefer-nullish-coalescing */ const [splitDraftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const {policy} = usePolicyForTransaction({transaction, reportPolicyID: report?.policyID, action, iouType, policyDraft}); @@ -191,7 +190,6 @@ function IOURequestStepDistanceRate({ updatedTaxAmount: taxAmount, updatedTaxCode: taxRateExternalID, updatedTaxValue: taxValue, - defaultP2PMileageRate, }); } } diff --git a/src/pages/iou/request/step/IOURequestStepReport.tsx b/src/pages/iou/request/step/IOURequestStepReport.tsx index 88e1b4a5ffea..8f3c3fea190c 100644 --- a/src/pages/iou/request/step/IOURequestStepReport.tsx +++ b/src/pages/iou/request/step/IOURequestStepReport.tsx @@ -5,7 +5,6 @@ import {usePersonalDetails, useSession} from '@components/OnyxListItemProvider'; import {useSearchActionsContext} from '@components/Search/SearchContext'; import type {ListItem} from '@components/SelectionList/types'; import useConditionalCreateEmptyReportConfirmation from '@hooks/useConditionalCreateEmptyReportConfirmation'; -import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useOptimisticDraftTransactions from '@hooks/useOptimisticDraftTransactions'; import usePermissions from '@hooks/usePermissions'; @@ -45,7 +44,6 @@ const getIOUActionsSelector = (actions: OnyxEntry): ReportAction[ }; function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { - const {translate, toLocaleDigit} = useLocalize(); const {backTo, action, iouType, transactionID, reportID: reportIDFromRoute, reportActionID} = route.params; const [allReports] = useOnyx(ONYXKEYS.COLLECTION.REPORT); const isUnreported = transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID; @@ -95,7 +93,6 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { const perDiemOriginalPolicy = getPolicyByCustomUnitID(transaction, allPolicies); const [transactions] = useOptimisticDraftTransactions(transaction); const [betas] = useOnyx(ONYXKEYS.BETAS); - const [defaultP2PMileageRate] = useOnyx(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE); const handleGoBack = () => { if (isEditing) { Navigation.dismissToSuperWideRHP(); @@ -187,9 +184,6 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { reportNextStep: undefined, policyCategories: allPolicyCategories?.[`${ONYXKEYS.COLLECTION.POLICY_CATEGORIES}${item.policyID}`], allTransactions, - translate, - toLocaleDigit, - defaultP2PMileageRate, }); removeTransaction(transaction.transactionID); } @@ -234,9 +228,6 @@ function IOURequestStepReport({route, transaction}: IOURequestStepReportProps) { email: session?.email ?? '', policy: allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${personalPolicyID}`], allTransactions, - translate, - toLocaleDigit, - defaultP2PMileageRate, }); removeTransaction(transaction.transactionID); }); diff --git a/src/types/onyx/DefaultP2PMileageRate.ts b/src/types/onyx/DefaultP2PMileageRate.ts index f413df043732..fbad55c52746 100644 --- a/src/types/onyx/DefaultP2PMileageRate.ts +++ b/src/types/onyx/DefaultP2PMileageRate.ts @@ -1,12 +1,10 @@ -import type {Unit} from './Policy'; - -/** Model of the default P2P mileage rate fetched from Auth */ +/** Default P2P mileage rate fetched from Auth for the user's reporting currency */ type DefaultP2PMileageRate = { - /** Rate in cents per unit */ + /** Rate in cents per unit (e.g. 6700 = $0.67/mile) */ rate: number; - /** Unit of measurement: 'km' or 'mi' */ - unit: Unit; + /** Distance unit: "mi" or "km" */ + unit: string; }; export default DefaultP2PMileageRate; diff --git a/src/types/onyx/index.ts b/src/types/onyx/index.ts index 9bf3c289887f..adf447f942c3 100644 --- a/src/types/onyx/index.ts +++ b/src/types/onyx/index.ts @@ -42,7 +42,6 @@ import type Credentials from './Credentials'; import type Currency from './Currency'; import type {CurrencyList} from './Currency'; import type CustomStatusDraft from './CustomStatusDraft'; -import type DefaultP2PMileageRate from './DefaultP2PMileageRate'; import type { CardFeedErrorsDerivedValue, NonPersonalAndWorkspaceCardListDerivedValue, @@ -209,7 +208,6 @@ export type { Currency, CurrencyList, CustomStatusDraft, - DefaultP2PMileageRate, UnshareBankAccount, DismissedReferralBanners, Domain, diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 6babc050f536..a9d10dc7613a 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -73,7 +73,7 @@ import {createRandomReport} from '../utils/collections/reports'; import createRandomTransaction from '../utils/collections/transaction'; import getOnyxValue from '../utils/getOnyxValue'; import type {MockFetch} from '../utils/TestHelper'; -import {getGlobalFetchMock, getOnyxData, setPersonalDetails, signInWithTestUser, toLocaleDigit, translateLocal} from '../utils/TestHelper'; +import {getGlobalFetchMock, getOnyxData, setPersonalDetails, signInWithTestUser, translateLocal} from '../utils/TestHelper'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import waitForNetworkPromises from '../utils/waitForNetworkPromises'; @@ -6184,8 +6184,6 @@ describe('actions/IOU', () => { newReport: result.current.report, policy: mockPolicy, allTransactions, - translate: translateLocal, - toLocaleDigit, }); let updatedTransaction: OnyxEntry; diff --git a/tests/unit/TransactionTest.ts b/tests/unit/TransactionTest.ts index d0585f1ddc2f..89f6dc449ee5 100644 --- a/tests/unit/TransactionTest.ts +++ b/tests/unit/TransactionTest.ts @@ -127,8 +127,6 @@ describe('Transaction', () => { newReport: report, policy: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const reportActions = await new Promise>((resolve) => { @@ -177,8 +175,6 @@ describe('Transaction', () => { newReport: report, policy: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const reportActions = await new Promise>((resolve) => { @@ -241,8 +237,6 @@ describe('Transaction', () => { policy: undefined, reportNextStep: mockReportNextStep, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -307,8 +301,6 @@ describe('Transaction', () => { policy: undefined, reportNextStep: mockReportNextStep, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -361,8 +353,6 @@ describe('Transaction', () => { policy: undefined, reportNextStep: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -427,8 +417,6 @@ describe('Transaction', () => { newReport: report, policy: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -485,8 +473,6 @@ describe('Transaction', () => { newReport: report, policy: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -540,8 +526,6 @@ describe('Transaction', () => { newReport: report, policy: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -601,8 +585,6 @@ describe('Transaction', () => { newReport: expenseReport, policy: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const report = await new Promise>((resolve) => { @@ -662,8 +644,6 @@ describe('Transaction', () => { newReport: expenseReport, policy: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const report = await new Promise>((resolve) => { @@ -730,8 +710,6 @@ describe('Transaction', () => { newReport: newExpenseReport, policy: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const report = await new Promise>((resolve) => { @@ -797,8 +775,6 @@ describe('Transaction', () => { newReport: newExpenseReport, policy: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); const report = await new Promise>((resolve) => { @@ -857,8 +833,6 @@ describe('Transaction', () => { newReport: fakeReport, policy: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -919,8 +893,6 @@ describe('Transaction', () => { newReport: fakeReport, policy: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -975,8 +947,6 @@ describe('Transaction', () => { reportNextStep: undefined, policyCategories, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); @@ -1030,8 +1000,6 @@ describe('Transaction', () => { reportNextStep: undefined, policyCategories: undefined, allTransactions, - translate: TestHelper.translateLocal, - toLocaleDigit: TestHelper.toLocaleDigit, }); await waitForBatchedUpdates(); From 4fa91a4a1baed4bbf934ab6d7a6ee957cf20bdc5 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 22 May 2026 08:37:46 -0700 Subject: [PATCH 35/51] Revert unintended ReportActionsUtils changes inherited from old merge Co-authored-by: Cursor --- Mobile-Expensify | 2 +- src/libs/ReportActionsUtils.ts | 5 +++-- tests/unit/ReportActionsUtilsTest.ts | 15 +++++++++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index b7e4ee893c9a..f81070bd5167 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit b7e4ee893c9a2775e6aedcd814753a74fc31409e +Subproject commit f81070bd51677b0323bd14603fc66bf6b2f90a4e diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index a812ef12214e..52e943620d35 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -208,7 +208,8 @@ function isDeletedAction(reportAction: OnyxInputOrEntry, + reportAction: OnyxInputOrEntry, ): reportAction is ReportAction< | typeof CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED | typeof CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED_VIRTUAL diff --git a/tests/unit/ReportActionsUtilsTest.ts b/tests/unit/ReportActionsUtilsTest.ts index 534707b7ce66..9f6394f4e198 100644 --- a/tests/unit/ReportActionsUtilsTest.ts +++ b/tests/unit/ReportActionsUtilsTest.ts @@ -1751,6 +1751,21 @@ describe('ReportActionsUtils', () => { expect(ReportActionsUtils.isDeletedAction(reportAction)).toBe(false); }); + it('should return false for CARD_ISSUED_VIRTUAL action with empty message array', () => { + const reportAction: ReportAction = { + actionName: CONST.REPORT.ACTIONS.TYPE.CARD_ISSUED_VIRTUAL, + reportActionID: 'card-issued-virtual-123', + actorAccountID: 21052128, + created: '2026-05-19 01:00:00.000', + message: [], + originalMessage: { + assigneeAccountID: 21052128, + cardID: 12345, + }, + }; + expect(ReportActionsUtils.isDeletedAction(reportAction)).toBe(false); + }); + it('should return false for CARDFROZEN action with a backend-provided message fragment', () => { const reportAction: ReportAction = { actionName: CONST.REPORT.ACTIONS.TYPE.CARD_FROZEN, From 7b43d168b4d5f5804572df6c17e9ad97d4a5de31 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 22 May 2026 08:49:17 -0700 Subject: [PATCH 36/51] Narrow DefaultP2PMileageRate.unit to Unit and add unit tests - Tighten DefaultP2PMileageRate.unit from string to the Unit ('mi' | 'km') alias so getRateForP2P no longer needs an 'as Unit' cast. - Add tests/unit/DefaultP2PMileageRateTest.ts covering: - getDefaultP2PMileageRate parses {rate, unit} from the side-effect response and exposes it via getStoredDefaultP2PMileageRate. - Stored rate is left undefined for malformed or missing responses. - getRateForP2P falls back to USD 67c/mile when no rate has been fetched yet, and uses the stored rate with the caller's currency once a rate is available. Co-authored-by: Cursor --- src/libs/DistanceRequestUtils.ts | 5 +- src/types/onyx/DefaultP2PMileageRate.ts | 4 +- tests/unit/DefaultP2PMileageRateTest.ts | 82 +++++++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 tests/unit/DefaultP2PMileageRateTest.ts diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index a864370c2cf1..6f770645014f 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -5,6 +5,7 @@ import type {LocaleContextProps} from '@components/LocaleContextProvider'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {LastSelectedDistanceRates, OnyxInputOrEntry, Transaction} from '@src/types/onyx'; +import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -287,11 +288,11 @@ function getDistanceMerchant( */ function getRateForP2P(currency: string, transaction: OnyxEntry): MileageRate { const defaultRate = getStoredDefaultP2PMileageRate(); - const p2pRate = defaultRate ?? {rate: 6700, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}; + const p2pRate: DefaultP2PMileageRate = defaultRate ?? {rate: 6700, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}; const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? p2pRate.rate) : p2pRate.rate; return { rate, - unit: p2pRate.unit as Unit, + unit: p2pRate.unit, currency: defaultRate ? currency : CONST.CURRENCY.USD, }; } diff --git a/src/types/onyx/DefaultP2PMileageRate.ts b/src/types/onyx/DefaultP2PMileageRate.ts index fbad55c52746..93267f4217cb 100644 --- a/src/types/onyx/DefaultP2PMileageRate.ts +++ b/src/types/onyx/DefaultP2PMileageRate.ts @@ -1,10 +1,12 @@ +import type {Unit} from './Policy'; + /** Default P2P mileage rate fetched from Auth for the user's reporting currency */ type DefaultP2PMileageRate = { /** Rate in cents per unit (e.g. 6700 = $0.67/mile) */ rate: number; /** Distance unit: "mi" or "km" */ - unit: string; + unit: Unit; }; export default DefaultP2PMileageRate; diff --git a/tests/unit/DefaultP2PMileageRateTest.ts b/tests/unit/DefaultP2PMileageRateTest.ts new file mode 100644 index 000000000000..9d4b88a8e0b2 --- /dev/null +++ b/tests/unit/DefaultP2PMileageRateTest.ts @@ -0,0 +1,82 @@ +import type * as TransactionModuleType from '@libs/actions/Transaction'; +import type * as APIModuleType from '@libs/API'; +import type DistanceRequestUtilsType from '@libs/DistanceRequestUtils'; +import CONST from '@src/CONST'; + +type DistanceRequestUtilsModule = typeof DistanceRequestUtilsType; +type TransactionModule = typeof TransactionModuleType; +type APIModule = typeof APIModuleType; + +/** + * Load fresh copies of the modules so the in-memory `storedDefaultP2PMileageRate` + * starts undefined for every test. + */ +function loadFreshModules() { + let api!: APIModule; + let transaction!: TransactionModule; + let distanceRequestUtils!: DistanceRequestUtilsModule; + jest.isolateModules(() => { + api = require('@libs/API') as APIModule; + transaction = require('@libs/actions/Transaction') as TransactionModule; + distanceRequestUtils = (require('@libs/DistanceRequestUtils') as {default: DistanceRequestUtilsModule}).default; + }); + return {api, transaction, distanceRequestUtils}; +} + +describe('Default P2P mileage rate', () => { + describe('getDefaultP2PMileageRate', () => { + it('parses the {rate, unit} response and exposes it via getStoredDefaultP2PMileageRate', async () => { + const {api, transaction} = loadFreshModules(); + const response = {onyxData: [{key: 'defaultP2PMileageRate', value: {rate: 7000, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS}}]}; + jest.spyOn(api, 'makeRequestWithSideEffects').mockResolvedValueOnce(response as never); + + transaction.getDefaultP2PMileageRate(); + await Promise.resolve(); + + expect(transaction.getStoredDefaultP2PMileageRate()).toEqual({rate: 7000, unit: 'km'}); + }); + + it('leaves the stored rate undefined when the response has no defaultP2PMileageRate entry', async () => { + const {api, transaction} = loadFreshModules(); + jest.spyOn(api, 'makeRequestWithSideEffects').mockResolvedValueOnce({onyxData: []} as never); + + transaction.getDefaultP2PMileageRate(); + await Promise.resolve(); + + expect(transaction.getStoredDefaultP2PMileageRate()).toBeUndefined(); + }); + + it('leaves the stored rate undefined when the value is missing required keys', async () => { + const {api, transaction} = loadFreshModules(); + const response = {onyxData: [{key: 'defaultP2PMileageRate', value: {rate: 5000}}]}; + jest.spyOn(api, 'makeRequestWithSideEffects').mockResolvedValueOnce(response as never); + + transaction.getDefaultP2PMileageRate(); + await Promise.resolve(); + + expect(transaction.getStoredDefaultP2PMileageRate()).toBeUndefined(); + }); + }); + + describe('getRateForP2P', () => { + it('falls back to USD 67ยข/mile when no rate has been fetched yet', () => { + const {distanceRequestUtils} = loadFreshModules(); + + const result = distanceRequestUtils.getRateForP2P('EUR', undefined); + + expect(result).toEqual({rate: 6700, unit: 'mi', currency: CONST.CURRENCY.USD}); + }); + + it("uses the stored rate with the caller's currency once a rate is available", async () => { + const {api, transaction, distanceRequestUtils} = loadFreshModules(); + const response = {onyxData: [{key: 'defaultP2PMileageRate', value: {rate: 5500, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS}}]}; + jest.spyOn(api, 'makeRequestWithSideEffects').mockResolvedValueOnce(response as never); + transaction.getDefaultP2PMileageRate(); + await Promise.resolve(); + + const result = distanceRequestUtils.getRateForP2P('EUR', undefined); + + expect(result).toEqual({rate: 5500, unit: 'km', currency: 'EUR'}); + }); + }); +}); From 046eb43d6123e151561b2cfbfb6cd51080386981 Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Fri, 22 May 2026 15:51:21 +0000 Subject: [PATCH 37/51] Fix SessionTest: account for GetDefaultP2PMileageRate xhr call in mock setup The new getDefaultP2PMileageRate() call in openApp fires an HttpUtils.xhr request before OpenApp. The test only mocked two responses (407 + Authenticate success), so the new request consumed the first mock, misaligning all subsequent mock responses. Added a third mock for GetDefaultP2PMileageRate. Co-authored-by: Neil Marcellini --- tests/actions/SessionTest.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/actions/SessionTest.ts b/tests/actions/SessionTest.ts index fac013420337..69958e4f7560 100644 --- a/tests/actions/SessionTest.ts +++ b/tests/actions/SessionTest.ts @@ -120,6 +120,13 @@ describe('Session', () => { // data. (HttpUtils.xhr as jest.MockedFunction) + // The first call is GetDefaultP2PMileageRate (fired by openApp before OpenApp) + .mockImplementationOnce(() => + Promise.resolve({ + jsonCode: CONST.JSON_CODE.SUCCESS, + }), + ) + // This will make the call to OpenApp below return with an expired session code .mockImplementationOnce(() => Promise.resolve({ From 082fdd3c9c78a39e77f2511401cb2ee491b8f6b2 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 22 May 2026 08:52:56 -0700 Subject: [PATCH 38/51] Reset Mobile-Expensify submodule pointer to match main Co-authored-by: Cursor --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index f81070bd5167..b7e4ee893c9a 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit f81070bd51677b0323bd14603fc66bf6b2f90a4e +Subproject commit b7e4ee893c9a2775e6aedcd814753a74fc31409e From 05bcbac62bfa91671eec61340afac3a0f5fc20f3 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 22 May 2026 10:04:30 -0700 Subject: [PATCH 39/51] Fix P2P fallback rate magnitude and clear stale cache before refetch - getRateForP2P fallback was returning rate 6700, which produced $67.00/mile instead of the intended $0.67/mile (distance rates are already in cents per unit, no offset). Correct the literal to 67 so the fallback path during the brief startup race / offline / API failure window matches the documented behavior. - getDefaultP2PMileageRate now clears storedDefaultP2PMileageRate before issuing the request. Without this, openApp flows that don't fully reload (sign-out, supportal, delegate switch) could leave a previous account's rate in the module cache if the new fetch is delayed or returns nothing usable. - Add a regression test for the cache-reset behavior and update the existing fallback test to expect rate 67. Co-authored-by: Cursor --- src/libs/DistanceRequestUtils.ts | 2 +- src/libs/actions/Transaction.ts | 3 +++ tests/unit/DefaultP2PMileageRateTest.ts | 20 +++++++++++++++++++- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 6f770645014f..4a4194090b73 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -288,7 +288,7 @@ function getDistanceMerchant( */ function getRateForP2P(currency: string, transaction: OnyxEntry): MileageRate { const defaultRate = getStoredDefaultP2PMileageRate(); - const p2pRate: DefaultP2PMileageRate = defaultRate ?? {rate: 6700, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}; + const p2pRate: DefaultP2PMileageRate = defaultRate ?? {rate: 67, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}; const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? p2pRate.rate) : p2pRate.rate; return { rate, diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 80ce8f3838e6..78f9da6bf8f3 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -1820,6 +1820,9 @@ function changeTransactionsReport({ let storedDefaultP2PMileageRate: DefaultP2PMileageRate | undefined; function getDefaultP2PMileageRate() { + // Reset before each fetch so a stale rate from a previous session/account can't leak + // through flows that call openApp without a full reload (sign-out, supportal, delegate switch). + storedDefaultP2PMileageRate = undefined; // eslint-disable-next-line rulesdir/no-api-side-effects-method API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, null).then((response) => { const updates = response?.onyxData as Array<{key: string; value: unknown}> | undefined; diff --git a/tests/unit/DefaultP2PMileageRateTest.ts b/tests/unit/DefaultP2PMileageRateTest.ts index 9d4b88a8e0b2..36e7ab01371a 100644 --- a/tests/unit/DefaultP2PMileageRateTest.ts +++ b/tests/unit/DefaultP2PMileageRateTest.ts @@ -56,6 +56,23 @@ describe('Default P2P mileage rate', () => { expect(transaction.getStoredDefaultP2PMileageRate()).toBeUndefined(); }); + + it('clears a previously stored rate before refetching, so a failed/empty refetch cannot leak the old value', async () => { + const {api, transaction} = loadFreshModules(); + const validResponse = {onyxData: [{key: 'defaultP2PMileageRate', value: {rate: 5500, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS}}]}; + const spy = jest.spyOn(api, 'makeRequestWithSideEffects').mockResolvedValueOnce(validResponse as never); + + transaction.getDefaultP2PMileageRate(); + await Promise.resolve(); + expect(transaction.getStoredDefaultP2PMileageRate()).toEqual({rate: 5500, unit: 'km'}); + + // Simulate an account switch where the second fetch returns nothing usable. + spy.mockResolvedValueOnce({onyxData: []} as never); + transaction.getDefaultP2PMileageRate(); + + // The stored rate must be cleared synchronously when the fetch starts, before any response. + expect(transaction.getStoredDefaultP2PMileageRate()).toBeUndefined(); + }); }); describe('getRateForP2P', () => { @@ -64,7 +81,8 @@ describe('Default P2P mileage rate', () => { const result = distanceRequestUtils.getRateForP2P('EUR', undefined); - expect(result).toEqual({rate: 6700, unit: 'mi', currency: CONST.CURRENCY.USD}); + // Rate is in cents per unit (no offset), so 67 = $0.67/mile. + expect(result).toEqual({rate: 67, unit: 'mi', currency: CONST.CURRENCY.USD}); }); it("uses the stored rate with the caller's currency once a rate is available", async () => { From fdc54291b7905d37bff9c071e0d3bd794797360f Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 2 Jun 2026 10:06:52 -0700 Subject: [PATCH 40/51] Fetch default P2P mileage rate when starting any track distance flow vs open app --- src/libs/actions/App.ts | 3 --- src/libs/actions/IOU/MoneyRequest.ts | 3 ++- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libs/actions/App.ts b/src/libs/actions/App.ts index a750396c633e..e8d8d9c49ccf 100644 --- a/src/libs/actions/App.ts +++ b/src/libs/actions/App.ts @@ -32,7 +32,6 @@ import clearOnyxAndSeedFullReconnect from './clearOnyxAndSeedFullReconnect'; import {setShouldForceOffline} from './Network'; import {getAll, rollbackOngoingRequest, save} from './PersistedRequests'; import {createDraftInitialWorkspace, createWorkspace, generateDefaultWorkspaceName, generatePolicyID} from './Policy/Policy'; -import {getDefaultP2PMileageRate} from './Transaction'; type PolicyParamsForOpenOrReconnect = { policyIDList: string[]; @@ -442,8 +441,6 @@ function openApp(shouldKeepPublicRooms = false, allReportsWithDraftComments?: Re }); } - getDefaultP2PMileageRate(); - return getPolicyParamsForOpenOrReconnect() .then((policyParams: PolicyParamsForOpenOrReconnect) => { const params: OpenAppParams = {enablePriorityModeFilter: true, ...policyParams}; diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 3b18f612b530..d2221e42d804 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -35,7 +35,7 @@ import { isOdometerDistanceRequest as isOdometerDistanceRequestTransactionUtils, } from '@libs/TransactionUtils'; import type {ReceiptFile} from '@pages/iou/request/step/IOURequestStepScan/types'; -import {setTransactionReport} from '@userActions/Transaction'; +import {getDefaultP2PMileageRate, setTransactionReport} from '@userActions/Transaction'; import {getRemoveDraftTransactionsByIDsData, removeDraftTransactionsByIDs} from '@userActions/TransactionEdit'; import type {IOURequestType, IOUType} from '@src/CONST'; import CONST from '@src/CONST'; @@ -1068,6 +1068,7 @@ function startDistanceRequest( backToReport?: string, isFromFloatingActionButton?: boolean, ) { + getDefaultP2PMileageRate(); clearMoneyRequest(CONST.IOU.OPTIMISTIC_TRANSACTION_ID, draftTransactionIDs, skipConfirmation); if (isFromFloatingActionButton) { Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`, {isFromFloatingActionButton}); From 5d9123256b9310b88d2d30939875fce9026a55b6 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 2 Jun 2026 10:55:38 -0700 Subject: [PATCH 41/51] WIP change approach, add module for stored default p2p mileage rate --- src/ONYXKEYS.ts | 3 +++ src/libs/API/types.ts | 2 +- src/libs/actions/Transaction.ts | 19 +------------- .../getStoredDefaultP2PMileageRate/index.ts | 25 +++++++++++++++++++ 4 files changed, 30 insertions(+), 19 deletions(-) create mode 100644 src/libs/getStoredDefaultP2PMileageRate/index.ts diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 8e30de4e3f7b..7d9395426b0a 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -27,6 +27,9 @@ const ONYXKEYS = { * which tab is the leader, and which ones are the followers */ ACTIVE_CLIENTS: 'activeClients', + /** Contains the default rate and unit to use for P2P distance expenses, based on the user's personal policy outputCurrency. */ + DEFAULT_P2P_MILEAGE_RATE: 'defaultP2PMileageRate', + /** A unique ID for the device */ DEVICE_ID: 'deviceID', diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 71dd520debde..92aa41fdb8aa 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1267,6 +1267,7 @@ type WriteCommandParameters = { }; const READ_COMMANDS = { + GET_DEFAULT_P2P_MILEAGE_RATE: 'GetDefaultP2PMileageRate', GET_CORPAY_BANK_ACCOUNT_FIELDS: 'GetCorpayBankAccountFields', CONNECT_POLICY_TO_QUICKBOOKS_ONLINE: 'ConnectPolicyToQuickbooksOnline', CONNECT_POLICY_TO_XERO: 'ConnectPolicyToXero', @@ -1511,7 +1512,6 @@ const SIDE_EFFECT_REQUEST_COMMANDS = { LINK_CARD_FEED_TO_POLICY: 'LinkCardFeedToPolicy', REVEAL_CARD_PIN: 'RevealCardPIN', CHANGE_CARD_PIN: 'ChangeCardPIN', - GET_DEFAULT_P2P_MILEAGE_RATE: 'GetDefaultP2PMileageRate', } as const; type SideEffectRequestCommand = ValueOf; diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 78f9da6bf8f3..a1b8927b67c2 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -1817,25 +1817,8 @@ function changeTransactionsReport({ }); } -let storedDefaultP2PMileageRate: DefaultP2PMileageRate | undefined; - function getDefaultP2PMileageRate() { - // Reset before each fetch so a stale rate from a previous session/account can't leak - // through flows that call openApp without a full reload (sign-out, supportal, delegate switch). - storedDefaultP2PMileageRate = undefined; - // eslint-disable-next-line rulesdir/no-api-side-effects-method - API.makeRequestWithSideEffects(SIDE_EFFECT_REQUEST_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, null).then((response) => { - const updates = response?.onyxData as Array<{key: string; value: unknown}> | undefined; - const rateUpdate = updates?.find((update) => update.key === 'defaultP2PMileageRate'); - const value = rateUpdate?.value; - if (value && typeof value === 'object' && 'rate' in value && 'unit' in value) { - storedDefaultP2PMileageRate = value as DefaultP2PMileageRate; - } - }); -} - -function getStoredDefaultP2PMileageRate(): DefaultP2PMileageRate | undefined { - return storedDefaultP2PMileageRate; + API.read(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, null); } function mergeTransactionIdsHighlightOnSearchRoute(type: SearchDataTypes, data: Record | null) { diff --git a/src/libs/getStoredDefaultP2PMileageRate/index.ts b/src/libs/getStoredDefaultP2PMileageRate/index.ts new file mode 100644 index 000000000000..c01f2e9043cc --- /dev/null +++ b/src/libs/getStoredDefaultP2PMileageRate/index.ts @@ -0,0 +1,25 @@ +// This module is used to load the default P2P mileage rate for a user based on their personal policy outputCurrency (default / reporting currency). +// Whenever a user starts the "Track distance" flow the getDefaultP2PMileageRate action will fetch the rate and unit from the hard coded mapping stored in Auth +// (CURRENCY_TO_DEFAULT_MILEAGE_RATE), via the API read command GetDefaultP2PMileageRate. +// The rate will be stored in Onyx and loaded into a variable here via Onyx.connectWithoutView. Normally useOnyx should be used instead, but because +// the default P2P mileage rate is used across many library functions an exception is allowed to prevent having to pass the value through many functions +// across the codebase. +// DO NOT use this pattern for other Onyx data unless you get authorization from the internal Expensify team in Slack. + +import ONYXKEYS from "@src/ONYXKEYS"; +import Onyx from "react-native-onyx"; +import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; + +let defaultP2PMileageRate: DefaultP2PMileageRate | undefined; +Onyx.connectWithoutView({ + key: ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE, + callback: (value) => { + defaultP2PMileageRate = value; + }, +}); + +function getStoredDefaultP2PMileageRate() { + return defaultP2PMileageRate; +} + +export default getStoredDefaultP2PMileageRate; \ No newline at end of file From 15e1ccbc4b69043885c603effc893fc90ccc77a7 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 2 Jun 2026 10:58:54 -0700 Subject: [PATCH 42/51] Add Onyx key type definition --- src/ONYXKEYS.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 7d9395426b0a..af76cac0e4c7 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -12,6 +12,7 @@ import type {AnyOnyxUpdate} from './types/onyx/Request'; import type {SavedCSVColumnLayoutList} from './types/onyx/SavedCSVColumnLayout'; import type AssertTypesEqual from './types/utils/AssertTypesEqual'; import type DeepValueOf from './types/utils/DeepValueOf'; +import DefaultP2PMileageRate from './types/onyx/DefaultP2PMileageRate'; /** * This is a file containing constants for all the top level keys in our store @@ -1388,6 +1389,7 @@ type OnyxCollectionValuesMapping = { type OnyxValuesMapping = { [ONYXKEYS.ACCOUNT]: OnyxTypes.Account; [ONYXKEYS.ACCOUNT_MANAGER_REPORT_ID]: string; + [ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE]: DefaultP2PMileageRate; [ONYXKEYS.NVP_ONBOARDING]: Onboarding; From 78a6ed7b86bbd625ed78f502cbfd41fe42b2eced Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 2 Jun 2026 11:10:22 -0700 Subject: [PATCH 43/51] Clean up all non-test code for new approach --- src/ONYXKEYS.ts | 2 +- src/libs/API/types.ts | 2 +- src/libs/DistanceRequestUtils.ts | 2 +- src/libs/actions/Transaction.ts | 4 +--- src/types/onyx/DefaultP2PMileageRate.ts | 4 ++-- 5 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index af76cac0e4c7..776609f34129 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -28,7 +28,7 @@ const ONYXKEYS = { * which tab is the leader, and which ones are the followers */ ACTIVE_CLIENTS: 'activeClients', - /** Contains the default rate and unit to use for P2P distance expenses, based on the user's personal policy outputCurrency. */ + /** Contains the default rate and unit to use for P2P distance expenses, based on the user's personal policy outputCurrency (default / report currency). */ DEFAULT_P2P_MILEAGE_RATE: 'defaultP2PMileageRate', /** A unique ID for the device */ diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 92aa41fdb8aa..0cf064030b7b 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1371,6 +1371,7 @@ const READ_COMMANDS = { type ReadCommand = ValueOf; type ReadCommandParameters = { + [READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE]: null; [READ_COMMANDS.CONNECT_POLICY_TO_QUICKBOOKS_ONLINE]: Parameters.ConnectPolicyToAccountingIntegrationParams; [READ_COMMANDS.CONNECT_POLICY_TO_XERO]: Parameters.ConnectPolicyToAccountingIntegrationParams; [READ_COMMANDS.CONNECT_POLICY_TO_GUSTO]: Parameters.ConnectPolicyToGustoParams; @@ -1552,7 +1553,6 @@ type SideEffectRequestCommandParameters = { [SIDE_EFFECT_REQUEST_COMMANDS.LINK_CARD_FEED_TO_POLICY]: Parameters.LinkCardToPolicyParams; [SIDE_EFFECT_REQUEST_COMMANDS.REVEAL_CARD_PIN]: Parameters.RevealCardPINParams; [SIDE_EFFECT_REQUEST_COMMANDS.CHANGE_CARD_PIN]: Parameters.ChangeCardPINParams; - [SIDE_EFFECT_REQUEST_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE]: null; }; type ApiRequestCommandParameters = WriteCommandParameters & ReadCommandParameters & SideEffectRequestCommandParameters; diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 4a4194090b73..40a9b60d4a92 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -9,7 +9,7 @@ import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; -import {getStoredDefaultP2PMileageRate} from './actions/Transaction'; +import getStoredDefaultP2PMileageRate from './getStoredDefaultP2PMileageRate'; import {replaceAllDigits} from './MoneyRequestUtils'; import {getDistanceRateCustomUnit, getDistanceRateCustomUnitRate, getUnitRateValue} from './PolicyUtils'; import {getCurrency, getRateID, isCustomUnitRateIDForP2P, isExpenseUnreported} from './TransactionUtils'; diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index a1b8927b67c2..3abb3f1b5d8a 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -11,7 +11,7 @@ import type { MarkAsCashParams, TransactionThreadInfo, } from '@libs/API/parameters'; -import {READ_COMMANDS, SIDE_EFFECT_REQUEST_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; +import {READ_COMMANDS, WRITE_COMMANDS} from '@libs/API/types'; import * as CollectionUtils from '@libs/CollectionUtils'; import {getCurrencySymbol} from '@libs/CurrencyUtils'; import DateUtils from '@libs/DateUtils'; @@ -78,7 +78,6 @@ import type { TransactionViolation, TransactionViolations, } from '@src/types/onyx'; -import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; import type {OriginalMessageIOU, OriginalMessageModifiedExpense} from '@src/types/onyx/OriginalMessage'; import type {OnyxData} from '@src/types/onyx/Request'; import type {SearchDataTypes} from '@src/types/onyx/SearchResults'; @@ -1857,7 +1856,6 @@ export { changeTransactionsReport, setTransactionReport, getDefaultP2PMileageRate, - getStoredDefaultP2PMileageRate, mergeTransactionIdsHighlightOnSearchRoute, getDuplicateTransactionDetails, }; diff --git a/src/types/onyx/DefaultP2PMileageRate.ts b/src/types/onyx/DefaultP2PMileageRate.ts index 93267f4217cb..54c197d2c99c 100644 --- a/src/types/onyx/DefaultP2PMileageRate.ts +++ b/src/types/onyx/DefaultP2PMileageRate.ts @@ -1,8 +1,8 @@ import type {Unit} from './Policy'; -/** Default P2P mileage rate fetched from Auth for the user's reporting currency */ +/** Default P2P mileage rate fetched from Auth for the user's personal policy outputCurrency (default / report currency) */ type DefaultP2PMileageRate = { - /** Rate in cents per unit (e.g. 6700 = $0.67/mile) */ + /** Rate in cents per unit (e.g. 67 = $0.67/mile) */ rate: number; /** Distance unit: "mi" or "km" */ From c0c073a07f58e8436ea11b45706419780a29a367 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 2 Jun 2026 11:55:35 -0700 Subject: [PATCH 44/51] Remove obsolete GetDefaultP2PMileageRate mock from SessionTest openApp no longer fetches the default P2P rate; it is loaded when startDistanceRequest runs instead. Co-authored-by: Cursor --- tests/actions/SessionTest.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/tests/actions/SessionTest.ts b/tests/actions/SessionTest.ts index 69958e4f7560..fac013420337 100644 --- a/tests/actions/SessionTest.ts +++ b/tests/actions/SessionTest.ts @@ -120,13 +120,6 @@ describe('Session', () => { // data. (HttpUtils.xhr as jest.MockedFunction) - // The first call is GetDefaultP2PMileageRate (fired by openApp before OpenApp) - .mockImplementationOnce(() => - Promise.resolve({ - jsonCode: CONST.JSON_CODE.SUCCESS, - }), - ) - // This will make the call to OpenApp below return with an expired session code .mockImplementationOnce(() => Promise.resolve({ From 8b0d7a311c1dfcf021f981fb9404e77dba047fd8 Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Tue, 2 Jun 2026 19:18:00 +0000 Subject: [PATCH 45/51] Fix typecheck and prettier failures Cast the partial transaction in DefaultP2PMileageRateTest to Transaction to satisfy getRateForP2P's OnyxEntry param, and format ONYXKEYS and getStoredDefaultP2PMileageRate per prettier. Co-authored-by: Neil Marcellini --- src/ONYXKEYS.ts | 2 +- src/libs/getStoredDefaultP2PMileageRate/index.ts | 7 +++---- tests/unit/DefaultP2PMileageRateTest.ts | 3 ++- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 488e47f929d2..dc6a6aba3ce3 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -6,13 +6,13 @@ import type {OnboardingCompanySize} from './libs/actions/Welcome/OnboardingFlow' import type Platform from './libs/getPlatform/types'; import type * as FormTypes from './types/form'; import type * as OnyxTypes from './types/onyx'; +import DefaultP2PMileageRate from './types/onyx/DefaultP2PMileageRate'; import type {Attendee, DistanceExpenseType, Participant} from './types/onyx/IOU'; import type Onboarding from './types/onyx/Onboarding'; import type {AnyOnyxUpdate} from './types/onyx/Request'; import type {SavedCSVColumnLayoutList} from './types/onyx/SavedCSVColumnLayout'; import type AssertTypesEqual from './types/utils/AssertTypesEqual'; import type DeepValueOf from './types/utils/DeepValueOf'; -import DefaultP2PMileageRate from './types/onyx/DefaultP2PMileageRate'; /** * This is a file containing constants for all the top level keys in our store diff --git a/src/libs/getStoredDefaultP2PMileageRate/index.ts b/src/libs/getStoredDefaultP2PMileageRate/index.ts index c01f2e9043cc..9c1f11ec86b2 100644 --- a/src/libs/getStoredDefaultP2PMileageRate/index.ts +++ b/src/libs/getStoredDefaultP2PMileageRate/index.ts @@ -5,9 +5,8 @@ // the default P2P mileage rate is used across many library functions an exception is allowed to prevent having to pass the value through many functions // across the codebase. // DO NOT use this pattern for other Onyx data unless you get authorization from the internal Expensify team in Slack. - -import ONYXKEYS from "@src/ONYXKEYS"; -import Onyx from "react-native-onyx"; +import Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; import type DefaultP2PMileageRate from '@src/types/onyx/DefaultP2PMileageRate'; let defaultP2PMileageRate: DefaultP2PMileageRate | undefined; @@ -22,4 +21,4 @@ function getStoredDefaultP2PMileageRate() { return defaultP2PMileageRate; } -export default getStoredDefaultP2PMileageRate; \ No newline at end of file +export default getStoredDefaultP2PMileageRate; diff --git a/tests/unit/DefaultP2PMileageRateTest.ts b/tests/unit/DefaultP2PMileageRateTest.ts index 078a10c81481..866c3e6bbce3 100644 --- a/tests/unit/DefaultP2PMileageRateTest.ts +++ b/tests/unit/DefaultP2PMileageRateTest.ts @@ -6,6 +6,7 @@ import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import getStoredDefaultP2PMileageRate from '@libs/getStoredDefaultP2PMileageRate'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; +import type {Transaction} from '@src/types/onyx'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; describe('Default P2P mileage rate', () => { @@ -61,7 +62,7 @@ describe('Default P2P mileage rate', () => { created: new Date().toISOString(), currency: CONST.CURRENCY.USD, comment: {customUnit: {defaultP2PRate: 99}}, - }; + } as unknown as Transaction; const result = DistanceRequestUtils.getRateForP2P(CONST.CURRENCY.USD, transaction); From ff667f03c6081baebe77d89ebebc1b925d8d44f4 Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Tue, 2 Jun 2026 19:27:01 +0000 Subject: [PATCH 46/51] Fix ESLint consistent-type-imports on DefaultP2PMileageRate import DefaultP2PMileageRate is only used as a type in ONYXKEYS.ts, so it must be imported with `import type` to satisfy the consistent-type-imports rule. Co-authored-by: Neil Marcellini --- src/ONYXKEYS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index dc6a6aba3ce3..434fcd7b961b 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -6,7 +6,7 @@ import type {OnboardingCompanySize} from './libs/actions/Welcome/OnboardingFlow' import type Platform from './libs/getPlatform/types'; import type * as FormTypes from './types/form'; import type * as OnyxTypes from './types/onyx'; -import DefaultP2PMileageRate from './types/onyx/DefaultP2PMileageRate'; +import type DefaultP2PMileageRate from './types/onyx/DefaultP2PMileageRate'; import type {Attendee, DistanceExpenseType, Participant} from './types/onyx/IOU'; import type Onboarding from './types/onyx/Onboarding'; import type {AnyOnyxUpdate} from './types/onyx/Request'; From 181ede3d85e41607783848a4a748a5b3590ef19a Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 2 Jun 2026 17:39:50 -0400 Subject: [PATCH 47/51] Fall back to transaction currency/unit in getRateForP2P when default rate unloaded Editing an existing non-USD distance expense before the async default P2P rate loads flipped the expense currency to USD (and unit to miles). Use the transaction's own currency and distanceUnit as the fallback instead; brand-new requests still default to USD/miles. Co-authored-by: Cursor --- src/libs/DistanceRequestUtils.ts | 11 +++++++-- tests/unit/DistanceRequestUtilsTest.ts | 34 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 40a9b60d4a92..bec1aa851993 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -290,10 +290,17 @@ function getRateForP2P(currency: string, transaction: OnyxEntry): M const defaultRate = getStoredDefaultP2PMileageRate(); const p2pRate: DefaultP2PMileageRate = defaultRate ?? {rate: 67, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}; const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? p2pRate.rate) : p2pRate.rate; + + // The default P2P rate is fetched asynchronously when a distance request starts, so it may not be + // loaded yet. While it's missing, fall back to the existing transaction's own unit and currency + // rather than hardcoding USD/miles. Otherwise editing an existing non-USD distance expense before + // the rate loads would flip its currency to USD. A brand-new request has no transaction, so + // getCurrency returns USD and the unit defaults to miles. + const fallbackUnit = transaction?.comment?.customUnit?.distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; return { rate, - unit: p2pRate.unit, - currency: defaultRate ? currency : CONST.CURRENCY.USD, + unit: defaultRate ? p2pRate.unit : fallbackUnit, + currency: defaultRate ? currency : getCurrency(transaction), }; } diff --git a/tests/unit/DistanceRequestUtilsTest.ts b/tests/unit/DistanceRequestUtilsTest.ts index 6a637d0649fa..3d26e622ee22 100644 --- a/tests/unit/DistanceRequestUtilsTest.ts +++ b/tests/unit/DistanceRequestUtilsTest.ts @@ -2,6 +2,7 @@ import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import CONST from '@src/CONST'; import type {Unit} from '@src/types/onyx/Policy'; import type Policy from '@src/types/onyx/Policy'; +import type Transaction from '@src/types/onyx/Transaction'; import createRandomTransaction from '../utils/collections/transaction'; import {translateLocal} from '../utils/TestHelper'; @@ -214,6 +215,39 @@ describe('DistanceRequestUtils', () => { }); }); + describe('getRateForP2P', () => { + // These tests run with the default P2P mileage rate unloaded (it's fetched asynchronously when a + // distance request starts), which is the case for flows that don't start a new distance request, + // such as editing an existing distance expense. + it('falls back to the existing transaction currency and unit when the default P2P rate is not loaded', () => { + // Given an existing P2P distance expense in GBP measured in kilometers, with its own saved rate + const transaction = { + ...createRandomTransaction(1), + currency: 'GBP', + comment: {customUnit: {distanceUnit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS, defaultP2PRate: 45}}, + } as Transaction; + + // When reading the P2P rate for that transaction's currency + const result = DistanceRequestUtils.getRateForP2P('GBP', transaction); + + // Then it preserves the transaction's currency, unit, and saved rate instead of flipping to USD/miles + expect(result.currency).toBe('GBP'); + expect(result.unit).toBe(CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS); + expect(result.rate).toBe(45); + }); + + it('falls back to USD and miles for a brand-new request with no transaction', () => { + // Given a brand-new distance request that has no transaction yet + // When reading the P2P rate + const result = DistanceRequestUtils.getRateForP2P('GBP', undefined); + + // Then it falls back to the hardcoded USD/miles default + expect(result.currency).toBe(CONST.CURRENCY.USD); + expect(result.unit).toBe(CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); + expect(result.rate).toBe(67); + }); + }); + describe('getDistanceMerchant', () => { const toLocaleDigitMock = (dot: string): string => dot; const getCurrencySymbolMock = (currency: string): string | undefined => { From 722d337c84bc3ab986d8e361705ab28dd0054458 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 2 Jun 2026 17:47:54 -0400 Subject: [PATCH 48/51] Simplify comment --- src/libs/DistanceRequestUtils.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index bec1aa851993..061d06d03285 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -291,11 +291,7 @@ function getRateForP2P(currency: string, transaction: OnyxEntry): M const p2pRate: DefaultP2PMileageRate = defaultRate ?? {rate: 67, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}; const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? p2pRate.rate) : p2pRate.rate; - // The default P2P rate is fetched asynchronously when a distance request starts, so it may not be - // loaded yet. While it's missing, fall back to the existing transaction's own unit and currency - // rather than hardcoding USD/miles. Otherwise editing an existing non-USD distance expense before - // the rate loads would flip its currency to USD. A brand-new request has no transaction, so - // getCurrency returns USD and the unit defaults to miles. + // If a distance expense is being edited, the defaultP2PRate may not have been loaded yet, so use data from the existing transaction. const fallbackUnit = transaction?.comment?.customUnit?.distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; return { rate, From 944095868c9e933b6a44ab261cfd73f003633dc7 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 3 Jun 2026 07:06:37 -0400 Subject: [PATCH 49/51] Extract DEFAULT_P2P_RATE_CENTS_PER_MILE constant Replace the hardcoded 67 fallback in getRateForP2P with a named constant per CONSISTENCY-2 review feedback on PR #84791. Co-authored-by: Cursor --- src/libs/DistanceRequestUtils.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 061d06d03285..d74c1f65c6a4 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -35,6 +35,7 @@ Onyx.connectWithoutView({ const METERS_TO_KM = 0.001; // 1 kilometer is 1000 meters const METERS_TO_MILES = 0.000621371; // There are approximately 0.000621371 miles in a meter +const DEFAULT_P2P_RATE_CENTS_PER_MILE = 67; function getMileageRates(policy: OnyxInputOrEntry, includeDisabledRates = false, selectedRateID?: string): Record { const mileageRates: Record = {}; @@ -288,7 +289,7 @@ function getDistanceMerchant( */ function getRateForP2P(currency: string, transaction: OnyxEntry): MileageRate { const defaultRate = getStoredDefaultP2PMileageRate(); - const p2pRate: DefaultP2PMileageRate = defaultRate ?? {rate: 67, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}; + const p2pRate: DefaultP2PMileageRate = defaultRate ?? {rate: DEFAULT_P2P_RATE_CENTS_PER_MILE, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}; const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? p2pRate.rate) : p2pRate.rate; // If a distance expense is being edited, the defaultP2PRate may not have been loaded yet, so use data from the existing transaction. From 5590a1bf93217e1ec4e410e3e79ed0b42a764521 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 9 Jun 2026 07:03:53 -0400 Subject: [PATCH 50/51] Guard on transaction existence per review --- src/libs/DistanceRequestUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index d74c1f65c6a4..e0c145149040 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -290,7 +290,7 @@ function getDistanceMerchant( function getRateForP2P(currency: string, transaction: OnyxEntry): MileageRate { const defaultRate = getStoredDefaultP2PMileageRate(); const p2pRate: DefaultP2PMileageRate = defaultRate ?? {rate: DEFAULT_P2P_RATE_CENTS_PER_MILE, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}; - const rate = getCurrency(transaction) === currency ? (transaction?.comment?.customUnit?.defaultP2PRate ?? p2pRate.rate) : p2pRate.rate; + const rate = transaction && getCurrency(transaction) === currency ? (transaction.comment?.customUnit?.defaultP2PRate ?? p2pRate.rate) : p2pRate.rate; // If a distance expense is being edited, the defaultP2PRate may not have been loaded yet, so use data from the existing transaction. const fallbackUnit = transaction?.comment?.customUnit?.distanceUnit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; From a5e24271c3cab5001f40d473bd227f4cc64e49c2 Mon Sep 17 00:00:00 2001 From: "Neil Marcellini (via MelvinBot)" Date: Tue, 9 Jun 2026 11:35:26 +0000 Subject: [PATCH 51/51] Fix: avoid unsafe type assertion in DefaultP2PMileageRateTest Use the typed createRandomTransaction factory with typed overrides instead of casting an object literal with `as unknown as Transaction`, which the no-unsafe-type-assertion ESLint rule flagged. Co-authored-by: Neil Marcellini --- tests/unit/DefaultP2PMileageRateTest.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/unit/DefaultP2PMileageRateTest.ts b/tests/unit/DefaultP2PMileageRateTest.ts index 866c3e6bbce3..820a10480f01 100644 --- a/tests/unit/DefaultP2PMileageRateTest.ts +++ b/tests/unit/DefaultP2PMileageRateTest.ts @@ -6,7 +6,7 @@ import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import getStoredDefaultP2PMileageRate from '@libs/getStoredDefaultP2PMileageRate'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Transaction} from '@src/types/onyx'; +import createRandomTransaction from '../utils/collections/transaction'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; describe('Default P2P mileage rate', () => { @@ -57,12 +57,10 @@ describe('Default P2P mileage rate', () => { await waitForBatchedUpdates(); const transaction = { - amount: 100, - merchant: 'Test', - created: new Date().toISOString(), + ...createRandomTransaction(1), currency: CONST.CURRENCY.USD, comment: {customUnit: {defaultP2PRate: 99}}, - } as unknown as Transaction; + }; const result = DistanceRequestUtils.getRateForP2P(CONST.CURRENCY.USD, transaction);