From 059f89b1c68e244aea65647cd64857e086209527 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 1 Sep 2025 04:51:01 +0500 Subject: [PATCH 1/9] add distance rate edits --- src/ROUTES.ts | 9 ++++++ .../MoneyRequestConfirmationListFooter.tsx | 5 +++ .../ReportActionItem/MoneyRequestView.tsx | 8 +++++ src/libs/Navigation/linkingConfig/config.ts | 1 + src/libs/Navigation/types.ts | 26 ++++++--------- src/libs/Permissions.ts | 2 +- src/libs/ReportUtils.ts | 8 +++-- src/libs/TransactionUtils/index.ts | 22 +++++++++++++ src/libs/actions/IOU.ts | 22 ++++++++----- .../step/IOURequestStepConfirmation.tsx | 17 +++++++--- .../step/IOURequestStepDistanceManual.tsx | 32 +++++++++++++------ src/types/onyx/Transaction.ts | 3 ++ 12 files changed, 113 insertions(+), 42 deletions(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index c2448916376a..69cccae63870 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -855,6 +855,15 @@ const ROUTES = { return getUrlWithBackToParam(`${action as string}/${iouType as string}/distance/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo); }, }, + MONEY_REQUEST_STEP_DISTANCE_MANUAL: { + route: ':action/:iouType/distance-manual/:transactionID/:reportID/:reportActionID?', + getRoute: (action: IOUAction, iouType: IOUType, transactionID: string | undefined, reportID: string | undefined, backTo = '', reportActionID?: string) => { + if (!transactionID || !reportID) { + Log.warn('Invalid transactionID or reportID is used to build the MONEY_REQUEST_STEP_DISTANCE_MANUAL route'); + } + return getUrlWithBackToParam(`${action as string}/${iouType as string}/distance-manual/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo); + }, + }, MONEY_REQUEST_STEP_DISTANCE_RATE: { route: ':action/:iouType/distanceRate/:transactionID/:reportID/:reportActionID?', getRoute: (action: IOUAction, iouType: IOUType, transactionID: string | undefined, reportID: string | undefined, backTo = '', reportActionID?: string) => { diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index 52a5af5e4c59..ca9b16131473 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -451,6 +451,11 @@ function MoneyRequestConfirmationListFooter({ return; } + if (isManualDistanceRequest) { + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE_MANUAL.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute(), reportActionID)); + return; + } + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(action, iouType, transactionID, reportID, Navigation.getActiveRoute(), reportActionID)); }} disabled={didConfirm} diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index fcb9d8148467..33231a350691 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -464,6 +464,14 @@ function MoneyRequestView({ if (!transaction?.transactionID || !report?.reportID) { return; } + + if (isManualDistanceRequest) { + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_DISTANCE_MANUAL.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction.transactionID, report.reportID, getReportRHPActiveRoute()), + ); + return; + } + Navigation.navigate( ROUTES.MONEY_REQUEST_STEP_DISTANCE.getRoute(CONST.IOU.ACTION.EDIT, iouType, transaction.transactionID, report.reportID, getReportRHPActiveRoute()), ); diff --git a/src/libs/Navigation/linkingConfig/config.ts b/src/libs/Navigation/linkingConfig/config.ts index 735921529396..1d123180fe1e 100644 --- a/src/libs/Navigation/linkingConfig/config.ts +++ b/src/libs/Navigation/linkingConfig/config.ts @@ -1322,6 +1322,7 @@ const config: LinkingOptions['config'] = { [SCREENS.MONEY_REQUEST.STEP_DATE]: ROUTES.MONEY_REQUEST_STEP_DATE.route, [SCREENS.MONEY_REQUEST.STEP_DESCRIPTION]: ROUTES.MONEY_REQUEST_STEP_DESCRIPTION.route, [SCREENS.MONEY_REQUEST.STEP_DISTANCE]: ROUTES.MONEY_REQUEST_STEP_DISTANCE.route, + [SCREENS.MONEY_REQUEST.STEP_DISTANCE_MANUAL]: ROUTES.MONEY_REQUEST_STEP_DISTANCE_MANUAL.route, [SCREENS.MONEY_REQUEST.STEP_DISTANCE_RATE]: ROUTES.MONEY_REQUEST_STEP_DISTANCE_RATE.route, [SCREENS.MONEY_REQUEST.HOLD]: ROUTES.MONEY_REQUEST_HOLD_REASON.route, [SCREENS.MONEY_REQUEST.STEP_MERCHANT]: ROUTES.MONEY_REQUEST_STEP_MERCHANT.route, diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index b04a9e3b0cd9..d81f327b6698 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1390,6 +1390,15 @@ type MoneyRequestNavigatorParamList = { backToReport?: string; reportActionID?: string; }; + [SCREENS.MONEY_REQUEST.STEP_DISTANCE_MANUAL]: { + action: IOUAction; + iouType: IOUType; + transactionID: string; + reportID: string; + backTo: Routes; + backToReport?: string; + reportActionID?: string; + }; [SCREENS.MONEY_REQUEST.CREATE]: { iouType: IOUType; reportID: string; @@ -1553,23 +1562,6 @@ type MoneyRequestNavigatorParamList = { backToReport?: string; reportActionID?: string; }; - [SCREENS.MONEY_REQUEST.STEP_DISTANCE_MANUAL]: { - action: IOUAction; - iouType: IOUType; - transactionID: string; - reportID: string; - backTo: Routes; - backToReport?: string; - }; - [SCREENS.MONEY_REQUEST.STEP_DISTANCE_MAP]: { - action: IOUAction; - iouType: IOUType; - transactionID: string; - reportID: string; - backTo: Routes; - backToReport?: string; - reportActionID?: string; - }; }; type WorkspaceConfirmationNavigatorParamList = { diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 408df3326275..7a04b537b319 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -23,7 +23,7 @@ function canUseLinkPreviews(): boolean { function isBetaEnabled(beta: Beta, betas: OnyxEntry): boolean { // Remove this check once the manual distance tracking feature is fully rolled out if (beta === CONST.BETAS.MANUAL_DISTANCE) { - return false; + return true; } return !!betas?.includes(beta) || canUseAllBetas(betas); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 161c29b653e7..a5f84805db34 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -254,7 +254,7 @@ import { isDistanceRequest, isExpensifyCardTransaction, isFetchingWaypointsFromServer, - isManualDistanceRequest, + isManualDistanceRequest as isManualDistanceRequestTransactionUtils, isOnHold as isOnHoldTransactionUtils, isPayAtEndExpense, isPending, @@ -709,6 +709,7 @@ type TransactionDetails = { originalAmount: number; originalCurrency: string; postedDate: string; + distance?: number; }; type OptimisticIOUReport = Pick< @@ -3990,6 +3991,8 @@ function getTransactionDetails( return; } const report = getReportOrDraftReport(transaction?.reportID); + const isManualDistanceRequest = isManualDistanceRequestTransactionUtils(transaction); + return { created: getFormattedCreated(transaction, createdDateFormat), amount: getTransactionAmount(transaction, !isEmptyObject(report) && isExpenseReport(report), transaction?.reportID === CONST.REPORT.UNREPORTED_REPORT_ID), @@ -4011,6 +4014,7 @@ function getTransactionDetails( originalAmount: getOriginalAmount(transaction), originalCurrency: getOriginalCurrency(transaction), postedDate: getFormattedPostedDate(transaction), + ...isManualDistanceRequest && {distance: transaction.comment?.customUnit?.quantity ?? undefined}, }; } @@ -4259,7 +4263,7 @@ function canEditFieldOfMoneyRequest( !isInvoiceReport(moneyRequestReport) && !isReceiptBeingScanned(transaction) && !isPerDiemRequest(transaction) && - (!isDistanceRequest(transaction) || isManualDistanceRequest(transaction)) && + (!isDistanceRequest(transaction) || isManualDistanceRequestTransactionUtils(transaction)) && (isAdmin || isManager || isRequestor) && (isDeleteAction ? isRequestor : true) ); diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 464b6331db7a..4e2d6d3d6129 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -589,6 +589,28 @@ function getUpdatedTransaction({ updatedTransaction.receipt.state = CONST.IOU.RECEIPT_STATE.OPEN; } + if (Object.hasOwn(transactionChanges, 'distance') && typeof transactionChanges.distance === 'number') { + const distance = roundToTwoDecimalPlaces((transactionChanges.distance ?? 0)); + + lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance); + shouldStopSmartscan = true; + + + const mileageRate = DistanceRequestUtils.getRate({transaction, policy}); + const {unit, rate} = mileageRate; + const distanceInMeters = getDistanceInMeters(updatedTransaction, mileageRate?.unit); + const amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, rate ?? 0); + const updatedCurrency = mileageRate.currency ?? CONST.CURRENCY.USD; + const updatedMerchant = DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, updatedCurrency, translateLocal, (digit) => + toLocaleDigit(IntlStore.getCurrentLocale(), digit), + ); + + updatedTransaction.amount = amount; + updatedTransaction.modifiedAmount = amount; + updatedTransaction.modifiedMerchant = updatedMerchant; + updatedTransaction.modifiedCurrency = updatedCurrency; + } + updatedTransaction.pendingFields = { ...(updatedTransaction?.pendingFields ?? {}), ...(Object.hasOwn(transactionChanges, 'comment') && {comment: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 63f013e326f0..ecae0046529c 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4856,7 +4856,8 @@ function updateMoneyRequestTaxRate({transactionID, optimisticReportActionID, tax type UpdateMoneyRequestDistanceParams = { transactionID: string | undefined; transactionThreadReportID: string | undefined; - waypoints: WaypointCollection; + waypoints?: WaypointCollection; + distance?: number; routes?: Routes; policy?: OnyxEntry; policyTagList?: OnyxEntry; @@ -4869,15 +4870,18 @@ function updateMoneyRequestDistance({ transactionID, transactionThreadReportID, waypoints, + distance, routes = undefined, policy = {} as OnyxTypes.Policy, policyTagList = {}, policyCategories = {}, transactionBackup, }: UpdateMoneyRequestDistanceParams) { + const transactionChanges: TransactionChanges = { - waypoints: sanitizeRecentWaypoints(waypoints), + ...waypoints && {waypoints: sanitizeRecentWaypoints(waypoints)}, routes, + ...distance && {distance}, }; const transactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null; const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport?.parentReportID}`] ?? null; @@ -4889,12 +4893,14 @@ function updateMoneyRequestDistance({ } const {params, onyxData} = data; - const recentServerValidatedWaypoints = getRecentWaypoints().filter((item) => !item.pendingAction); - onyxData?.failureData?.push({ - onyxMethod: Onyx.METHOD.SET, - key: `${ONYXKEYS.NVP_RECENT_WAYPOINTS}`, - value: recentServerValidatedWaypoints, - }); + if (!distance) { + const recentServerValidatedWaypoints = getRecentWaypoints().filter((item) => !item.pendingAction); + onyxData?.failureData?.push({ + onyxMethod: Onyx.METHOD.SET, + key: `${ONYXKEYS.NVP_RECENT_WAYPOINTS}`, + value: recentServerValidatedWaypoints, + }); + } if (transactionBackup) { const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 55e1d53145e8..2b1adb56b951 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -42,7 +42,17 @@ import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; import Performance from '@libs/Performance'; import {isPaidGroupPolicy} from '@libs/PolicyUtils'; import {generateReportID, getReportOrDraftReport, isProcessingReport, isReportOutstanding, isSelectedManagerMcTest} from '@libs/ReportUtils'; -import {getAttendees, getDefaultTaxCode, getRateID, getRequestType, getValidWaypoints, hasReceipt, isScanRequest} from '@libs/TransactionUtils'; +import { + getAttendees, + getDefaultTaxCode, + getRateID, + getRequestType, + getValidWaypoints, + hasReceipt, + isDistanceRequest as isDistanceRequestTransactionUtils, + isManualDistanceRequest as isManualDistanceRequestTransactionUtils, + isScanRequest +} from '@libs/TransactionUtils'; import type {FileObject} from '@pages/media/AttachmentModalScreen/types'; import type {GpsPoint} from '@userActions/IOU'; import { @@ -165,9 +175,8 @@ function IOURequestStepConfirmation({ const [receiptFiles, setReceiptFiles] = useState>({}); const requestType = getRequestType(transaction, isBetaEnabled(CONST.BETAS.MANUAL_DISTANCE)); - const isDistanceRequest = - requestType === CONST.IOU.REQUEST_TYPE.DISTANCE || requestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MAP || requestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MANUAL; - const isManualDistanceRequest = requestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MANUAL; + const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); + const isManualDistanceRequest = isManualDistanceRequestTransactionUtils(transaction); const isPerDiemRequest = requestType === CONST.IOU.REQUEST_TYPE.PER_DIEM; const [lastLocationPermissionPrompt] = useOnyx(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, {canBeMissing: true}); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx index 0d09f55ab550..71e1b8903016 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx @@ -22,10 +22,11 @@ import { setMoneyRequestParticipantsFromReport, setMoneyRequestPendingFields, trackExpense, + updateMoneyRequestDistance, } from '@libs/actions/IOU'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; -import {navigateToParticipantPage} from '@libs/IOUUtils'; +import {navigateToParticipantPage, shouldUseTransactionDraft} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import {roundToTwoDecimalPlaces} from '@libs/NumberUtils'; import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; @@ -80,9 +81,10 @@ function IOURequestStepDistanceManual({ const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: (val) => val?.reports}); const isEditing = action === CONST.IOU.ACTION.EDIT; - const isSplitBill = iouType === CONST.IOU.TYPE.SPLIT; const isCreatingNewRequest = !(backTo || isEditing); + const isTransactionDraft = shouldUseTransactionDraft(action, iouType); + const customUnitRateID = getRateID(transaction); const unit = DistanceRequestUtils.getRate({transaction, policy}).unit; const distance = transaction?.comment?.customUnit?.quantity ? roundToTwoDecimalPlaces(transaction.comment.customUnit.quantity) : undefined; @@ -95,12 +97,12 @@ function IOURequestStepDistanceManual({ }, [distance]); const shouldSkipConfirmation: boolean = useMemo(() => { - if (isSplitBill || !skipConfirmation || !report?.reportID) { + if (!skipConfirmation || !report?.reportID) { return false; } return !(isArchivedReport(reportNameValuePairs) || isPolicyExpenseChatUtils(report)); - }, [report, isSplitBill, skipConfirmation, reportNameValuePairs]); + }, [report, skipConfirmation, reportNameValuePairs]); useFocusEffect( useCallback(() => { @@ -120,14 +122,11 @@ function IOURequestStepDistanceManual({ const buttonText = useMemo(() => { if (shouldSkipConfirmation) { - if (iouType === CONST.IOU.TYPE.SPLIT) { - return translate('iou.split'); - } return translate('iou.createExpense'); } return isCreatingNewRequest ? translate('common.next') : translate('common.save'); - }, [shouldSkipConfirmation, translate, isCreatingNewRequest, iouType]); + }, [shouldSkipConfirmation, translate, isCreatingNewRequest]); const navigateToConfirmationPage = useCallback(() => { switch (iouType) { @@ -142,7 +141,19 @@ function IOURequestStepDistanceManual({ const navigateToNextPage = useCallback( (amount: string) => { const distanceAsFloat = roundToTwoDecimalPlaces(parseFloat(amount)); - setMoneyRequestDistance(transactionID, distanceAsFloat, isCreatingNewRequest); + setMoneyRequestDistance(transactionID, distanceAsFloat, isTransactionDraft); + + if (action === CONST.IOU.ACTION.EDIT) { + updateMoneyRequestDistance({ + transactionID: transaction?.transactionID, + transactionThreadReportID: transaction?.reportID, + distance: distanceAsFloat, + transactionBackup: undefined, + policy, + }); + Navigation.goBack(backTo); + return; + } if (backTo) { Navigation.goBack(backTo); @@ -253,7 +264,7 @@ function IOURequestStepDistanceManual({ currentUserPersonalDetails.login, currentUserPersonalDetails.accountID, reportNameValuePairs, - isCreatingNewRequest, + isTransactionDraft, activePolicy, shouldSkipConfirmation, personalDetails, @@ -263,6 +274,7 @@ function IOURequestStepDistanceManual({ customUnitRateID, translate, navigateToConfirmationPage, + action, ], ); diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index a2200bae432a..83a414802ed0 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -583,6 +583,9 @@ type AdditionalTransactionChanges = { /** Previous currency before changes */ oldCurrency?: string; + + /** Previous distance before changes */ + distance?: number; }; /** Model of transaction changes */ From 5a2d2a99c874268ba586083b13abc4a68962d74e Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 1 Sep 2025 05:20:40 +0500 Subject: [PATCH 2/9] fix neg amount --- src/libs/TransactionUtils/index.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 4e2d6d3d6129..138dd1f0990e 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -600,13 +600,14 @@ function getUpdatedTransaction({ const {unit, rate} = mileageRate; const distanceInMeters = getDistanceInMeters(updatedTransaction, mileageRate?.unit); const amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, rate ?? 0); + const updatedAmount = isFromExpenseReport || isUnReportedExpense ? amount : -amount; const updatedCurrency = mileageRate.currency ?? CONST.CURRENCY.USD; const updatedMerchant = DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, updatedCurrency, translateLocal, (digit) => toLocaleDigit(IntlStore.getCurrentLocale(), digit), ); - updatedTransaction.amount = amount; - updatedTransaction.modifiedAmount = amount; + updatedTransaction.amount = updatedAmount; + updatedTransaction.modifiedAmount = updatedAmount; updatedTransaction.modifiedMerchant = updatedMerchant; updatedTransaction.modifiedCurrency = updatedCurrency; } From e938447c0d3f3ce2d37394b875ba534b5234008b Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 1 Sep 2025 05:37:13 +0500 Subject: [PATCH 3/9] fix lint issues --- src/libs/Navigation/types.ts | 9 +++++++++ src/libs/Permissions.ts | 2 +- src/libs/ReportUtils.ts | 2 +- src/libs/TransactionUtils/index.ts | 3 +-- src/libs/actions/IOU.ts | 5 ++--- .../iou/request/step/IOURequestStepConfirmation.tsx | 2 +- 6 files changed, 15 insertions(+), 8 deletions(-) diff --git a/src/libs/Navigation/types.ts b/src/libs/Navigation/types.ts index d81f327b6698..743082a914f8 100644 --- a/src/libs/Navigation/types.ts +++ b/src/libs/Navigation/types.ts @@ -1390,6 +1390,15 @@ type MoneyRequestNavigatorParamList = { backToReport?: string; reportActionID?: string; }; + [SCREENS.MONEY_REQUEST.STEP_DISTANCE_MAP]: { + action: IOUAction; + iouType: IOUType; + transactionID: string; + reportID: string; + backTo: Routes; + backToReport?: string; + reportActionID?: string; + }; [SCREENS.MONEY_REQUEST.STEP_DISTANCE_MANUAL]: { action: IOUAction; iouType: IOUType; diff --git a/src/libs/Permissions.ts b/src/libs/Permissions.ts index 7a04b537b319..408df3326275 100644 --- a/src/libs/Permissions.ts +++ b/src/libs/Permissions.ts @@ -23,7 +23,7 @@ function canUseLinkPreviews(): boolean { function isBetaEnabled(beta: Beta, betas: OnyxEntry): boolean { // Remove this check once the manual distance tracking feature is fully rolled out if (beta === CONST.BETAS.MANUAL_DISTANCE) { - return true; + return false; } return !!betas?.includes(beta) || canUseAllBetas(betas); diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a5f84805db34..440ee3b24411 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4014,7 +4014,7 @@ function getTransactionDetails( originalAmount: getOriginalAmount(transaction), originalCurrency: getOriginalCurrency(transaction), postedDate: getFormattedPostedDate(transaction), - ...isManualDistanceRequest && {distance: transaction.comment?.customUnit?.quantity ?? undefined}, + ...(isManualDistanceRequest && {distance: transaction.comment?.customUnit?.quantity ?? undefined}), }; } diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 138dd1f0990e..d1372c31f8d6 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -590,12 +590,11 @@ function getUpdatedTransaction({ } if (Object.hasOwn(transactionChanges, 'distance') && typeof transactionChanges.distance === 'number') { - const distance = roundToTwoDecimalPlaces((transactionChanges.distance ?? 0)); + const distance = roundToTwoDecimalPlaces(transactionChanges.distance ?? 0); lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance); shouldStopSmartscan = true; - const mileageRate = DistanceRequestUtils.getRate({transaction, policy}); const {unit, rate} = mileageRate; const distanceInMeters = getDistanceInMeters(updatedTransaction, mileageRate?.unit); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index ecae0046529c..5a1e0ac92393 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4877,11 +4877,10 @@ function updateMoneyRequestDistance({ policyCategories = {}, transactionBackup, }: UpdateMoneyRequestDistanceParams) { - const transactionChanges: TransactionChanges = { - ...waypoints && {waypoints: sanitizeRecentWaypoints(waypoints)}, + ...(waypoints && {waypoints: sanitizeRecentWaypoints(waypoints)}), routes, - ...distance && {distance}, + ...(distance && {distance}), }; const transactionThreadReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`] ?? null; const parentReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReport?.parentReportID}`] ?? null; diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 2b1adb56b951..63799aa88ee2 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -51,7 +51,7 @@ import { hasReceipt, isDistanceRequest as isDistanceRequestTransactionUtils, isManualDistanceRequest as isManualDistanceRequestTransactionUtils, - isScanRequest + isScanRequest, } from '@libs/TransactionUtils'; import type {FileObject} from '@pages/media/AttachmentModalScreen/types'; import type {GpsPoint} from '@userActions/IOU'; From 028d3fb32fe84c75be25ec2c9c6b124858f8f528 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Tue, 2 Sep 2025 00:00:10 +0500 Subject: [PATCH 4/9] do not make api call if amount is same --- .../step/IOURequestStepDistanceManual.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx index 71e1b8903016..eacb9f9d6961 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx @@ -144,13 +144,15 @@ function IOURequestStepDistanceManual({ setMoneyRequestDistance(transactionID, distanceAsFloat, isTransactionDraft); if (action === CONST.IOU.ACTION.EDIT) { - updateMoneyRequestDistance({ - transactionID: transaction?.transactionID, - transactionThreadReportID: transaction?.reportID, - distance: distanceAsFloat, - transactionBackup: undefined, - policy, - }); + if (distance !== distanceAsFloat) { + updateMoneyRequestDistance({ + transactionID: transaction?.transactionID, + transactionThreadReportID: transaction?.reportID, + distance: distanceAsFloat, + transactionBackup: undefined, + policy, + }); + } Navigation.goBack(backTo); return; } @@ -275,6 +277,7 @@ function IOURequestStepDistanceManual({ translate, navigateToConfirmationPage, action, + distance, ], ); From ec5f5355ecaf7e6d9218294ef03fdb35c1e987df Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Thu, 4 Sep 2025 01:56:28 +0500 Subject: [PATCH 5/9] add better optimisitic handling --- src/libs/ReportUtils.ts | 8 +++---- src/libs/TransactionUtils/index.ts | 22 ++++++++++++------- src/libs/actions/IOU.ts | 1 - .../step/IOURequestStepDistanceManual.tsx | 2 +- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index e309150c58d2..4a50df033121 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4831,15 +4831,15 @@ function getModifiedExpenseOriginalMessage( originalMessage.billable = transactionChanges?.billable ? translateLocal('common.billable').toLowerCase() : translateLocal('common.nonBillable').toLowerCase(); } - if ('customUnitRateID' in transactionChanges && updatedTransaction?.comment?.customUnit?.customUnitRateID) { + if (('customUnitRateID' in transactionChanges && updatedTransaction?.comment?.customUnit?.customUnitRateID) || ('distance' in transactionChanges && updatedTransaction?.comment?.customUnit?.quantity)) { originalMessage.oldAmount = getTransactionAmount(oldTransaction, isFromExpenseReport); originalMessage.oldCurrency = getCurrency(oldTransaction); originalMessage.oldMerchant = getMerchant(oldTransaction); // For the originalMessage, we should use the non-negative amount, similar to what getAmount does for oldAmount - originalMessage.amount = Math.abs(updatedTransaction.modifiedAmount ?? 0); - originalMessage.currency = updatedTransaction.modifiedCurrency ?? CONST.CURRENCY.USD; - originalMessage.merchant = updatedTransaction.modifiedMerchant; + originalMessage.amount = Math.abs(updatedTransaction?.modifiedAmount ?? 0); + originalMessage.currency = updatedTransaction?.modifiedCurrency ?? CONST.CURRENCY.USD; + originalMessage.merchant = updatedTransaction?.modifiedMerchant; } return originalMessage; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index d1372c31f8d6..346fb52365e4 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -595,18 +595,19 @@ function getUpdatedTransaction({ lodashSet(updatedTransaction, 'comment.customUnit.quantity', distance); shouldStopSmartscan = true; - const mileageRate = DistanceRequestUtils.getRate({transaction, policy}); - const {unit, rate} = mileageRate; - const distanceInMeters = getDistanceInMeters(updatedTransaction, mileageRate?.unit); - const amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, rate ?? 0); - const updatedAmount = isFromExpenseReport || isUnReportedExpense ? amount : -amount; - const updatedCurrency = mileageRate.currency ?? CONST.CURRENCY.USD; + const updatedMileageRate = DistanceRequestUtils.getRate({transaction: updatedTransaction, policy, useTransactionDistanceUnit: false}); + const {unit, rate} = updatedMileageRate; + + const distanceInMeters = getDistanceInMeters(updatedTransaction, unit); + let amount = DistanceRequestUtils.getDistanceRequestAmount(distanceInMeters, unit, rate ?? 0); + amount = isFromExpenseReport || isUnReportedExpense ? -amount : amount; + const updatedCurrency = updatedMileageRate.currency ?? CONST.CURRENCY.USD; const updatedMerchant = DistanceRequestUtils.getDistanceMerchant(true, distanceInMeters, unit, rate, updatedCurrency, translateLocal, (digit) => toLocaleDigit(IntlStore.getCurrentLocale(), digit), ); - updatedTransaction.amount = updatedAmount; - updatedTransaction.modifiedAmount = updatedAmount; + updatedTransaction.amount = amount; + updatedTransaction.modifiedAmount = amount; updatedTransaction.modifiedMerchant = updatedMerchant; updatedTransaction.modifiedCurrency = updatedCurrency; } @@ -626,6 +627,11 @@ function getUpdatedTransaction({ ...(Object.hasOwn(transactionChanges, 'taxAmount') && {taxAmount: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(Object.hasOwn(transactionChanges, 'taxCode') && {taxCode: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), ...(Object.hasOwn(transactionChanges, 'attendees') && {attendees: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE}), + ...(Object.hasOwn(transactionChanges, 'distance') && { + quantity: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + amount: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }), }; return updatedTransaction; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 607e2e0585db..d9dc67a4fed6 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -4295,7 +4295,6 @@ function getUpdateMoneyRequestParams( key: `${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, value: { ...updatedTransaction, - pendingFields, errorFields: null, }, }); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx index eacb9f9d6961..d61711881ca3 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx @@ -147,7 +147,7 @@ function IOURequestStepDistanceManual({ if (distance !== distanceAsFloat) { updateMoneyRequestDistance({ transactionID: transaction?.transactionID, - transactionThreadReportID: transaction?.reportID, + transactionThreadReportID: reportID, distance: distanceAsFloat, transactionBackup: undefined, policy, From e1621be852a43951e6b5e1d75b72ae344c580b5f Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Thu, 4 Sep 2025 02:07:15 +0500 Subject: [PATCH 6/9] fix lint issues --- src/libs/ReportUtils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 4a50df033121..a57877af1bac 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4831,7 +4831,11 @@ function getModifiedExpenseOriginalMessage( originalMessage.billable = transactionChanges?.billable ? translateLocal('common.billable').toLowerCase() : translateLocal('common.nonBillable').toLowerCase(); } - if (('customUnitRateID' in transactionChanges && updatedTransaction?.comment?.customUnit?.customUnitRateID) || ('distance' in transactionChanges && updatedTransaction?.comment?.customUnit?.quantity)) { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + if ( + ('customUnitRateID' in transactionChanges && updatedTransaction?.comment?.customUnit?.customUnitRateID) || + ('distance' in transactionChanges && updatedTransaction?.comment?.customUnit?.quantity) + ) { originalMessage.oldAmount = getTransactionAmount(oldTransaction, isFromExpenseReport); originalMessage.oldCurrency = getCurrency(oldTransaction); originalMessage.oldMerchant = getMerchant(oldTransaction); From b92dd60f11d0add0ffa03bf696990ecfc96db773 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Thu, 4 Sep 2025 02:11:17 +0500 Subject: [PATCH 7/9] fix botched prettier call --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index a57877af1bac..8b26878301e3 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -4831,8 +4831,8 @@ function getModifiedExpenseOriginalMessage( originalMessage.billable = transactionChanges?.billable ? translateLocal('common.billable').toLowerCase() : translateLocal('common.nonBillable').toLowerCase(); } - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing if ( + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing ('customUnitRateID' in transactionChanges && updatedTransaction?.comment?.customUnit?.customUnitRateID) || ('distance' in transactionChanges && updatedTransaction?.comment?.customUnit?.quantity) ) { From cbd31103548d557c340a466c3ecc7721b2269b96 Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 15 Sep 2025 05:35:10 +0500 Subject: [PATCH 8/9] handle comments --- src/ROUTES.ts | 1 + src/libs/TransactionUtils/index.ts | 1 - .../step/IOURequestStepDistanceManual.tsx | 1 + tests/unit/TransactionUtilsTest.ts | 61 +++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 56a44749fb48..2109a050227f 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1011,6 +1011,7 @@ const ROUTES = { if (!transactionID || !reportID) { Log.warn('Invalid transactionID or reportID is used to build the MONEY_REQUEST_STEP_DISTANCE_MANUAL route'); } + // eslint-disable-next-line no-restricted-syntax -- Legacy route generation return getUrlWithBackToParam(`${action as string}/${iouType as string}/distance-manual/${transactionID}/${reportID}${reportActionID ? `/${reportActionID}` : ''}`, backTo); }, }, diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index e580b8e11c1b..db552ff1f987 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -606,7 +606,6 @@ function getUpdatedTransaction({ toLocaleDigit(IntlStore.getCurrentLocale(), digit), ); - updatedTransaction.amount = amount; updatedTransaction.modifiedAmount = amount; updatedTransaction.modifiedMerchant = updatedMerchant; updatedTransaction.modifiedCurrency = updatedCurrency; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx index d61711881ca3..fb9bc98ef5b6 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx @@ -149,6 +149,7 @@ function IOURequestStepDistanceManual({ transactionID: transaction?.transactionID, transactionThreadReportID: reportID, distance: distanceAsFloat, + // Not required for manual distance request transactionBackup: undefined, policy, }); diff --git a/tests/unit/TransactionUtilsTest.ts b/tests/unit/TransactionUtilsTest.ts index 09689eedbec5..34c25c08b11f 100644 --- a/tests/unit/TransactionUtilsTest.ts +++ b/tests/unit/TransactionUtilsTest.ts @@ -335,6 +335,67 @@ describe('TransactionUtils', () => { expect(updatedTransaction.taxCode).toBe(taxCode); expect(updatedTransaction.taxAmount).toBe(5); }); + + it('should update transaction when distance is changed', () => { + // Given: a policy with a mileage rate + const fakePolicy: Policy = { + ...createRandomPolicy(0), + customUnits: { + distance: { + name: CONST.CUSTOM_UNITS.NAME_DISTANCE, + customUnitID: 'distance', + rates: { + default: { + customUnitRateID: "1", + currency: CONST.CURRENCY.USD, + rate: 1, // 1 USD per mile + } + }, + attributes: { + unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + }, + }, + }, + }; + const transaction = generateTransaction({ + comment: { + customUnit: { + distanceUnit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, + quantity: 10, // original distance + }, + }, + currency: CONST.CURRENCY.USD, + }); + + const newDistance = 20; // change distance to 20 miles + + // When: updating the transaction with a new distance + const updatedTransaction = TransactionUtils.getUpdatedTransaction({ + transaction, + isFromExpenseReport: false, + policy: fakePolicy, + transactionChanges: {distance: newDistance}, + }); + + // Then: quantity should be updated + expect(updatedTransaction.comment?.customUnit?.quantity).toBe(newDistance); + + // And: amount should be recalculated (20 miles × 1 USD = 20) + expect(updatedTransaction.modifiedAmount).toBe(20); + + // And: merchant should be updated with mileage description + expect(updatedTransaction.modifiedMerchant).toContain('20'); + + // And: currency should be set from policy mileage rate + expect(updatedTransaction.modifiedCurrency).toBe(CONST.CURRENCY.USD); + + // And: pending fields should mark distance-related updates + expect(updatedTransaction.pendingFields).toMatchObject({ + quantity: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + amount: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + merchant: CONST.RED_BRICK_ROAD_PENDING_ACTION.UPDATE, + }); + }); }); describe('shouldShowRTERViolationMessage', () => { From 07913db6e4f58e0362fc3de7f0cfb01d58f5926e Mon Sep 17 00:00:00 2001 From: Sibtain Ali Date: Mon, 15 Sep 2025 05:38:51 +0500 Subject: [PATCH 9/9] prettier --- tests/unit/TransactionUtilsTest.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit/TransactionUtilsTest.ts b/tests/unit/TransactionUtilsTest.ts index 34c25c08b11f..02586c40a4cb 100644 --- a/tests/unit/TransactionUtilsTest.ts +++ b/tests/unit/TransactionUtilsTest.ts @@ -346,10 +346,10 @@ describe('TransactionUtils', () => { customUnitID: 'distance', rates: { default: { - customUnitRateID: "1", + customUnitRateID: '1', currency: CONST.CURRENCY.USD, rate: 1, // 1 USD per mile - } + }, }, attributes: { unit: CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES,