From c3325105122fff0c353a69715fee564c3f339678 Mon Sep 17 00:00:00 2001 From: "Cristi Paval (via MelvinBot)" Date: Thu, 9 Apr 2026 20:00:51 +0000 Subject: [PATCH 1/3] Preserve distance rate for unreported expenses from non-default policies When a distance request is unreported from a report tied to a non-default policy, resolve the policy that owns the customUnitRateID and use it instead of the default moving-expenses policy. This mirrors the existing pattern for perDiem requests. Co-authored-by: Cristi Paval --- .../ReportActionItem/MoneyRequestView.tsx | 16 +++++++++++---- src/hooks/usePolicyForTransaction.ts | 20 ++++++++++++++++--- src/libs/PolicyUtils.ts | 17 ++++++++++++++++ 3 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 3a8bffc0124c..19b102419f30 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -53,6 +53,7 @@ import { getLengthOfTag, getPerDiemCustomUnit, getPolicyByCustomUnitID, + getPolicyByCustomUnitRateID, getTagLists, hasDependentTags as hasDependentTagsPolicyUtils, isAttendeeTrackingEnabled, @@ -209,19 +210,26 @@ function MoneyRequestView({ const [policiesWithPerDiem] = useOnyx(ONYXKEYS.COLLECTION.POLICY, { selector: perDiemPoliciesSelector, }); + const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY); const isPerDiemRequest = isPerDiemRequestTransactionUtils(transaction); const perDiemOriginalPolicy = getPolicyByCustomUnitID(transaction, policiesWithPerDiem); + const isDistanceRequestForPolicy = isDistanceRequestTransactionUtils(transaction); + const distanceOriginalPolicy = isDistanceRequestForPolicy ? getPolicyByCustomUnitRateID(transaction, allPolicies) : undefined; let policy; let policyID; // If the expense is unreported the policy should be the user's default policy, if the expense is a per diem request and is unreported - // the policy should be the one where the per diem rates are enabled, otherwise it should be the expense's report policy - if (isExpenseUnreported && !isPerDiemRequest) { - policy = policyForMovingExpenses; - policyID = policyForMovingExpensesID; + // the policy should be the one where the per diem rates are enabled, if the expense is a distance request and is unreported + // the policy should be the one where the distance rate belongs to, otherwise it should be the expense's report policy + if (isExpenseUnreported && isDistanceRequestForPolicy && distanceOriginalPolicy) { + policy = distanceOriginalPolicy; + policyID = distanceOriginalPolicy?.id; } else if (isExpenseUnreported && isPerDiemRequest) { policy = perDiemOriginalPolicy; policyID = perDiemOriginalPolicy?.id; + } else if (isExpenseUnreported) { + policy = policyForMovingExpenses; + policyID = policyForMovingExpensesID; } else { policy = expensePolicy; policyID = parentReport?.policyID; diff --git a/src/hooks/usePolicyForTransaction.ts b/src/hooks/usePolicyForTransaction.ts index 432ba0e6a704..24914d73907b 100644 --- a/src/hooks/usePolicyForTransaction.ts +++ b/src/hooks/usePolicyForTransaction.ts @@ -1,7 +1,7 @@ import {useMemo} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; -import {getPolicyByCustomUnitID} from '@libs/PolicyUtils'; -import {isExpenseUnreported} from '@libs/TransactionUtils'; +import {getPolicyByCustomUnitID, getPolicyByCustomUnitRateID} from '@libs/PolicyUtils'; +import {isDistanceRequest, isExpenseUnreported} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Policy, Transaction} from '@src/types/onyx'; @@ -42,12 +42,26 @@ function usePolicyForTransaction({transaction, reportPolicyID, action, iouType, return getPolicyByCustomUnitID(transaction, allPolicies); }, [transaction, allPolicies]); + const distanceRatePolicy = useMemo(() => { + return getPolicyByCustomUnitRateID(transaction, allPolicies); + }, [transaction, allPolicies]); + const [reportPolicy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${reportPolicyID}`); const isUnreportedExpense = isExpenseUnreported(transaction); const isCreatingTrackExpense = action === CONST.IOU.ACTION.CREATE && iouType === CONST.IOU.TYPE.TRACK; + const isDistanceExpense = isDistanceRequest(transaction); - const policyForSelfDMExpense = isPerDiemRequest ? customUnitPolicy : policyForMovingExpenses; + const getPolicyForSelfDMExpense = () => { + if (isPerDiemRequest) { + return customUnitPolicy; + } + if (isDistanceExpense && distanceRatePolicy) { + return distanceRatePolicy; + } + return policyForMovingExpenses; + }; + const policyForSelfDMExpense = getPolicyForSelfDMExpense(); const policy = isUnreportedExpense || isCreatingTrackExpense ? policyForSelfDMExpense : (reportPolicy ?? policyDraft); return {policy}; diff --git a/src/libs/PolicyUtils.ts b/src/libs/PolicyUtils.ts index 348c096a970e..0ecd4eac7818 100644 --- a/src/libs/PolicyUtils.ts +++ b/src/libs/PolicyUtils.ts @@ -230,6 +230,22 @@ function getPolicyByCustomUnitID(transaction: OnyxEntry, policies: }); } +/** + * Finds a policy that contains the customUnitRateID from the transaction's distance rate + */ +function getPolicyByCustomUnitRateID(transaction: OnyxEntry, policies: OnyxCollection): OnyxEntry { + const customUnitRateID = transaction?.comment?.customUnit?.customUnitRateID; + + if (!customUnitRateID || !policies) { + return undefined; + } + + return Object.values(policies).find((policy) => { + const distanceUnit = getDistanceRateCustomUnit(policy); + return !!distanceUnit?.rates && customUnitRateID in distanceUnit.rates; + }); +} + /** * Retrieves custom unit rate object from the given customUnitRateID */ @@ -2167,6 +2183,7 @@ export { getDistanceRateCustomUnit, getPerDiemCustomUnit, getPolicyByCustomUnitID, + getPolicyByCustomUnitRateID, getDistanceRateCustomUnitRate, getPerDiemRateCustomUnitRate, sortWorkspacesBySelected, From 8a71d12b7afbbe37e30a1c91e7756bb9a11a45dc Mon Sep 17 00:00:00 2001 From: "Cristi Paval (via MelvinBot)" Date: Thu, 16 Apr 2026 09:11:58 +0000 Subject: [PATCH 2/3] Pass policyID to backend when updating distance requests Add policyID to UpdateMoneyRequestParams and include it in API params for both getUpdateMoneyRequestParams and getUpdateTrackExpenseParams so the backend receives the correct policy context when updating distance. Co-authored-by: cristipaval Co-authored-by: Cristi Paval --- src/libs/API/parameters/UpdateMoneyRequestParams.ts | 1 + src/libs/actions/IOU/UpdateMoneyRequest.ts | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/libs/API/parameters/UpdateMoneyRequestParams.ts b/src/libs/API/parameters/UpdateMoneyRequestParams.ts index 68dbb7a8f679..2d0eb5b55509 100644 --- a/src/libs/API/parameters/UpdateMoneyRequestParams.ts +++ b/src/libs/API/parameters/UpdateMoneyRequestParams.ts @@ -4,6 +4,7 @@ type UpdateMoneyRequestParams = Partial & { reportID?: string; transactionID?: string; reportActionID?: string; + policyID?: string; /** Used for bulk updates - JSON stringified object containing only changed fields */ updates?: string; }; diff --git a/src/libs/actions/IOU/UpdateMoneyRequest.ts b/src/libs/actions/IOU/UpdateMoneyRequest.ts index 5ebc61c00b8a..de983e275eaa 100644 --- a/src/libs/actions/IOU/UpdateMoneyRequest.ts +++ b/src/libs/actions/IOU/UpdateMoneyRequest.ts @@ -964,6 +964,7 @@ function getUpdateMoneyRequestParams(params: GetUpdateMoneyRequestParamsType): U ...dataToIncludeInParams, reportID: iouReport?.reportID, transactionID, + policyID: policy?.id, }; const hasPendingWaypoints = 'waypoints' in transactionChanges; @@ -1482,6 +1483,7 @@ function getUpdateTrackExpenseParams( ...dataToIncludeInParams, reportID: chatReport?.reportID, transactionID, + policyID: policy?.id, }; const hasPendingWaypoints = 'waypoints' in transactionChanges; From f7da9235bd295352b10e6b83cd3241c906e2c9d7 Mon Sep 17 00:00:00 2001 From: "Cristi Paval (via MelvinBot)" Date: Tue, 21 Apr 2026 08:05:50 +0000 Subject: [PATCH 3/3] Use default policy for distance rate picker on unreported expenses The rate picker was showing rates from the original non-default workspace instead of the user's default workspace for unreported distance expenses. This decouples the rate display (which correctly uses the original policy) from the rate picker list (which now uses policyForMovingExpenses). Co-authored-by: Cristi Paval --- .../step/IOURequestStepDistanceRate.tsx | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx index 3ff9a2f0a605..6eba9cf668d6 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceRate.tsx @@ -10,6 +10,7 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails' import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import usePermissions from '@hooks/usePermissions'; +import usePolicyForMovingExpenses from '@hooks/usePolicyForMovingExpenses'; import usePolicyForTransaction from '@hooks/usePolicyForTransaction'; import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -66,6 +67,7 @@ function IOURequestStepDistanceRate({ const [splitDraftTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`); const {policy} = usePolicyForTransaction({transaction, reportPolicyID: report?.policyID, action, iouType, policyDraft}); + const {policyForMovingExpenses} = usePolicyForMovingExpenses(); const styles = useThemeStyles(); const {translate, toLocaleDigit, localeCompare} = useLocalize(); @@ -76,7 +78,8 @@ function IOURequestStepDistanceRate({ const {getCurrencySymbol, getCurrencyDecimals} = useCurrencyListActions(); const isPolicyExpenseChat = isReportInGroupPolicy(report); const isTrackExpense = iouType === CONST.IOU.TYPE.TRACK; - const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat || isTrackExpense || isExpenseUnreported(currentTransaction), policy, isDistanceRequest); + const isUnreportedExpense = isExpenseUnreported(currentTransaction); + const shouldShowTax = isTaxTrackingEnabled(isPolicyExpenseChat || isTrackExpense || isUnreportedExpense, policy, isDistanceRequest); const currentRateID = getRateID(currentTransaction); const transactionCurrency = getCurrency(currentTransaction); @@ -91,7 +94,11 @@ function IOURequestStepDistanceRate({ // This keeps the problematic rate shown as selected so the user understands what they need to change. const [pendingRateID, setPendingRateID] = useState(); - const rates = DistanceRequestUtils.getMileageRates(policy, false, currentRateID); + // For unreported distance expenses, use the default policy for the rate picker list + // so users see rates from their default workspace, not the original non-default workspace. + // The `policy` (from usePolicyForTransaction) still reflects the original policy for display purposes. + const policyForRates = isUnreportedExpense && isDistanceRequest && policyForMovingExpenses ? policyForMovingExpenses : policy; + const rates = DistanceRequestUtils.getMileageRates(policyForRates, false, currentRateID); const isMovingTransactionFromTrackExpense = isMovingTransactionFromTrackExpenseUtil(action); const transactionUnit = transaction?.comment?.customUnit?.distanceUnit; const sortedRates = useMemo(() => Object.values(rates).sort((a, b) => localeCompare(a.name ?? '', b.name ?? '')), [rates, localeCompare]); @@ -109,7 +116,7 @@ function IOURequestStepDistanceRate({ const effectiveRateID = pendingRateID ?? currentRateID; const isSelected = effectiveRateID ? effectiveRateID === rate.customUnitRateID && !hasUnitMismatchForMovingTrackExpense - : DistanceRequestUtils.getDefaultMileageRate(policy)?.customUnitRateID === rate.customUnitRateID; + : DistanceRequestUtils.getDefaultMileageRate(policyForRates)?.customUnitRateID === rate.customUnitRateID; const rateForDisplay = DistanceRequestUtils.getFormattedRateValue(unit, rate.rate, isSelected ? transactionCurrency : rate.currency, translate, toLocaleDigit, getCurrencySymbol); return { text: rate.name ?? rateForDisplay, @@ -152,12 +159,12 @@ function IOURequestStepDistanceRate({ let taxRateExternalID; let taxValue; if (shouldShowTax) { - const policyCustomUnitRate = getDistanceRateCustomUnitRate(policy, customUnitRateID); - const defaultTaxCode = getDefaultTaxCode(policy, currentTransaction) ?? ''; + const policyCustomUnitRate = getDistanceRateCustomUnitRate(policyForRates, customUnitRateID); + const defaultTaxCode = getDefaultTaxCode(policyForRates, currentTransaction) ?? ''; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing taxRateExternalID = policyCustomUnitRate?.attributes?.taxRateExternalID || defaultTaxCode; - const taxableAmount = DistanceRequestUtils.getTaxableAmount(policy, customUnitRateID, getDistanceInMeters(currentTransaction, currentUnit)); - taxValue = taxRateExternalID ? getTaxValue(policy, currentTransaction, taxRateExternalID) : undefined; + const taxableAmount = DistanceRequestUtils.getTaxableAmount(policyForRates, customUnitRateID, getDistanceInMeters(currentTransaction, currentUnit)); + taxValue = taxRateExternalID ? getTaxValue(policyForRates, currentTransaction, taxRateExternalID) : undefined; taxAmount = convertToBackendAmount(calculateTaxAmount(taxValue, taxableAmount, getCurrencyDecimals(rates[customUnitRateID].currency))); setMoneyRequestTaxAmount(transactionID, taxAmount, shouldUseTransactionDraft(action)); setMoneyRequestTaxRate(transactionID, taxRateExternalID ?? null, shouldUseTransactionDraft(action)); @@ -167,12 +174,12 @@ function IOURequestStepDistanceRate({ if (currentRateID !== customUnitRateID || (isMovingTransactionFromTrackExpense && transactionUnit !== selectedRateUnit)) { // In the split flow, when editing we use SPLIT_TRANSACTION_DRAFT to save draft value if (isEditingSplit && transaction) { - setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {customUnitRateID}, policy); + setDraftSplitTransaction(transaction.transactionID, splitDraftTransaction, {customUnitRateID}, policyForRates); navigateBack(); return; } - setMoneyRequestDistanceRate(transaction, customUnitRateID, policy, shouldUseTransactionDraft(action)); + setMoneyRequestDistanceRate(transaction, customUnitRateID, policyForRates, shouldUseTransactionDraft(action)); if (isEditing && transaction?.transactionID) { updateMoneyRequestDistanceRate({ @@ -181,7 +188,7 @@ function IOURequestStepDistanceRate({ parentReport, parentReportNextStep, rateID: customUnitRateID, - policy, + policy: policyForRates, policyTagList: policyTags, policyCategories, currentUserAccountIDParam,