diff --git a/src/CONST/index.ts b/src/CONST/index.ts index 7c5e7eba54df..dc4967101bb9 100644 --- a/src/CONST/index.ts +++ b/src/CONST/index.ts @@ -16,7 +16,6 @@ import { FAB_OUT as ANIMATION_TIMING_FAB_OUT, MENU_ANIMATION_DURATION as ANIMATION_TIMING_MENU_ANIMATION_DURATION, } from '@libs/Animation/animationTiming'; -import type {MileageRate} from '@libs/DistanceRequestUtils'; import MULTIFACTOR_AUTHENTICATION_VALUES from '@libs/MultifactorAuthentication/VALUES'; import addTrailingForwardSlash from '@libs/UrlUtils'; import variables from '@styles/variables'; @@ -6373,678 +6372,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/ONYXKEYS.ts b/src/ONYXKEYS.ts index 736e8bca0801..826ac133329f 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -6,6 +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 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'; @@ -27,6 +28,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 / report currency). */ + DEFAULT_P2P_MILEAGE_RATE: 'defaultP2PMileageRate', + /** A unique ID for the device */ DEVICE_ID: 'deviceID', @@ -1437,6 +1441,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; diff --git a/src/libs/API/types.ts b/src/libs/API/types.ts index 7af778fec571..71c716323550 100644 --- a/src/libs/API/types.ts +++ b/src/libs/API/types.ts @@ -1276,6 +1276,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', @@ -1380,6 +1381,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; diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 588a883ab71b..e0c145149040 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -5,9 +5,11 @@ 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'; +import getStoredDefaultP2PMileageRate from './getStoredDefaultP2PMileageRate'; import {replaceAllDigits} from './MoneyRequestUtils'; import {getDistanceRateCustomUnit, getDistanceRateCustomUnitRate, getUnitRateValue} from './PolicyUtils'; import {getCurrency, getRateID, isCustomUnitRateIDForP2P, isExpenseUnreported} from './TransactionUtils'; @@ -33,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 = {}; @@ -276,13 +279,6 @@ 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'); -} - /** * Retrieves the rate and unit for a P2P distance expense for a given currency. * @@ -292,16 +288,16 @@ 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]; - ensureRateDefined(mileageRate.rate); + const defaultRate = getStoredDefaultP2PMileageRate(); + const p2pRate: DefaultP2PMileageRate = defaultRate ?? {rate: DEFAULT_P2P_RATE_CENTS_PER_MILE, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}; + const rate = transaction && getCurrency(transaction) === currency ? (transaction.comment?.customUnit?.defaultP2PRate ?? p2pRate.rate) : p2pRate.rate; - // 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; + // 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 { - ...mileageRate, - currency: currencyWithExistingRate, rate, + unit: defaultRate ? p2pRate.unit : fallbackUnit, + currency: defaultRate ? currency : getCurrency(transaction), }; } diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 7c61bb9dd4fe..8b96e8e07904 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -28,6 +28,7 @@ import { isOdometerDistanceRequest as isOdometerDistanceRequestTransactionUtils, } from '@libs/TransactionUtils'; import type {ReceiptFile} from '@pages/iou/request/step/IOURequestStepScan/types'; +import {getDefaultP2PMileageRate} from '@userActions/Transaction'; import {getRemoveDraftTransactionsByIDsData, removeDraftTransactionsByIDs} from '@userActions/TransactionEdit'; import type {IOURequestType} from '@src/CONST'; import CONST from '@src/CONST'; @@ -440,6 +441,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}); diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 9ea166d19008..5edcbe62a444 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -1810,6 +1810,10 @@ function changeTransactionsReport({ }); } +function getDefaultP2PMileageRate() { + API.read(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, null); +} + function mergeTransactionIdsHighlightOnSearchRoute(type: SearchDataTypes, data: Record | null) { return Onyx.merge(ONYXKEYS.TRANSACTION_IDS_HIGHLIGHT_ON_SEARCH_ROUTE, {[type]: data}); } @@ -1845,6 +1849,7 @@ export { revert, changeTransactionsReport, setTransactionReport, + getDefaultP2PMileageRate, mergeTransactionIdsHighlightOnSearchRoute, getDuplicateTransactionDetails, }; diff --git a/src/libs/getStoredDefaultP2PMileageRate/index.ts b/src/libs/getStoredDefaultP2PMileageRate/index.ts new file mode 100644 index 000000000000..9c1f11ec86b2 --- /dev/null +++ b/src/libs/getStoredDefaultP2PMileageRate/index.ts @@ -0,0 +1,24 @@ +// 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 Onyx from 'react-native-onyx'; +import ONYXKEYS from '@src/ONYXKEYS'; +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; diff --git a/src/types/onyx/DefaultP2PMileageRate.ts b/src/types/onyx/DefaultP2PMileageRate.ts new file mode 100644 index 000000000000..54c197d2c99c --- /dev/null +++ b/src/types/onyx/DefaultP2PMileageRate.ts @@ -0,0 +1,12 @@ +import type {Unit} from './Policy'; + +/** 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. 67 = $0.67/mile) */ + rate: number; + + /** Distance unit: "mi" or "km" */ + unit: Unit; +}; + +export default DefaultP2PMileageRate; diff --git a/tests/unit/DefaultP2PMileageRateTest.ts b/tests/unit/DefaultP2PMileageRateTest.ts new file mode 100644 index 000000000000..820a10480f01 --- /dev/null +++ b/tests/unit/DefaultP2PMileageRateTest.ts @@ -0,0 +1,70 @@ +import Onyx from 'react-native-onyx'; +import {getDefaultP2PMileageRate} from '@libs/actions/Transaction'; +import * as API from '@libs/API'; +import {READ_COMMANDS} from '@libs/API/types'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import getStoredDefaultP2PMileageRate from '@libs/getStoredDefaultP2PMileageRate'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import createRandomTransaction from '../utils/collections/transaction'; +import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; + +describe('Default P2P mileage rate', () => { + beforeAll(() => { + Onyx.init({ + keys: ONYXKEYS, + }); + }); + + beforeEach(() => { + return Onyx.clear().then(waitForBatchedUpdates); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + describe('getDefaultP2PMileageRate', () => { + it('calls API.read with GetDefaultP2PMileageRate', () => { + const readSpy = jest.spyOn(API, 'read').mockImplementation(() => {}); + + getDefaultP2PMileageRate(); + + expect(readSpy).toHaveBeenCalledWith(READ_COMMANDS.GET_DEFAULT_P2P_MILEAGE_RATE, null); + }); + }); + + describe('getRateForP2P', () => { + it('falls back to USD 67ยข/mile when no rate has been stored in Onyx', () => { + const result = DistanceRequestUtils.getRateForP2P('EUR', undefined); + + expect(result).toEqual({rate: 67, unit: 'mi', currency: CONST.CURRENCY.USD}); + }); + + it('uses the stored Onyx rate with the caller currency once a rate is available', async () => { + await Onyx.set(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE, {rate: 5500, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_KILOMETERS}); + await waitForBatchedUpdates(); + + expect(getStoredDefaultP2PMileageRate()).toEqual({rate: 5500, unit: 'km'}); + + const result = DistanceRequestUtils.getRateForP2P('EUR', undefined); + + expect(result).toEqual({rate: 5500, unit: 'km', currency: 'EUR'}); + }); + + it('uses the transaction defaultP2PRate when the transaction currency matches', async () => { + await Onyx.set(ONYXKEYS.DEFAULT_P2P_MILEAGE_RATE, {rate: 5500, unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES}); + await waitForBatchedUpdates(); + + const transaction = { + ...createRandomTransaction(1), + currency: CONST.CURRENCY.USD, + comment: {customUnit: {defaultP2PRate: 99}}, + }; + + const result = DistanceRequestUtils.getRateForP2P(CONST.CURRENCY.USD, transaction); + + expect(result).toEqual({rate: 99, unit: 'mi', currency: CONST.CURRENCY.USD}); + }); + }); +}); 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 => {