From b9d9f6b1ba75542b8c8fb5074cce0703f2dd130c Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 1 Oct 2024 08:01:02 -0700 Subject: [PATCH 01/28] Use transaction distanceUnit to prevent retroactive changes --- src/CONST.ts | 12 +++--------- src/components/MoneyRequestConfirmationList.tsx | 9 +++------ src/components/ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/DistanceRequestUtils.ts | 12 ++++++++---- src/types/onyx/Transaction.ts | 3 +++ 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index a977b164e8bf..d0fb8433792e 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -10,13 +10,7 @@ import * as Url from './libs/Url'; import SCREENS from './SCREENS'; import type PlaidBankAccount from './types/onyx/PlaidBankAccount'; import type {Unit} from './types/onyx/Policy'; - -type RateAndUnit = { - unit: Unit; - rate: number; - currency: string; -}; -type CurrencyDefaultMileageRate = Record; +import {MileageRate} from '@libs/DistanceRequestUtils'; // Creating a default array and object this way because objects ({}) and arrays ([]) are not stable types. // Freezing the array ensures that it cannot be unintentionally modified. @@ -5428,7 +5422,7 @@ const CONST = { "rate": 2377, "unit": "km" } - }`) as CurrencyDefaultMileageRate, + }`) as Record, EXIT_SURVEY: { REASONS: { @@ -5793,6 +5787,6 @@ type FeedbackSurveyOptionID = ValueOf; type CancellationType = ValueOf; -export type {Country, IOUAction, IOUType, RateAndUnit, OnboardingPurposeType, IOURequestType, SubscriptionType, FeedbackSurveyOptionID, CancellationType, OnboardingInviteType}; +export type {Country, IOUAction, IOUType, OnboardingPurposeType, IOURequestType, SubscriptionType, FeedbackSurveyOptionID, CancellationType, OnboardingInviteType}; export default CONST; diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 0e0438f83b2c..d6c384db887d 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -248,14 +248,11 @@ function MoneyRequestConfirmationList({ }, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID, isDistanceRequest]); const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; - const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policyCurrency) : mileageRates?.[customUnitRateID] ?? defaultMileageRate; - - const {unit, rate} = mileageRate ?? {}; - + const unit = DistanceRequestUtils.getDistanceUnit(transaction, mileageRate); + const {rate} = mileageRate ?? {}; const prevRate = usePrevious(rate); - - const currency = (mileageRate as MileageRate)?.currency ?? policyCurrency; + const currency = mileageRate?.currency ?? policyCurrency; // A flag for showing the categories field const shouldShowCategories = (isPolicyExpenseChat || isTypeInvoice) && (!!iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 549fd55ac1bb..7d5e0cdb11a2 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -216,7 +216,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const currency = policy ? policy.outputCurrency : PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(currency) : distanceRates[rateID] ?? {}; - const {unit} = mileageRate; + const unit = DistanceRequestUtils.getDistanceUnit(transaction, mileageRate); const rate = transaction?.comment?.customUnit?.defaultP2PRate ?? mileageRate.rate; const distance = TransactionUtils.getDistanceInMeters(transactionBackup ?? transaction, unit); diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index e9a2eaa8027d..9811ff219cdb 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -1,10 +1,9 @@ import type {OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {LocaleContextProps} from '@components/LocaleContextProvider'; -import type {RateAndUnit} from '@src/CONST'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {LastSelectedDistanceRates, OnyxInputOrEntry} 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'; @@ -223,9 +222,9 @@ function getDistanceMerchant( * Retrieves the rate and unit for a P2P distance expense for a given currency. * * @param currency - * @returns The rate and unit in RateAndUnit object. + * @returns The rate and unit in MileageRate object. */ -function getRateForP2P(currency: string): RateAndUnit { +function getRateForP2P(currency: string): MileageRate { const currencyWithExistingRate = CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currency] ? currency : CONST.CURRENCY.USD; return { ...CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currencyWithExistingRate], @@ -301,6 +300,10 @@ function getTaxableAmount(policy: OnyxEntry, customUnitRateID: string, d return amount * taxClaimablePercentage; } +function getDistanceUnit(transaction: OnyxEntry, mileageRate: OnyxEntry) { + return transaction?.comment?.customUnit?.distanceUnit ?? mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; +} + export default { getDefaultMileageRate, getDistanceMerchant, @@ -312,6 +315,7 @@ export default { getCustomUnitRateID, convertToDistanceInMeters, getTaxableAmount, + getDistanceUnit, }; export type {MileageRate}; diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index bc74921c62b0..8f876f18cca5 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -100,6 +100,9 @@ type TransactionCustomUnit = { /** Default rate for custom unit */ defaultP2PRate?: number | null; + + /** The unit for the distance/quantity */ + distanceUnit?: 'mi' | 'km'; }; /** Types of geometry */ From 3cedd463a7c35060ee9c70726adbce237ded4997 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 1 Oct 2024 08:22:25 -0700 Subject: [PATCH 02/28] Display rate with transaction's distance unit --- src/pages/iou/request/step/IOURequestStepDistanceRate.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 3cabcae9f79e..c8317f364ee5 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -71,7 +71,10 @@ function IOURequestStepDistanceRate({ }; const sections = Object.values(rates).map((rate) => { - const rateForDisplay = DistanceRequestUtils.getRateForDisplay(rate.unit, rate.rate, rate.currency, translate, toLocaleDigit); + // If the rate is currently used by the transaction, display the rate with the transaction's distance unit. Otherwise, display the rate's unit. + // Using the transaction's distance unit prevents the case where changing the policy distance unit, and therefore all rate units, effects the units of existing transactions. + const unit = transaction?.comment?.customUnit?.customUnitRateID === rate.customUnitRateID ? DistanceRequestUtils.getDistanceUnit(transaction, rate) : rate.unit; + const rateForDisplay = DistanceRequestUtils.getRateForDisplay(unit, rate.rate, rate.currency, translate, toLocaleDigit); return { text: rate.name ?? rateForDisplay, From d6c88de82439acc46021ea454dbec1c4beab61db Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 1 Oct 2024 12:19:56 -0700 Subject: [PATCH 03/28] Prompt with both units for when transaction unit doesn't match policy --- src/languages/en.ts | 2 +- src/languages/es.ts | 2 +- src/pages/iou/request/step/IOURequestStepDistanceRate.tsx | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index ae5a9314d72d..0e0420219f0a 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -1007,7 +1007,7 @@ const translations = { changed: 'changed', removed: 'removed', transactionPending: 'Transaction pending.', - chooseARate: ({unit}: ReimbursementRateParams) => `Select a workspace reimbursement rate per ${unit}`, + chooseARate: 'Select a workspace reimbursement rate per mile or kilometer', unapprove: 'Unapprove', unapproveReport: 'Unapprove report', headsUp: 'Heads up!', diff --git a/src/languages/es.ts b/src/languages/es.ts index 71cb5037029d..861364cfb5e0 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -1001,7 +1001,7 @@ const translations = { changed: 'cambió', removed: 'eliminó', transactionPending: 'Transacción pendiente.', - chooseARate: ({unit}: ReimbursementRateParams) => `Selecciona una tasa de reembolso por ${unit} del espacio de trabajo`, + chooseARate: 'Selecciona una tasa de reembolso por milla o kilómetro para el espacio de trabajo', unapprove: 'Desaprobar', unapproveReport: 'Anular la aprobación del informe', headsUp: 'Atención!', diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index c8317f364ee5..773a6488f1c1 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -86,8 +86,6 @@ function IOURequestStepDistanceRate({ }; }); - const unit = (Object.values(rates)[0]?.unit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? translate('common.mile') : translate('common.kilometer')) as Unit; - const initiallyFocusedOption = sections.find((item) => item.isSelected)?.keyForList; function selectDistanceRate(customUnitRateID: string) { @@ -96,6 +94,7 @@ function IOURequestStepDistanceRate({ if (shouldShowTax) { const policyCustomUnitRate = getCustomUnitRate(policy, customUnitRateID); taxRateExternalID = policyCustomUnitRate?.attributes?.taxRateExternalID ?? '-1'; + const unit = DistanceRequestUtils.getDistanceUnit(transaction, rates[customUnitRateID]); const taxableAmount = DistanceRequestUtils.getTaxableAmount(policy, customUnitRateID, TransactionUtils.getDistanceInMeters(transaction, unit)); const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxRateExternalID) ?? ''; taxAmount = CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage, taxableAmount, rates[customUnitRateID].currency ?? CONST.CURRENCY.USD)); @@ -121,7 +120,7 @@ function IOURequestStepDistanceRate({ shouldShowWrapper testID={IOURequestStepDistanceRate.displayName} > - {translate('iou.chooseARate', {unit})} + {translate('iou.chooseARate')} Date: Tue, 1 Oct 2024 15:12:34 -0700 Subject: [PATCH 04/28] WIP optimistically set distanceUnit --- src/libs/TransactionUtils/index.ts | 19 +++++++++++++++++++ src/libs/actions/IOU.ts | 7 ++++++- .../step/IOURequestStepConfirmation.tsx | 1 + 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index f14703fabdc8..41d0249e1ea8 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -139,6 +139,7 @@ function buildOptimisticTransaction( billable = false, pendingFields: Partial<{[K in TransactionPendingFieldsKey]: ValueOf}> | undefined = undefined, reimbursable = true, + existingTransaction: OnyxEntry | undefined = undefined, ): Transaction { // transactionIDs are random, positive, 64-bit numeric strings. // Because JS can only handle 53-bit numbers, transactionIDs are strings in the front-end (just like reportActionID) @@ -152,6 +153,24 @@ function buildOptimisticTransaction( commentJSON.originalTransactionID = originalTransactionID; } + const isDistanceTransaction = (pendingFields?.waypoints ?? '') !== ''; + if (isDistanceTransaction) { + // Get the policy of this transaction from the report.policyID + const allReports = ReportConnection.getAllReports(); + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; + const policyID = report?.policyID ?? ''; + const policy = PolicyUtils.getPolicy(policyID); + + // Set the distance unit, which comes from the policy distance unit or the P2P rate data + const distanceRates = DistanceRequestUtils.getMileageRates(policy, true); + const customUnitRateID = existingTransaction?.comment?.customUnit?.customUnitRateID ?? CONST.CUSTOM_UNITS.FAKE_P2P_ID; + const mileageRate = customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID ? DistanceRequestUtils.getRateForP2P(currency) : distanceRates[customUnitRateID] ?? {}; + if (!commentJSON.customUnit) { + commentJSON.customUnit = {}; + } + commentJSON.customUnit.distanceUnit = mileageRate.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; + } + return { ...(!isEmptyObject(pendingFields) ? {pendingFields} : {}), transactionID, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 874056cac4a0..6a759856bc87 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2019,6 +2019,7 @@ function getMoneyRequestInformation( payeeEmail = currentUserEmail, moneyRequestReportID = '', linkedTrackedExpenseReportAction?: OnyxTypes.ReportAction, + existingTransaction: OnyxEntry | undefined = undefined, ): MoneyRequestInformation { const payerEmail = PhoneNumber.addSMSDomainIfPhoneNumber(participant.login ?? ''); const payerAccountID = Number(participant.accountID); @@ -2071,7 +2072,6 @@ function getMoneyRequestInformation( } // STEP 3: Build an optimistic transaction with the receipt - const existingTransaction = allTransactionDrafts[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${existingTransactionID ?? CONST.IOU.OPTIMISTIC_TRANSACTION_ID}`]; const isDistanceRequest = existingTransaction && existingTransaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE; let optimisticTransaction = TransactionUtils.buildOptimisticTransaction( ReportUtils.isExpenseReport(iouReport) ? -amount : amount, @@ -2091,6 +2091,8 @@ function getMoneyRequestInformation( ReportUtils.isExpenseReport(iouReport) ? -(taxAmount ?? 0) : taxAmount, billable, isDistanceRequest ? {waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : undefined, + undefined, + existingTransaction, ); const optimisticPolicyRecentlyUsedCategories = Category.buildOptimisticPolicyRecentlyUsedCategories(iouReport.policyID, category); @@ -5092,6 +5094,7 @@ function createDistanceRequest( currentUserAccountID = -1, splitShares: SplitShares = {}, iouType: ValueOf = CONST.IOU.TYPE.SUBMIT, + existingTransaction: OnyxEntry | undefined = undefined, ) { // If the report is an iou or expense report, we should get the linked chat report to be passed to the getMoneyRequestInformation function const isMoneyRequestReport = ReportUtils.isMoneyRequestReport(report); @@ -5184,6 +5187,8 @@ function createDistanceRequest( userAccountID, currentUserEmail, moneyRequestReportID, + undefined, + existingTransaction, ); onyxData = moneyRequestOnyxData; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index b08f9a6ced5f..4ab66e04dac8 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -335,6 +335,7 @@ function IOURequestStepConfirmation({ currentUserPersonalDetails.accountID, transaction.splitShares, iouType, + transaction, ); }, [policy, policyCategories, policyTags, report, transaction, transactionTaxCode, transactionTaxAmount, customUnitRateID, currentUserPersonalDetails, iouType], From 540f11a1b0c37c8ec4dba0a31e8acca6eba65116 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 2 Oct 2024 06:59:08 -0700 Subject: [PATCH 05/28] Pass existing transaction in all distance cases --- src/libs/actions/IOU.ts | 3 +++ src/pages/iou/request/step/IOURequestStepDistance.tsx | 1 + 2 files changed, 4 insertions(+) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 6a759856bc87..c902978ca17a 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2331,6 +2331,7 @@ function getTrackExpenseInformation( billable, isDistanceRequest ? {waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : undefined, false, + existingTransaction, ); // If there is an existing transaction (which is the case for distance requests), then the data from the existing transaction @@ -3973,6 +3974,8 @@ function createSplitsAndOnyxData( taxAmount, billable, isDistanceRequest ? {waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : undefined, + undefined, + existingTransaction, ); // Important data is set on the draft distance transaction, such as the iouRequestType marking it as a distance request, so merge it into the optimistic split transaction diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 14597df8e313..b217aa3448e6 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -328,6 +328,7 @@ function IOURequestStepDistance({ currentUserPersonalDetails.accountID, transaction?.splitShares, iouType, + transaction, ); return; } From 3961d4cd8c49942cb1432b4266534fe396ca0d22 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 2 Oct 2024 07:28:21 -0700 Subject: [PATCH 06/28] Optimistically update to current distance unit when editing --- src/libs/TransactionUtils/index.ts | 6 ++---- src/libs/actions/IOU.ts | 9 +++++++++ 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 41d0249e1ea8..5b255ddeb1f4 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -25,6 +25,7 @@ import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, import type DeepValueOf from '@src/types/utils/DeepValueOf'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import getDistanceInMeters from './getDistanceInMeters'; +import lodashSet from 'lodash/set'; let allTransactions: OnyxCollection = {}; Onyx.connect({ @@ -165,10 +166,7 @@ function buildOptimisticTransaction( const distanceRates = DistanceRequestUtils.getMileageRates(policy, true); const customUnitRateID = existingTransaction?.comment?.customUnit?.customUnitRateID ?? CONST.CUSTOM_UNITS.FAKE_P2P_ID; const mileageRate = customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID ? DistanceRequestUtils.getRateForP2P(currency) : distanceRates[customUnitRateID] ?? {}; - if (!commentJSON.customUnit) { - commentJSON.customUnit = {}; - } - commentJSON.customUnit.distanceUnit = mileageRate.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; + lodashSet(commentJSON, 'customUnit.distanceUnit', mileageRate.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); } return { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index c902978ca17a..cff034b5da05 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -72,6 +72,7 @@ import * as Category from './Policy/Category'; import * as Policy from './Policy/Policy'; import * as Tag from './Policy/Tag'; import * as Report from './Report'; +import lodashSet from 'lodash/set'; type IOURequestType = ValueOf; @@ -2523,6 +2524,14 @@ function getUpdateMoneyRequestParams( ...TransactionUtils.calculateAmountForUpdatedWaypointOrRate(updatedTransaction, transactionChanges, policy, ReportUtils.isExpenseReport(iouReport)), }; + // Update the distanceUnit + const distanceRates = DistanceRequestUtils.getMileageRates(policy, true); + const customUnitRateID = updatedTransaction?.comment?.customUnit?.customUnitRateID ?? CONST.CUSTOM_UNITS.FAKE_P2P_ID; + const mileageRate = + customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID ? DistanceRequestUtils.getRateForP2P(updatedTransaction?.currency ?? 'USD') : distanceRates[customUnitRateID] ?? {}; + lodashSet(updatedTransaction, 'comment.customUnit.distanceUnit', mileageRate.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); + + // Delete the draft transaction when editing waypoints when the server responds successfully and there are no errors successData.push({ onyxMethod: Onyx.METHOD.SET, From 4520fe3edc6202115af26a82a9416ad3e1ab00a7 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 2 Oct 2024 07:32:28 -0700 Subject: [PATCH 07/28] Prettify --- src/CONST.ts | 3 +-- src/libs/TransactionUtils/index.ts | 2 +- src/libs/actions/IOU.ts | 3 +-- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/CONST.ts b/src/CONST.ts index d0fb8433792e..0ea4fc07dc3f 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -5,12 +5,11 @@ import Config from 'react-native-config'; import * as KeyCommand from 'react-native-key-command'; import type {ValueOf} from 'type-fest'; import type {Video} from './libs/actions/Report'; +import type {MileageRate} from './libs/DistanceRequestUtils'; import BankAccount from './libs/models/BankAccount'; import * as Url from './libs/Url'; import SCREENS from './SCREENS'; import type PlaidBankAccount from './types/onyx/PlaidBankAccount'; -import type {Unit} from './types/onyx/Policy'; -import {MileageRate} from '@libs/DistanceRequestUtils'; // Creating a default array and object this way because objects ({}) and arrays ([]) are not stable types. // Freezing the array ensures that it cannot be unintentionally modified. diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 5b255ddeb1f4..24477cd95922 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -1,5 +1,6 @@ import lodashHas from 'lodash/has'; import lodashIsEqual from 'lodash/isEqual'; +import lodashSet from 'lodash/set'; import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; @@ -25,7 +26,6 @@ import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, import type DeepValueOf from '@src/types/utils/DeepValueOf'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import getDistanceInMeters from './getDistanceInMeters'; -import lodashSet from 'lodash/set'; let allTransactions: OnyxCollection = {}; Onyx.connect({ diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index cff034b5da05..3bf5a7f85bd3 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -1,5 +1,6 @@ import {format} from 'date-fns'; import {fastMerge, Str} from 'expensify-common'; +import lodashSet from 'lodash/set'; import type {NullishDeep, OnyxCollection, OnyxEntry, OnyxInputValue, OnyxUpdate} from 'react-native-onyx'; import Onyx from 'react-native-onyx'; import type {PartialDeep, SetRequired, ValueOf} from 'type-fest'; @@ -72,7 +73,6 @@ import * as Category from './Policy/Category'; import * as Policy from './Policy/Policy'; import * as Tag from './Policy/Tag'; import * as Report from './Report'; -import lodashSet from 'lodash/set'; type IOURequestType = ValueOf; @@ -2531,7 +2531,6 @@ function getUpdateMoneyRequestParams( customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID ? DistanceRequestUtils.getRateForP2P(updatedTransaction?.currency ?? 'USD') : distanceRates[customUnitRateID] ?? {}; lodashSet(updatedTransaction, 'comment.customUnit.distanceUnit', mileageRate.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); - // Delete the draft transaction when editing waypoints when the server responds successfully and there are no errors successData.push({ onyxMethod: Onyx.METHOD.SET, From 993679089206febbb395fe209539266ac97103ac Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 7 Oct 2024 08:08:43 -0700 Subject: [PATCH 08/28] Remove unused types --- src/languages/en.ts | 1 - src/languages/es.ts | 1 - src/languages/params.ts | 3 --- src/pages/iou/request/step/IOURequestStepDistanceRate.tsx | 1 - 4 files changed, 6 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 9a0d42e86cb3..f5e2187badd7 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -108,7 +108,6 @@ import type { PayerSettledParams, PaySomeoneParams, ReconciliationWorksParams, - ReimbursementRateParams, RemovedFromApprovalWorkflowParams, RemovedTheRequestParams, RemoveMemberPromptParams, diff --git a/src/languages/es.ts b/src/languages/es.ts index 66a1cbd60a9e..dbac426b25f0 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -106,7 +106,6 @@ import type { PayerSettledParams, PaySomeoneParams, ReconciliationWorksParams, - ReimbursementRateParams, RemovedFromApprovalWorkflowParams, RemovedTheRequestParams, RemoveMemberPromptParams, diff --git a/src/languages/params.ts b/src/languages/params.ts index 5560316d2ddd..893b02e97591 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -279,8 +279,6 @@ type LogSizeAndDateParams = {size: number; date: string}; type HeldRequestParams = {comment: string}; -type ReimbursementRateParams = {unit: Unit}; - type ChangeFieldParams = {oldValue?: string; newValue: string; fieldName: string}; type ChangePolicyParams = {fromPolicy: string; toPolicy: string}; @@ -648,7 +646,6 @@ export type { PayerPaidAmountParams, PayerPaidParams, PayerSettledParams, - ReimbursementRateParams, RemovedTheRequestParams, RenamedRoomActionParams, ReportArchiveReasonsClosedParams, diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 773a6488f1c1..a432ce2f0d15 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -17,7 +17,6 @@ import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type * as OnyxTypes from '@src/types/onyx'; -import type {Unit} from '@src/types/onyx/Policy'; import StepScreenWrapper from './StepScreenWrapper'; import withFullTransactionOrNotFound from './withFullTransactionOrNotFound'; import type {WithWritableReportOrNotFoundProps} from './withWritableReportOrNotFound'; From 56c96fb8ea6f8fe7024d6e072b75b832e3ff9f4a Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Mon, 7 Oct 2024 08:21:20 -0700 Subject: [PATCH 09/28] Missed a spot --- src/languages/params.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/languages/params.ts b/src/languages/params.ts index 893b02e97591..e4a52f5150fd 100644 --- a/src/languages/params.ts +++ b/src/languages/params.ts @@ -1,6 +1,6 @@ import type {OnyxInputOrEntry, ReportAction} from '@src/types/onyx'; import type {DelegateRole} from '@src/types/onyx/Account'; -import type {AllConnectionName, ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName, Unit} from '@src/types/onyx/Policy'; +import type {AllConnectionName, ConnectionName, PolicyConnectionSyncStage, SageIntacctMappingName} from '@src/types/onyx/Policy'; import type {ViolationDataType} from '@src/types/onyx/TransactionViolation'; type AddressLineParams = { From 9a06cfea2d8f526ea9f89e0276f480d75a77e40c Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 9 Oct 2024 11:59:37 -0700 Subject: [PATCH 10/28] WIP use util to get transaction distance rate --- .../MoneyRequestConfirmationList.tsx | 20 +++---------------- src/libs/DistanceRequestUtils.ts | 18 +++++++++++++++++ 2 files changed, 21 insertions(+), 17 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 5d08468f792c..c65b97f2cb87 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -66,9 +66,6 @@ type MoneyRequestConfirmationListOnyxProps = { /** The draft policy of the report */ policyDraft: OnyxEntry; - /** Unit and rate used for if the expense is a distance expense */ - mileageRates: OnyxEntry>; - /** Mileage rate default for the policy */ defaultMileageRate: OnyxEntry; @@ -181,7 +178,6 @@ function MoneyRequestConfirmationList({ iouAmount, policyCategories: policyCategoriesReal, policyCategoriesDraft, - mileageRates: mileageRatesReal, isDistanceRequest = false, policy: policyReal, policyDraft, @@ -216,10 +212,6 @@ function MoneyRequestConfirmationList({ }: MoneyRequestConfirmationListProps) { const policy = policyReal ?? policyDraft; const policyCategories = policyCategoriesReal ?? policyCategoriesDraft; - const [mileageRatesDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${policyID || '-1'}`, { - selector: (selectedPolicy: OnyxEntry) => DistanceRequestUtils.getMileageRates(selectedPolicy), - }); - const mileageRates = isEmptyObject(mileageRatesReal) ? mileageRatesDraft : mileageRatesReal; const styles = useThemeStyles(); const {translate, toLocaleDigit} = useLocalize(); @@ -247,12 +239,10 @@ function MoneyRequestConfirmationList({ IOU.setCustomUnitRateID(transactionID, rateID); }, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID, isDistanceRequest]); - const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; - const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(policyCurrency) : mileageRates?.[customUnitRateID] ?? defaultMileageRate; - const unit = DistanceRequestUtils.getDistanceUnit(transaction, mileageRate); - const {rate} = mileageRate ?? {}; + const rate = DistanceRequestUtils.getRateForExistingTransaction({transaction, policy, policyDraft}); const prevRate = usePrevious(rate); - const currency = mileageRate?.currency ?? policyCurrency; + const unit = rate.unit; + const currency = rate.currency ?? CONST.CURRENCY.USD; // A flag for showing the categories field const shouldShowCategories = (isPolicyExpenseChat || isTypeInvoice) && (!!iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); @@ -986,10 +976,6 @@ export default withOnyx `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getDefaultMileageRate, }, - mileageRates: { - key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, - selector: (policy: OnyxEntry) => DistanceRequestUtils.getMileageRates(policy), - }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 1159d78bff0e..53d71b2779bd 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -11,6 +11,7 @@ import * as CurrencyUtils from './CurrencyUtils'; import * as PolicyUtils from './PolicyUtils'; import * as ReportConnection from './ReportConnection'; import * as ReportUtils from './ReportUtils'; +import * as TransactionUtils from './TransactionUtils'; type MileageRate = { customUnitRateID?: string; @@ -304,6 +305,22 @@ function getDistanceUnit(transaction: OnyxEntry, mileageRate: OnyxE return transaction?.comment?.customUnit?.distanceUnit ?? mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; } +function getRateForExistingTransaction({transaction, policy, policyDraft}: {transaction: OnyxEntry; policy: OnyxEntry; policyDraft?: OnyxEntry}): MileageRate { + let mileageRates = getMileageRates(policy, true, transaction?.comment?.customUnit?.customUnitRateID); + if (isEmptyObject(mileageRates) && policyDraft) { + mileageRates = getMileageRates(policyDraft, true, transaction?.comment?.customUnit?.customUnitRateID); + } + const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; + const defaultMileageRate = getDefaultMileageRate(policy); + const customUnitRateID = TransactionUtils.getRateID(transaction) ?? ''; + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? getRateForP2P(policyCurrency) : mileageRates?.[customUnitRateID] ?? defaultMileageRate; + const unit = getDistanceUnit(transaction, mileageRate); + return { + ...mileageRate, + unit, + }; +} + export default { getDefaultMileageRate, getDistanceMerchant, @@ -316,6 +333,7 @@ export default { convertToDistanceInMeters, getTaxableAmount, getDistanceUnit, + getRateForExistingTransaction, }; export type {MileageRate}; From 7982c2636306b93117144c78288a826ee0d2da76 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 9 Oct 2024 13:10:59 -0700 Subject: [PATCH 11/28] Util to get updated distance unit --- src/libs/DistanceRequestUtils.ts | 14 ++++++++++++++ src/libs/TransactionUtils/index.ts | 5 +---- src/libs/actions/IOU.ts | 14 ++++---------- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 53d71b2779bd..c7881ad59120 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -305,6 +305,10 @@ function getDistanceUnit(transaction: OnyxEntry, mileageRate: OnyxE return transaction?.comment?.customUnit?.distanceUnit ?? mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; } +/** + * Get the selected rate for an existing transaction, from the policy or P2P default rate. + * Use the distanceUnit stored on the transaction to prevent policy changes modifying existing transactions. If it's not present fallback to the unit from the rate. + */ function getRateForExistingTransaction({transaction, policy, policyDraft}: {transaction: OnyxEntry; policy: OnyxEntry; policyDraft?: OnyxEntry}): MileageRate { let mileageRates = getMileageRates(policy, true, transaction?.comment?.customUnit?.customUnitRateID); if (isEmptyObject(mileageRates) && policyDraft) { @@ -321,6 +325,15 @@ function getRateForExistingTransaction({transaction, policy, policyDraft}: {tran }; } +/** + * Get the updated distance unit from the selected rate instead of the distanceUnit stored on the transaction. + * Useful for updating the transaction distance unit when the distance or rate changes. + */ +function getUpdatedDistanceUnit({transaction, policy, policyDraft}: {transaction: OnyxEntry; policy: OnyxEntry; policyDraft?: OnyxEntry}) { + const mileageRate = getRateForExistingTransaction({transaction, policy, policyDraft}); + return getDistanceUnit(undefined, mileageRate); +} + export default { getDefaultMileageRate, getDistanceMerchant, @@ -333,6 +346,7 @@ export default { convertToDistanceInMeters, getTaxableAmount, getDistanceUnit, + getUpdatedDistanceUnit, getRateForExistingTransaction, }; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 4e900ab54c78..b1595a374310 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -163,10 +163,7 @@ function buildOptimisticTransaction( const policy = PolicyUtils.getPolicy(policyID); // Set the distance unit, which comes from the policy distance unit or the P2P rate data - const distanceRates = DistanceRequestUtils.getMileageRates(policy, true); - const customUnitRateID = existingTransaction?.comment?.customUnit?.customUnitRateID ?? CONST.CUSTOM_UNITS.FAKE_P2P_ID; - const mileageRate = customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID ? DistanceRequestUtils.getRateForP2P(currency) : distanceRates[customUnitRateID] ?? {}; - lodashSet(commentJSON, 'customUnit.distanceUnit', mileageRate.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); + lodashSet(commentJSON, 'customUnit.distanceUnit', DistanceRequestUtils.getUpdatedDistanceUnit({transaction: existingTransaction, policy})); } return { diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 13e18947de80..1917ea4eddef 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2477,7 +2477,7 @@ function getUpdateMoneyRequestParams( transactionID: string, transactionThreadReportID: string, transactionChanges: TransactionChanges, - policy: OnyxTypes.OnyxInputOrEntry, + policy: OnyxEntry, policyTagList: OnyxTypes.OnyxInputOrEntry, policyCategories: OnyxTypes.OnyxInputOrEntry, onlyIncludeChangedFields: boolean, @@ -2498,9 +2498,7 @@ function getUpdateMoneyRequestParams( const iouReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThread?.parentReportID}`] ?? null; const isFromExpenseReport = ReportUtils.isExpenseReport(iouReport); const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction); - let updatedTransaction: OnyxTypes.OnyxInputOrEntry = transaction - ? TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport) - : null; + let updatedTransaction: OnyxEntry = transaction ? TransactionUtils.getUpdatedTransaction(transaction, transactionChanges, isFromExpenseReport) : undefined; const transactionDetails = ReportUtils.getTransactionDetails(updatedTransaction); if (transactionDetails?.waypoints) { @@ -2527,11 +2525,7 @@ function getUpdateMoneyRequestParams( }; // Update the distanceUnit - const distanceRates = DistanceRequestUtils.getMileageRates(policy, true); - const customUnitRateID = updatedTransaction?.comment?.customUnit?.customUnitRateID ?? CONST.CUSTOM_UNITS.FAKE_P2P_ID; - const mileageRate = - customUnitRateID === CONST.CUSTOM_UNITS.FAKE_P2P_ID ? DistanceRequestUtils.getRateForP2P(updatedTransaction?.currency ?? 'USD') : distanceRates[customUnitRateID] ?? {}; - lodashSet(updatedTransaction, 'comment.customUnit.distanceUnit', mileageRate.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES); + lodashSet(updatedTransaction, 'comment.customUnit.distanceUnit', DistanceRequestUtils.getUpdatedDistanceUnit({transaction: updatedTransaction, policy})); // Delete the draft transaction when editing waypoints when the server responds successfully and there are no errors successData.push({ @@ -5584,7 +5578,7 @@ function updateMoneyRequestAmountAndCurrency({ if (ReportUtils.isTrackExpenseReport(transactionThreadReport) && ReportUtils.isSelfDM(parentReport)) { data = getUpdateTrackExpenseParams(transactionID, transactionThreadReportID, transactionChanges, true, policy ?? null); } else { - data = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy ?? null, policyTagList ?? null, policyCategories ?? null, true); + data = getUpdateMoneyRequestParams(transactionID, transactionThreadReportID, transactionChanges, policy, policyTagList ?? null, policyCategories ?? null, true); } const {params, onyxData} = data; API.write(WRITE_COMMANDS.UPDATE_MONEY_REQUEST_AMOUNT_AND_CURRENCY, params, onyxData); From 5c43231d862a9b79851c50cacc894707516f7f67 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 9 Oct 2024 13:11:20 -0700 Subject: [PATCH 12/28] Use get rate util in another spot --- src/components/ReportActionItem/MoneyRequestView.tsx | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 6d8854a37e7c..3034b0c98ab5 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -198,14 +198,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals let amountDescription = `${translate('iou.amount')}`; const hasRoute = TransactionUtils.hasRoute(transactionBackup ?? transaction, isDistanceRequest); - const rateID = TransactionUtils.getRateID(transaction) ?? '-1'; - - const currency = policy ? policy.outputCurrency : PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; - - const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? DistanceRequestUtils.getRateForP2P(currency) : distanceRates[rateID] ?? {}; - const unit = DistanceRequestUtils.getDistanceUnit(transaction, mileageRate); - const rate = transaction?.comment?.customUnit?.defaultP2PRate ?? mileageRate.rate; - + const {unit, rate, currency} = DistanceRequestUtils.getRateForExistingTransaction({transaction, policy}); const distance = TransactionUtils.getDistanceInMeters(transactionBackup ?? transaction, unit); const rateToDisplay = DistanceRequestUtils.getRateForDisplay(unit, rate, currency, translate, toLocaleDigit, isOffline); const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); From e07a8373a3526f617683a37e04dff2e0f41e4ed5 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 9 Oct 2024 13:27:39 -0700 Subject: [PATCH 13/28] Simplify using lodashSet --- src/libs/TransactionUtils/index.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index b1595a374310..ec5f057b75bc 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -275,14 +275,8 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra } if (Object.hasOwn(transactionChanges, 'customUnitRateID')) { - updatedTransaction.comment = { - ...(updatedTransaction?.comment ?? {}), - customUnit: { - ...updatedTransaction?.comment?.customUnit, - customUnitRateID: transactionChanges.customUnitRateID, - defaultP2PRate: null, - }, - }; + lodashSet(updatedTransaction, 'comment.customUnit.customUnitRateID', transactionChanges.customUnitRateID); + lodashSet(updatedTransaction, 'comment.customUnit.defaultP2PRate', null); shouldStopSmartscan = true; } From 18657fdd7a5086c5a16579db72774c2bf8b0f9e8 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 9 Oct 2024 14:14:21 -0700 Subject: [PATCH 14/28] WIP convert distance to new rate unit --- src/CONST.ts | 2 ++ src/libs/TransactionUtils/index.ts | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/CONST.ts b/src/CONST.ts index 1770a6349580..a92b9f2e79e8 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -2413,6 +2413,8 @@ const CONST = { DEFAULT_RATE: 'Default Rate', RATE_DECIMALS: 3, FAKE_P2P_ID: '_FAKE_P2P_ID_', + MILES_TO_KILOMETERS: 1.609344, + KILOMETERS_TO_MILES: 0.621371, }, TERMS: { diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index ec5f057b75bc..32535c68d395 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -278,6 +278,22 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra lodashSet(updatedTransaction, 'comment.customUnit.customUnitRateID', transactionChanges.customUnitRateID); lodashSet(updatedTransaction, 'comment.customUnit.defaultP2PRate', null); shouldStopSmartscan = true; + + const existingDistanceUnit = transaction?.comment?.customUnit?.distanceUnit; + const allReports = ReportConnection.getAllReports(); + const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction.reportID}`] ?? null; + const policyID = report?.policyID ?? ''; + const policy = PolicyUtils.getPolicy(policyID); + + // Get the new distance unit from the rate's unit + const newDistanceUnit = DistanceRequestUtils.getUpdatedDistanceUnit({transaction, policy}); + + // If the distanceUnit is set and the rate is changed to one that has a different unit, convert the distance to the new unit + if (existingDistanceUnit && newDistanceUnit !== existingDistanceUnit) { + const conversionFactor = existingDistanceUnit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? CONST.CUSTOM_UNITS.MILES_TO_KILOMETERS : CONST.CUSTOM_UNITS.KILOMETERS_TO_MILES; + const distance = Math.round(updatedTransaction?.comment?.customUnit?.quantity ?? 0 * conversionFactor * 100) / 100; + lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance); + } } if (Object.hasOwn(transactionChanges, 'taxAmount') && typeof transactionChanges.taxAmount === 'number') { From 1703edbaa1ec585341a44345293f491e49df9c54 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 9 Oct 2024 14:50:00 -0700 Subject: [PATCH 15/28] Refactor and fix getting updated distance unit --- .../MoneyRequestConfirmationList.tsx | 2 +- .../ReportActionItem/MoneyRequestView.tsx | 2 +- src/libs/DistanceRequestUtils.ts | 23 +++++++++++++------ 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index c65b97f2cb87..3408c0cc4c2f 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -239,7 +239,7 @@ function MoneyRequestConfirmationList({ IOU.setCustomUnitRateID(transactionID, rateID); }, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID, isDistanceRequest]); - const rate = DistanceRequestUtils.getRateForExistingTransaction({transaction, policy, policyDraft}); + const rate = DistanceRequestUtils.getRate({transaction, policy, policyDraft}); const prevRate = usePrevious(rate); const unit = rate.unit; const currency = rate.currency ?? CONST.CURRENCY.USD; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 3034b0c98ab5..4c1a4333da77 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -198,7 +198,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals let amountDescription = `${translate('iou.amount')}`; const hasRoute = TransactionUtils.hasRoute(transactionBackup ?? transaction, isDistanceRequest); - const {unit, rate, currency} = DistanceRequestUtils.getRateForExistingTransaction({transaction, policy}); + const {unit, rate, currency} = DistanceRequestUtils.getRate({transaction, policy}); const distance = TransactionUtils.getDistanceInMeters(transactionBackup ?? transaction, unit); const rateToDisplay = DistanceRequestUtils.getRateForDisplay(unit, rate, currency, translate, toLocaleDigit, isOffline); const distanceToDisplay = DistanceRequestUtils.getDistanceForDisplay(hasRoute, distance, unit, rate, translate); diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index c7881ad59120..e4e4bab7d981 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -306,10 +306,20 @@ function getDistanceUnit(transaction: OnyxEntry, mileageRate: OnyxE } /** - * Get the selected rate for an existing transaction, from the policy or P2P default rate. - * Use the distanceUnit stored on the transaction to prevent policy changes modifying existing transactions. If it's not present fallback to the unit from the rate. + * Get the selected rate for a transaction, from the policy or P2P default rate. + * Use the distanceUnit stored on the transaction by default to prevent policy changes modifying existing transactions. Otherwise, get the unit from the rate. */ -function getRateForExistingTransaction({transaction, policy, policyDraft}: {transaction: OnyxEntry; policy: OnyxEntry; policyDraft?: OnyxEntry}): MileageRate { +function getRate({ + transaction, + policy, + policyDraft, + useTransactionDistanceUnit = true, +}: { + transaction: OnyxEntry; + policy: OnyxEntry; + policyDraft?: OnyxEntry; + useTransactionDistanceUnit?: boolean; +}): MileageRate { let mileageRates = getMileageRates(policy, true, transaction?.comment?.customUnit?.customUnitRateID); if (isEmptyObject(mileageRates) && policyDraft) { mileageRates = getMileageRates(policyDraft, true, transaction?.comment?.customUnit?.customUnitRateID); @@ -318,7 +328,7 @@ function getRateForExistingTransaction({transaction, policy, policyDraft}: {tran const defaultMileageRate = getDefaultMileageRate(policy); const customUnitRateID = TransactionUtils.getRateID(transaction) ?? ''; const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? getRateForP2P(policyCurrency) : mileageRates?.[customUnitRateID] ?? defaultMileageRate; - const unit = getDistanceUnit(transaction, mileageRate); + const unit = getDistanceUnit(useTransactionDistanceUnit ? transaction : undefined, mileageRate); return { ...mileageRate, unit, @@ -330,8 +340,7 @@ function getRateForExistingTransaction({transaction, policy, policyDraft}: {tran * Useful for updating the transaction distance unit when the distance or rate changes. */ function getUpdatedDistanceUnit({transaction, policy, policyDraft}: {transaction: OnyxEntry; policy: OnyxEntry; policyDraft?: OnyxEntry}) { - const mileageRate = getRateForExistingTransaction({transaction, policy, policyDraft}); - return getDistanceUnit(undefined, mileageRate); + return getRate({transaction, policy, policyDraft, useTransactionDistanceUnit: false}).unit; } export default { @@ -347,7 +356,7 @@ export default { getTaxableAmount, getDistanceUnit, getUpdatedDistanceUnit, - getRateForExistingTransaction, + getRate, }; export type {MileageRate}; From 466990b42608f12ec1ec39ed1815f1b9a1d31f1b Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 9 Oct 2024 14:56:18 -0700 Subject: [PATCH 16/28] Fix distance conversion with parenthesis --- src/libs/TransactionUtils/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 32535c68d395..0d12a2038c04 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -291,7 +291,7 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra // If the distanceUnit is set and the rate is changed to one that has a different unit, convert the distance to the new unit if (existingDistanceUnit && newDistanceUnit !== existingDistanceUnit) { const conversionFactor = existingDistanceUnit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? CONST.CUSTOM_UNITS.MILES_TO_KILOMETERS : CONST.CUSTOM_UNITS.KILOMETERS_TO_MILES; - const distance = Math.round(updatedTransaction?.comment?.customUnit?.quantity ?? 0 * conversionFactor * 100) / 100; + const distance = Math.round((updatedTransaction?.comment?.customUnit?.quantity ?? 0) * conversionFactor * 100) / 100; lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance); } } From da47219c6cb80b45223024827b9632d831bfd96e Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 9 Oct 2024 15:00:26 -0700 Subject: [PATCH 17/28] Set distance pending when converted to new rate unit --- src/libs/TransactionUtils/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 0d12a2038c04..5a855c9d33c7 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -293,6 +293,7 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra const conversionFactor = existingDistanceUnit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? CONST.CUSTOM_UNITS.MILES_TO_KILOMETERS : CONST.CUSTOM_UNITS.KILOMETERS_TO_MILES; const distance = Math.round((updatedTransaction?.comment?.customUnit?.quantity ?? 0) * conversionFactor * 100) / 100; lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance); + lodashSet(updatedTransaction, 'pendingFields.waypoints', CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); } } From 8f10f7715fd49557957b7d6d29d038b68405f942 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 9 Oct 2024 15:10:23 -0700 Subject: [PATCH 18/28] Clear pendingFields that aren't part of transactionChanges --- src/libs/actions/IOU.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 1917ea4eddef..e3778494e440 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2488,7 +2488,6 @@ function getUpdateMoneyRequestParams( // Step 1: Set any "pending fields" (ones updated while the user was offline) to have error messages in the failureData const pendingFields = Object.fromEntries(Object.keys(transactionChanges).map((key) => [key, CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE])); - const clearedPendingFields = Object.fromEntries(Object.keys(transactionChanges).map((key) => [key, null])); const errorFields = Object.fromEntries(Object.keys(pendingFields).map((key) => [key, {[DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericEditFailureMessage')}])); const allReports = ReportConnection.getAllReports(); @@ -2725,6 +2724,8 @@ function getUpdateMoneyRequestParams( } } + const clearedPendingFields = Object.fromEntries(Object.keys(updatedTransaction?.pendingFields ?? transactionChanges).map((key) => [key, null])); + // Clear out the error fields and loading states on success successData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -2813,7 +2814,6 @@ function getUpdateTrackExpenseParams( // Step 1: Set any "pending fields" (ones updated while the user was offline) to have error messages in the failureData const pendingFields = Object.fromEntries(Object.keys(transactionChanges).map((key) => [key, CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE])); - const clearedPendingFields = Object.fromEntries(Object.keys(transactionChanges).map((key) => [key, null])); const errorFields = Object.fromEntries(Object.keys(pendingFields).map((key) => [key, {[DateUtils.getMicroseconds()]: Localize.translateLocal('iou.error.genericEditFailureMessage')}])); const allReports = ReportConnection.getAllReports(); @@ -2939,6 +2939,8 @@ function getUpdateTrackExpenseParams( }); } + const clearedPendingFields = Object.fromEntries(Object.keys(updatedTransaction?.pendingFields ?? transactionChanges).map((key) => [key, null])); + // Clear out the error fields and loading states on success successData.push({ onyxMethod: Onyx.METHOD.MERGE, From 5bd5c3a0dfd24adf84f5d456b4a2ee07d54dc9da Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 9 Oct 2024 15:32:56 -0700 Subject: [PATCH 19/28] Fix lint --- src/components/MoneyRequestConfirmationList.tsx | 3 +-- src/components/ReportActionItem/MoneyRequestView.tsx | 3 --- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 3408c0cc4c2f..d4e6678ed032 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -2,7 +2,7 @@ import {useFocusEffect, useIsFocused} from '@react-navigation/native'; import lodashIsEqual from 'lodash/isEqual'; import React, {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {InteractionManager, View} from 'react-native'; -import {useOnyx, withOnyx} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useDebouncedState from '@hooks/useDebouncedState'; @@ -37,7 +37,6 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {Participant} from '@src/types/onyx/IOU'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type {SplitShares} from '@src/types/onyx/Transaction'; -import {isEmptyObject} from '@src/types/utils/EmptyObject'; import ButtonWithDropdownMenu from './ButtonWithDropdownMenu'; import type {DropdownOption} from './ButtonWithDropdownMenu/types'; import FormHelpMessage from './FormHelpMessage'; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 4c1a4333da77..9b6ddb8406d2 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -94,9 +94,6 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals const [parentReportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${parentReportID}`, { canEvict: false, }); - const [distanceRates = {}] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${policyID}`, { - selector: () => DistanceRequestUtils.getMileageRates(policy, true), - }); const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${getTransactionID(report, parentReportActions)}`); const parentReportAction = parentReportActions?.[report?.parentReportActionID ?? '-1']; From a307c8df893aa94cec7bee105ac269a51e07c1f2 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 9 Oct 2024 15:38:12 -0700 Subject: [PATCH 20/28] Fix getting mileageRate.rate --- src/components/MoneyRequestConfirmationList.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index d4e6678ed032..421d755a9f4d 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -238,10 +238,11 @@ function MoneyRequestConfirmationList({ IOU.setCustomUnitRateID(transactionID, rateID); }, [defaultMileageRate, customUnitRateID, lastSelectedDistanceRates, policy?.id, canUseP2PDistanceRequests, transactionID, isDistanceRequest]); - const rate = DistanceRequestUtils.getRate({transaction, policy, policyDraft}); + const mileageRate = DistanceRequestUtils.getRate({transaction, policy, policyDraft}); + const rate = mileageRate.rate; const prevRate = usePrevious(rate); - const unit = rate.unit; - const currency = rate.currency ?? CONST.CURRENCY.USD; + const unit = mileageRate.unit; + const currency = mileageRate.currency ?? CONST.CURRENCY.USD; // A flag for showing the categories field const shouldShowCategories = (isPolicyExpenseChat || isTypeInvoice) && (!!iouCategory || OptionsListUtils.hasEnabledOptions(Object.values(policyCategories ?? {}))); From aded5c22add186f090b6f329bd19323a0f38a58b Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 11 Oct 2024 07:44:40 -0700 Subject: [PATCH 21/28] Ensure currency is always set --- src/libs/DistanceRequestUtils.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index e4e4bab7d981..53178b9dd005 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -332,6 +332,7 @@ function getRate({ return { ...mileageRate, unit, + currency: mileageRate.currency ?? policyCurrency, }; } From 7a9d0ea609c586861fc76f90184ebdf40f8ad8c1 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 11 Oct 2024 08:11:39 -0700 Subject: [PATCH 22/28] Use stored P2P rate for existing transactions --- src/libs/DistanceRequestUtils.ts | 19 ++++++++++++++++--- src/libs/TransactionUtils/index.ts | 2 +- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 53178b9dd005..4c89a69310d0 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -219,17 +219,30 @@ function getDistanceMerchant( return `${distanceInUnits} @ ${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. * * @param currency * @returns The rate and unit in MileageRate object. */ -function getRateForP2P(currency: string): MileageRate { +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); + + // Ensure the rate is updated when the currency changes, otherwise use the stored rate + const rate = TransactionUtils.getCurrency(transaction) === currency ? transaction?.comment?.customUnit?.defaultP2PRate ?? mileageRate.rate : mileageRate.rate; return { - ...CONST.CURRENCY_TO_DEFAULT_MILEAGE_RATE[currencyWithExistingRate], + ...mileageRate, currency: currencyWithExistingRate, + rate, }; } @@ -327,7 +340,7 @@ function getRate({ const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; const defaultMileageRate = getDefaultMileageRate(policy); const customUnitRateID = TransactionUtils.getRateID(transaction) ?? ''; - const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? getRateForP2P(policyCurrency) : mileageRates?.[customUnitRateID] ?? defaultMileageRate; + const mileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) ? getRateForP2P(policyCurrency, transaction) : mileageRates?.[customUnitRateID] ?? defaultMileageRate; const unit = getDistanceUnit(useTransactionDistanceUnit ? transaction : undefined, mileageRate); return { ...mileageRate, diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 756fdf5149bf..1c57ebb306d2 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -820,7 +820,7 @@ function calculateAmountForUpdatedWaypointOrRate( const mileageRates = DistanceRequestUtils.getMileageRates(policy, true); const policyCurrency = policy?.outputCurrency ?? PolicyUtils.getPersonalPolicy()?.outputCurrency ?? CONST.CURRENCY.USD; const mileageRate = isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(policyCurrency) + ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction ?? undefined) : mileageRates?.[customUnitRateID] ?? DistanceRequestUtils.getDefaultMileageRate(policy); const {unit, rate, currency} = mileageRate; From 1e87ca31549664767334529ac1d4bf9d4ee5a24d Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 11 Oct 2024 08:18:18 -0700 Subject: [PATCH 23/28] Explain why the update distance unit should come from the rate --- src/libs/DistanceRequestUtils.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 4c89a69310d0..6e4aed00308d 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -352,6 +352,9 @@ function getRate({ /** * Get the updated distance unit from the selected rate instead of the distanceUnit stored on the transaction. * Useful for updating the transaction distance unit when the distance or rate changes. + * + * 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; From ce80ef171b692bc15f33e5c1e6a1246870bb2651 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Fri, 11 Oct 2024 08:31:16 -0700 Subject: [PATCH 24/28] Apply feedback to clean things up --- src/libs/TransactionUtils/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 1c57ebb306d2..9d22f817dfa8 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -154,7 +154,7 @@ function buildOptimisticTransaction( commentJSON.originalTransactionID = originalTransactionID; } - const isDistanceTransaction = (pendingFields?.waypoints ?? '') !== ''; + const isDistanceTransaction = !!pendingFields?.waypoints; if (isDistanceTransaction) { // Get the policy of this transaction from the report.policyID const allReports = ReportConnection.getAllReports(); @@ -286,12 +286,12 @@ function getUpdatedTransaction(transaction: Transaction, transactionChanges: Tra const policy = PolicyUtils.getPolicy(policyID); // Get the new distance unit from the rate's unit - const newDistanceUnit = DistanceRequestUtils.getUpdatedDistanceUnit({transaction, policy}); + const newDistanceUnit = DistanceRequestUtils.getUpdatedDistanceUnit({transaction: updatedTransaction, policy}); // If the distanceUnit is set and the rate is changed to one that has a different unit, convert the distance to the new unit if (existingDistanceUnit && newDistanceUnit !== existingDistanceUnit) { const conversionFactor = existingDistanceUnit === CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES ? CONST.CUSTOM_UNITS.MILES_TO_KILOMETERS : CONST.CUSTOM_UNITS.KILOMETERS_TO_MILES; - const distance = Math.round((updatedTransaction?.comment?.customUnit?.quantity ?? 0) * conversionFactor * 100) / 100; + const distance = NumberUtils.roundToTwoDecimalPlaces((transaction?.comment?.customUnit?.quantity ?? 0) * conversionFactor); lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance); lodashSet(updatedTransaction, 'pendingFields.waypoints', CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE); } From a2f0074fc27ff3b343941ee155f1ba95ceb97a17 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 15 Oct 2024 12:44:48 -0700 Subject: [PATCH 25/28] Fix some ts errors --- src/libs/DistanceRequestUtils.ts | 2 +- src/types/onyx/Transaction.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/DistanceRequestUtils.ts b/src/libs/DistanceRequestUtils.ts index 6e4aed00308d..be41f88b8297 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -314,7 +314,7 @@ function getTaxableAmount(policy: OnyxEntry, customUnitRateID: string, d return amount * taxClaimablePercentage; } -function getDistanceUnit(transaction: OnyxEntry, mileageRate: OnyxEntry) { +function getDistanceUnit(transaction: OnyxEntry, mileageRate: OnyxEntry): Unit { return transaction?.comment?.customUnit?.distanceUnit ?? mileageRate?.unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES; } diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index 8f876f18cca5..d7d10e07f955 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -5,6 +5,7 @@ import type ONYXKEYS from '@src/ONYXKEYS'; import type CollectionDataSet from '@src/types/utils/CollectionDataSet'; import type {Participant, Split} from './IOU'; import type * as OnyxCommon from './OnyxCommon'; +import type {Unit} from './Policy'; import type RecentWaypoint from './RecentWaypoint'; import type ReportAction from './ReportAction'; import type {ViolationName} from './TransactionViolation'; @@ -102,7 +103,7 @@ type TransactionCustomUnit = { defaultP2PRate?: number | null; /** The unit for the distance/quantity */ - distanceUnit?: 'mi' | 'km'; + distanceUnit?: Unit; }; /** Types of geometry */ From 00eeb4a8c00a29e39d398d438ed8e16f2de08908 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Tue, 15 Oct 2024 13:03:22 -0700 Subject: [PATCH 26/28] Fix if somehow mileageRate is undefined --- 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 be41f88b8297..c52827cec964 100644 --- a/src/libs/DistanceRequestUtils.ts +++ b/src/libs/DistanceRequestUtils.ts @@ -345,7 +345,7 @@ function getRate({ return { ...mileageRate, unit, - currency: mileageRate.currency ?? policyCurrency, + currency: mileageRate?.currency ?? policyCurrency, }; } From 01e652a01d2d4fb88edabc411ef572d9593f9040 Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 16 Oct 2024 11:30:20 -0700 Subject: [PATCH 27/28] Ensure policy is defined for optimistic distance unit --- src/libs/TransactionUtils/index.ts | 7 +------ src/libs/actions/IOU.ts | 2 ++ 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 9d22f817dfa8..2bba5bdd8be1 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -141,6 +141,7 @@ function buildOptimisticTransaction( pendingFields: Partial<{[K in TransactionPendingFieldsKey]: ValueOf}> | undefined = undefined, reimbursable = true, existingTransaction: OnyxEntry | undefined = undefined, + policy: OnyxEntry = undefined, ): Transaction { // transactionIDs are random, positive, 64-bit numeric strings. // Because JS can only handle 53-bit numbers, transactionIDs are strings in the front-end (just like reportActionID) @@ -156,12 +157,6 @@ function buildOptimisticTransaction( const isDistanceTransaction = !!pendingFields?.waypoints; if (isDistanceTransaction) { - // Get the policy of this transaction from the report.policyID - const allReports = ReportConnection.getAllReports(); - const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] ?? null; - const policyID = report?.policyID ?? ''; - const policy = PolicyUtils.getPolicy(policyID); - // Set the distance unit, which comes from the policy distance unit or the P2P rate data lodashSet(commentJSON, 'customUnit.distanceUnit', DistanceRequestUtils.getUpdatedDistanceUnit({transaction: existingTransaction, policy})); } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 3231499fd83d..0746294083e4 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -2101,6 +2101,7 @@ function getMoneyRequestInformation( isDistanceRequest ? {waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : undefined, undefined, existingTransaction, + policy, ); const optimisticPolicyRecentlyUsedCategories = Category.buildOptimisticPolicyRecentlyUsedCategories(iouReport.policyID, category); @@ -2340,6 +2341,7 @@ function getTrackExpenseInformation( isDistanceRequest ? {waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD} : undefined, false, existingTransaction, + policy, ); // If there is an existing transaction (which is the case for distance requests), then the data from the existing transaction From 611ad8bd45923bd6c63b062c8e98fa247b38daee Mon Sep 17 00:00:00 2001 From: neil-marcellini Date: Wed, 16 Oct 2024 11:38:12 -0700 Subject: [PATCH 28/28] Fix ts, missing param --- src/pages/iou/request/step/IOURequestStepDistance.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 78cfab52dafe..899f024de4be 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -125,7 +125,7 @@ function IOURequestStepDistance({ const mileageRates = DistanceRequestUtils.getMileageRates(IOUpolicy); const defaultMileageRate = DistanceRequestUtils.getDefaultMileageRate(IOUpolicy); const mileageRate: MileageRate = TransactionUtils.isCustomUnitRateIDForP2P(transaction) - ? DistanceRequestUtils.getRateForP2P(policyCurrency) + ? DistanceRequestUtils.getRateForP2P(policyCurrency, transaction) : mileageRates?.[customUnitRateID] ?? defaultMileageRate; const {unit, rate} = mileageRate ?? {};