From 7be9ce24eb5bd9c02adc0df4ed76ad28fc4841a8 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Mon, 19 Jan 2026 18:18:57 +0100 Subject: [PATCH 01/20] Create GPS expenses using captured data --- .../MoneyRequestConfirmationList.tsx | 5 + .../MoneyRequestConfirmationListFooter.tsx | 8 +- .../ReportActionItem/MoneyRequestView.tsx | 15 +- .../TransactionPreviewContent.tsx | 4 +- src/hooks/useFetchRoute.ts | 4 +- .../parameters/CreateDistanceRequestParams.ts | 1 + src/libs/API/parameters/TrackExpenseParams.ts | 1 + src/libs/ReportUtils.ts | 1 + src/libs/TransactionUtils/index.ts | 22 +- src/libs/actions/IOU/index.ts | 114 +++++++++- src/pages/Share/SubmitDetailsPage.tsx | 1 + src/pages/iou/SplitBillDetailsPage.tsx | 13 +- .../step/IOURequestStepConfirmation.tsx | 15 +- .../DistanceCounter/index.tsx | 20 +- .../index.native.tsx | 209 +++++++++++++++++- tests/ui/MoneyRequestReportFooter.tsx | 1 + 16 files changed, 408 insertions(+), 26 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index ecc196fb3868..832e6807bff3 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -156,6 +156,9 @@ type MoneyRequestConfirmationListProps = { /** Whether the expense is an odometer distance expense */ isOdometerDistanceRequest?: boolean; + /** Whether the expense is a GPS distance expense */ + isGPSDistanceRequest: boolean; + /** Whether the expense is a per diem expense */ isPerDiemRequest?: boolean; @@ -221,6 +224,7 @@ function MoneyRequestConfirmationList({ isDistanceRequest, isManualDistanceRequest, isOdometerDistanceRequest = false, + isGPSDistanceRequest = false, isPerDiemRequest = false, isPolicyExpenseChat = false, iouCategory = '', @@ -1175,6 +1179,7 @@ function MoneyRequestConfirmationList({ isDistanceRequest={isDistanceRequest} isManualDistanceRequest={isManualDistanceRequest} isOdometerDistanceRequest={isOdometerDistanceRequest} + isGPSDistanceRequest={isGPSDistanceRequest} isPerDiemRequest={isPerDiemRequest} isMerchantEmpty={isMerchantEmpty} isMerchantRequired={isMerchantRequired} diff --git a/src/components/MoneyRequestConfirmationListFooter.tsx b/src/components/MoneyRequestConfirmationListFooter.tsx index aa53c6de3291..a47bdeca7dd4 100644 --- a/src/components/MoneyRequestConfirmationListFooter.tsx +++ b/src/components/MoneyRequestConfirmationListFooter.tsx @@ -117,6 +117,9 @@ type MoneyRequestConfirmationListFooterProps = { /** Flag indicating if it is an odometer distance request */ isOdometerDistanceRequest?: boolean; + /** Flag indicating if it is a GPS distance request */ + isGPSDistanceRequest: boolean; + /** Flag indicating if it is a per diem request */ isPerDiemRequest: boolean; @@ -235,6 +238,7 @@ function MoneyRequestConfirmationListFooter({ isDistanceRequest, isManualDistanceRequest, isOdometerDistanceRequest = false, + isGPSDistanceRequest, isPerDiemRequest, isMerchantEmpty, isMerchantRequired, @@ -494,7 +498,7 @@ function MoneyRequestConfirmationListFooter({ item: ( ), shouldShow: isDistanceRequest, diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 894eb7fd0e1e..f158a698da99 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -90,6 +90,7 @@ import { isCategoryBeingAnalyzed, isDistanceRequest as isDistanceRequestTransactionUtils, isExpenseUnreported as isExpenseUnreportedTransactionUtils, + isGPSDistanceRequest as isGPSDistanceRequestTransactionUtils, isManualDistanceRequest as isManualDistanceRequestTransactionUtils, isOdometerDistanceRequest as isOdometerDistanceRequestTransactionUtils, isPerDiemRequest as isPerDiemRequestTransactionUtils, @@ -278,8 +279,9 @@ function MoneyRequestView({ const isEmptyMerchant = transactionMerchant === '' || transactionMerchant === CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT; const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const isManualDistanceRequest = isManualDistanceRequestTransactionUtils(transaction, !!mergeTransactionID); + const isGPSDistanceRequest = isGPSDistanceRequestTransactionUtils(transaction); const isOdometerDistanceRequest = isOdometerDistanceRequestTransactionUtils(transaction); - const isMapDistanceRequest = isDistanceRequest && !isManualDistanceRequest; + const isMapDistanceRequest = isDistanceRequest && !isManualDistanceRequest && !isGPSDistanceRequest; const isTransactionScanning = isScanning(updatedTransaction ?? transaction); const hasRoute = hasRouteTransactionUtils(transactionBackup ?? transaction, isDistanceRequest); @@ -327,13 +329,18 @@ function MoneyRequestView({ const canEditTaxFields = canEdit && !isDistanceRequest; const canEditAmount = - isEditable && (canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT, undefined, isChatReportArchived) || (isExpenseSplit && isSplitAvailable)); + !isGPSDistanceRequest && + isEditable && + (canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT, undefined, isChatReportArchived) || (isExpenseSplit && isSplitAvailable)); const canEditMerchant = isEditable && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.MERCHANT, undefined, isChatReportArchived); const canEditDate = isEditable && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DATE, undefined, isChatReportArchived); const canEditDistance = - isEditable && canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE, undefined, isChatReportArchived) && isPolicyAccessible(policy, currentUserEmailParam); + !isGPSDistanceRequest && + isEditable && + canEditFieldOfMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.DISTANCE, undefined, isChatReportArchived) && + isPolicyAccessible(policy, currentUserEmailParam); const canEditDistanceRate = isEditable && @@ -866,7 +873,7 @@ function MoneyRequestView({ copyable={!!descriptionCopyValue} /> - {isManualDistanceRequest || isOdometerDistanceRequest || (isMapDistanceRequest && transaction?.comment?.waypoints) ? ( + {isManualDistanceRequest || isGPSDistanceRequest || isOdometerDistanceRequest || (isMapDistanceRequest && transaction?.comment?.waypoints) ? ( distanceRequestFields ) : ( diff --git a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx index a417b3917126..2e73a191d4c7 100644 --- a/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx +++ b/src/components/ReportActionItem/TransactionPreview/TransactionPreviewContent.tsx @@ -32,7 +32,7 @@ import {canEditMoneyRequest, getTransactionDetails, isPolicyExpenseChat, isRepor import StringUtils from '@libs/StringUtils'; import type {TranslationPathOrText} from '@libs/TransactionPreviewUtils'; import {createTransactionPreviewConditionals, getIOUPayerAndReceiver, getTransactionPreviewTextAndTranslationPaths} from '@libs/TransactionPreviewUtils'; -import {isManagedCardTransaction as isCardTransactionUtils, isMapDistanceRequest, isScanning} from '@libs/TransactionUtils'; +import {isManagedCardTransaction as isCardTransactionUtils, isGPSDistanceRequest, isMapDistanceRequest, isScanning} from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -241,7 +241,7 @@ function TransactionPreviewContent({ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing isHovered={isHovered || isTransactionScanning} size={1} - shouldUseAspectRatio={!isMapDistanceRequest(transaction)} + shouldUseAspectRatio={!isMapDistanceRequest(transaction) && !isGPSDistanceRequest(transaction)} /> {shouldShowSkeleton ? ( diff --git a/src/hooks/useFetchRoute.ts b/src/hooks/useFetchRoute.ts index b5aae9700906..a384bb82b4e8 100644 --- a/src/hooks/useFetchRoute.ts +++ b/src/hooks/useFetchRoute.ts @@ -2,7 +2,7 @@ import {deepEqual} from 'fast-equals'; import {useEffect} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {getRoute} from '@libs/actions/Transaction'; -import {getValidWaypoints, hasRoute as hasRouteTransactionUtils, isDistanceRequest, isManualDistanceRequest} from '@libs/TransactionUtils'; +import {getValidWaypoints, hasRoute as hasRouteTransactionUtils, isDistanceRequest, isGPSDistanceRequest, isManualDistanceRequest} from '@libs/TransactionUtils'; import type {IOUAction} from '@src/CONST'; import CONST from '@src/CONST'; import type {Transaction} from '@src/types/onyx'; @@ -25,7 +25,7 @@ export default function useFetchRoute( const validatedWaypoints = getValidWaypoints(waypoints); const previousValidatedWaypoints = usePrevious(validatedWaypoints); const haveValidatedWaypointsChanged = !deepEqual(previousValidatedWaypoints, validatedWaypoints); - const isMapDistanceRequest = isDistanceRequest(transaction) && !isManualDistanceRequest(transaction); + const isMapDistanceRequest = isDistanceRequest(transaction) && !isManualDistanceRequest(transaction) && !isGPSDistanceRequest(transaction); const shouldFetchRoute = isMapDistanceRequest && (isRouteAbsentWithoutErrors || haveValidatedWaypointsChanged) && !isLoadingRoute && Object.keys(validatedWaypoints).length > 1; useEffect(() => { diff --git a/src/libs/API/parameters/CreateDistanceRequestParams.ts b/src/libs/API/parameters/CreateDistanceRequestParams.ts index 04a853d17be6..7e26ec749358 100644 --- a/src/libs/API/parameters/CreateDistanceRequestParams.ts +++ b/src/libs/API/parameters/CreateDistanceRequestParams.ts @@ -29,6 +29,7 @@ type CreateDistanceRequestParams = { distance?: number; odometerStart?: number; odometerEnd?: number; + gpsCoordinates?: string; }; export default CreateDistanceRequestParams; diff --git a/src/libs/API/parameters/TrackExpenseParams.ts b/src/libs/API/parameters/TrackExpenseParams.ts index 9b653d5a2a3c..f01e6c107e9a 100644 --- a/src/libs/API/parameters/TrackExpenseParams.ts +++ b/src/libs/API/parameters/TrackExpenseParams.ts @@ -35,6 +35,7 @@ type TrackExpenseParams = { customUnitRateID?: string; description?: string; distance?: number; + gpsCoordinates?: string; }; export default TrackExpenseParams; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 767a7b9e51f5..5592ae101c11 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -795,6 +795,7 @@ type TransactionDetails = { odometerStart?: number; odometerEnd?: number; convertedAmount: number; + gpsCoordinates?: string; }; type OptimisticIOUReport = Pick< diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 1d0a5f194962..4c36399d50d9 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -114,6 +114,7 @@ type TransactionParams = { distance?: number; odometerStart?: number; odometerEnd?: number; + gpsCoordinates?: string; type?: ValueOf; count?: number; rate?: number; @@ -150,8 +151,9 @@ function isDistanceRequest(transaction: OnyxEntry): boolean { return ( transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE || transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MAP || - transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MANUAL || - transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER + transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER || + transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_GPS || + transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MANUAL ); } @@ -179,6 +181,18 @@ function isMapDistanceRequest(transaction: OnyxEntry): boolean { return hasDistanceCustomUnit(transaction); } +function isGPSDistanceRequest(transaction: OnyxEntry): boolean { + // This is used during the expense creation flow before the transaction has been saved to the server + if (lodashHas(transaction, 'iouRequestType')) { + return transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_GPS; + } + + // todo(@GCyganek): currently there is no way to tell server map transaction object from + // server gps transaction object, this will be discussed and updated later + // This is the case for transaction objects once they have been saved to the server + return hasDistanceCustomUnit(transaction); +} + function isManualDistanceRequest(transaction: OnyxEntry, isUpdatedMergeTransaction = false): boolean { // This is used during the expense creation flow before the transaction has been saved to the server if (lodashHas(transaction, 'iouRequestType') && !isUpdatedMergeTransaction) { @@ -267,6 +281,9 @@ function getRequestType(transaction: OnyxEntry): IOURequestType { if (isTimeRequest(transaction)) { return CONST.IOU.REQUEST_TYPE.TIME; } + if (isGPSDistanceRequest(transaction)) { + return CONST.IOU.REQUEST_TYPE.DISTANCE_GPS; + } return CONST.IOU.REQUEST_TYPE.MANUAL; } @@ -2596,6 +2613,7 @@ export { getValidDuplicateTransactionIDs, isDistanceRequest, isMapDistanceRequest, + isGPSDistanceRequest, isManualDistanceRequest, isOdometerDistanceRequest, isFetchingWaypointsFromServer, diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 420b54f3d486..caaba5786b79 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -214,6 +214,7 @@ import { isDistanceRequest as isDistanceRequestTransactionUtils, isDuplicate, isFetchingWaypointsFromServer, + isGPSDistanceRequest as isGPSDistanceRequestTransactionUtils, isManualDistanceRequest as isManualDistanceRequestTransactionUtils, isMapDistanceRequest, isOdometerDistanceRequest as isOdometerDistanceRequestTransactionUtils, @@ -606,6 +607,7 @@ type DistanceRequestTransactionParams = BaseTransactionParams & { receipt?: Receipt; odometerStart?: number; odometerEnd?: number; + gpsCoordinates?: string; }; type CreateDistanceRequestInformation = { @@ -668,6 +670,7 @@ type TrackExpenseTransactionParams = { isLinkedTrackedExpenseReportArchived?: boolean; odometerStart?: number; odometerEnd?: number; + gpsCoordinates?: string; }; type TrackExpenseAccountantParams = { @@ -711,6 +714,7 @@ type GetTrackExpenseInformationTransactionParams = { distance?: number; odometerStart?: number; odometerEnd?: number; + gpsCoordinates?: string; }; type GetTrackExpenseInformationParticipantParams = { @@ -952,6 +956,12 @@ Onyx.connect({ callback: (val) => (recentWaypoints = val ?? []), }); +let firstCreatedGpsExpenseDateNewDot: OnyxEntry; +Onyx.connect({ + key: ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, + callback: (val) => (firstCreatedGpsExpenseDateNewDot = val), +}); + function getAllPersonalDetails(): OnyxTypes.PersonalDetailsList { return allPersonalDetails; } @@ -1113,7 +1123,8 @@ function initMoneyRequest({ newIouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE || newIouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MAP || newIouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MANUAL || - newIouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER + newIouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER || + newIouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_GPS ) { if (!isFromGlobalCreate) { const isPolicyExpenseChat = isPolicyExpenseChatReportUtil(report) || isPolicyExpenseChatReportUtil(parentReport); @@ -1359,6 +1370,63 @@ function setCustomUnitRateID(transactionID: string, customUnitRateID: string | u }); } +function getGPSWaypoints(gpsDraftDetails: OnyxTypes.GpsDraftDetails | undefined): WaypointCollection { + const gpsCoordinates = gpsDraftDetails?.gpsPoints ?? []; + const firstPoint = gpsCoordinates.at(0); + const lastPoint = gpsCoordinates.at(-1); + + return { + ...(firstPoint + ? { + waypoint0: { + lat: firstPoint.lat, + lng: firstPoint.long, + address: gpsDraftDetails?.startAddress.value ?? '', + name: gpsDraftDetails?.startAddress.value ?? '', + }, + } + : {}), + ...(lastPoint + ? { + waypoint1: { + lat: lastPoint.lat, + lng: lastPoint.long, + address: gpsDraftDetails?.endAddress.value ?? '', + name: gpsDraftDetails?.endAddress.value ?? '', + }, + } + : {}), + }; +} + +function getGPSRoutes(gpsDraftDetails: OnyxTypes.GpsDraftDetails | undefined): Routes { + const distanceInMeters = parseFloat((gpsDraftDetails?.distanceInMeters ?? 0).toFixed(2)); + const gpsCoordinates = gpsDraftDetails?.gpsPoints ?? []; + + return { + route0: { + distance: distanceInMeters, + geometry: { + type: 'LineString', + coordinates: gpsCoordinates.map(({lat, long}) => [long, lat]), + }, + }, + }; +} + +function setGPSTransactionDraftData(transactionID: string, gpsDraftDetails: OnyxTypes.GpsDraftDetails | undefined, distance: number) { + const waypoints = getGPSWaypoints(gpsDraftDetails); + const routes = getGPSRoutes(gpsDraftDetails); + + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, { + comment: { + customUnit: {quantity: distance}, + waypoints, + }, + routes, + }); +} + /** * Revert custom unit of the draft transaction to the original transaction's value */ @@ -3151,7 +3219,8 @@ function getMoneyRequestInformation(moneyRequestInformation: MoneyRequestInforma (existingTransaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE || existingTransaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MAP || existingTransaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MANUAL || - existingTransaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER); + existingTransaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER || + existingTransaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_GPS); const isManualDistanceRequest = existingTransaction && existingTransaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MANUAL; let optimisticTransaction = buildOptimisticTransaction({ existingTransactionID: optimisticTransactionID, @@ -3713,6 +3782,7 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T attendees, odometerStart, odometerEnd, + gpsCoordinates, } = transactionParams; const optimisticData: OnyxUpdate[] = []; @@ -3890,6 +3960,7 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T const isDistanceRequest = existingTransaction && isDistanceRequestTransactionUtils(existingTransaction); const isManualDistanceRequest = existingTransaction && isManualDistanceRequestTransactionUtils(existingTransaction); const isOdometerDistanceRequest = existingTransaction && isOdometerDistanceRequestTransactionUtils(existingTransaction); + const isGPSDistanceRequest = existingTransaction && isGPSDistanceRequestTransactionUtils(existingTransaction); let optimisticTransaction = buildOptimisticTransaction({ existingTransactionID: optimisticTransactionID, existingTransaction, @@ -3914,6 +3985,7 @@ function getTrackExpenseInformation(params: GetTrackExpenseInformationParams): T attendees, odometerStart: isOdometerDistanceRequest ? odometerStart : undefined, odometerEnd: isOdometerDistanceRequest ? odometerEnd : undefined, + gpsCoordinates: isGPSDistanceRequest ? gpsCoordinates : undefined, }, }); if (iouReport) { @@ -6401,6 +6473,7 @@ function trackExpense(params: CreateTrackExpenseParams) { attendees, odometerStart, odometerEnd, + gpsCoordinates, } = transactionData; const isMoneyRequestReport = isMoneyRequestReportReportUtils(report); const currentChatReport = isMoneyRequestReport ? getReportOrDraftReport(report?.chatReportID) : report; @@ -6491,6 +6564,7 @@ function trackExpense(params: CreateTrackExpenseParams) { attendees, odometerStart, odometerEnd, + gpsCoordinates, }, policyParams: { policy, @@ -6514,7 +6588,9 @@ function trackExpense(params: CreateTrackExpenseParams) { value: recentServerValidatedWaypoints, }); - if (isMapDistanceRequest(transaction) || isManualDistanceRequestTransactionUtils(transaction) || isOdometerDistanceRequestTransactionUtils(transaction)) { + const isGPSDistanceRequest = isGPSDistanceRequestTransactionUtils(transaction); + + if (isMapDistanceRequest(transaction) || isManualDistanceRequestTransactionUtils(transaction) || isOdometerDistanceRequestTransactionUtils(transaction) || isGPSDistanceRequest) { // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 onyxData?.optimisticData?.push({ onyxMethod: Onyx.METHOD.SET, @@ -6523,6 +6599,14 @@ function trackExpense(params: CreateTrackExpenseParams) { }); } + if (isGPSDistanceRequest && !firstCreatedGpsExpenseDateNewDot) { + onyxData?.optimisticData?.push({ + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, + value: DateUtils.getDBTime(), + }); + } + const mileageRate = isCustomUnitRateIDForP2P(transaction) ? undefined : customUnitRateID; if (shouldPlaySound) { playSound(SOUNDS.DONE); @@ -6664,10 +6748,12 @@ function trackExpense(params: CreateTrackExpenseParams) { waypoints: sanitizedWaypoints, customUnitRateID, description: parsedComment, + gpsCoordinates, }; if (actionableWhisperReportActionIDParam) { parameters.actionableWhisperReportActionID = actionableWhisperReportActionIDParam; } + API.write(WRITE_COMMANDS.TRACK_EXPENSE, parameters, onyxData); } } @@ -8189,10 +8275,12 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest receipt, odometerStart, odometerEnd, + gpsCoordinates, } = transactionParams; // 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 = isMoneyRequestReportReportUtils(report); + const isGPSDistanceRequest = !!gpsCoordinates; const currentChatReport = isMoneyRequestReport ? getReportOrDraftReport(report?.chatReportID) : report; const moneyRequestReportID = isMoneyRequestReport ? report?.reportID : ''; const isManualDistanceRequest = isEmptyObject(validWaypoints); @@ -8264,6 +8352,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest attendees: attendees ? JSON.stringify(attendees) : undefined, odometerStart, odometerEnd, + gpsCoordinates, }; } else { const participant = participants.at(0) ?? {}; @@ -8321,7 +8410,12 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest onyxData = moneyRequestOnyxData; - if (transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MAP || isManualDistanceRequest || transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER) { + if ( + transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_MAP || + transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_GPS || + isManualDistanceRequest || + transaction.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_ODOMETER + ) { // @ts-expect-error - will be solved in https://github.com/Expensify/App/issues/73830 onyxData?.optimisticData?.push({ onyxMethod: Onyx.METHOD.SET, @@ -8330,6 +8424,14 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest }); } + if (isGPSDistanceRequest && !firstCreatedGpsExpenseDateNewDot) { + onyxData?.optimisticData?.push({ + onyxMethod: Onyx.METHOD.SET, + key: ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, + value: DateUtils.getDBTime(), + }); + } + parameters = { comment, iouReportID: iouReport.reportID, @@ -8357,6 +8459,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest customUnitRateID, description: parsedComment, attendees: attendees ? JSON.stringify(attendees) : undefined, + gpsCoordinates, }; } @@ -14209,6 +14312,9 @@ export { resetDraftTransactionsCustomUnit, savePreferredPaymentMethod, setCustomUnitRateID, + getGPSWaypoints, + getGPSRoutes, + setGPSTransactionDraftData, setCustomUnitID, removeSubrate, addSubrate, diff --git a/src/pages/Share/SubmitDetailsPage.tsx b/src/pages/Share/SubmitDetailsPage.tsx index cec6bab7efff..7c3c05ebfbd2 100644 --- a/src/pages/Share/SubmitDetailsPage.tsx +++ b/src/pages/Share/SubmitDetailsPage.tsx @@ -274,6 +274,7 @@ function SubmitDetailsPage({ shouldShowSmartScanFields={false} isDistanceRequest={false} isManualDistanceRequest={false} + isGPSDistanceRequest={false} onPDFLoadError={() => { if (errorTitle) { return; diff --git a/src/pages/iou/SplitBillDetailsPage.tsx b/src/pages/iou/SplitBillDetailsPage.tsx index 0f41962fde96..e77c8837091a 100644 --- a/src/pages/iou/SplitBillDetailsPage.tsx +++ b/src/pages/iou/SplitBillDetailsPage.tsx @@ -24,7 +24,14 @@ import Parser from '@libs/Parser'; import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; import {getTransactionDetails, isPolicyExpenseChat} from '@libs/ReportUtils'; import type {OptionData} from '@libs/ReportUtils'; -import {areRequiredFieldsEmpty, hasReceipt, isDistanceRequest as isDistanceRequestUtil, isManualDistanceRequest as isManualDistanceRequestUtil, isScanning} from '@libs/TransactionUtils'; +import { + areRequiredFieldsEmpty, + hasReceipt, + isDistanceRequest as isDistanceRequestUtil, + isGPSDistanceRequest as isGPSDistanceRequestUtil, + isManualDistanceRequest as isManualDistanceRequestUtil, + isScanning, +} from '@libs/TransactionUtils'; import withReportAndReportActionOrNotFound from '@pages/home/report/withReportAndReportActionOrNotFound'; import type {WithReportAndReportActionOrNotFoundProps} from '@pages/home/report/withReportAndReportActionOrNotFound'; import variables from '@styles/variables'; @@ -74,7 +81,8 @@ function SplitBillDetailsPage({route, report, reportAction}: SplitBillDetailsPag const isDistanceRequest = isDistanceRequestUtil(transaction); const isEditingSplitBill = session?.accountID === actorAccountID && areRequiredFieldsEmpty(transaction) && !isDistanceRequest; const isManualDistanceRequest = isManualDistanceRequestUtil(transaction); - const isMapDistanceRequest = isDistanceRequest && !isManualDistanceRequest; + const isGPSDistanceRequest = isGPSDistanceRequestUtil(transaction); + const isMapDistanceRequest = isDistanceRequest && !isGPSDistanceRequest && !isManualDistanceRequest; const [isConfirmed, setIsConfirmed] = useState(false); const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); @@ -147,6 +155,7 @@ function SplitBillDetailsPage({route, report, reportAction}: SplitBillDetailsPag receiptFilename={transaction?.receipt?.filename} isDistanceRequest={isDistanceRequest} isManualDistanceRequest={isManualDistanceRequest} + isGPSDistanceRequest={isGPSDistanceRequest} isEditingSplitBill={isEditingSplitBill} hasSmartScanFailed={hasSmartScanFailed} reportID={reportID} diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 5e4be0eba26c..ccfcab1a46c7 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -68,6 +68,7 @@ import { getValidWaypoints, hasReceipt, isDistanceRequest as isDistanceRequestTransactionUtils, + isGPSDistanceRequest as isGPSDistanceRequestTransactionUtils, isManualDistanceRequest as isManualDistanceRequestTransactionUtils, isOdometerDistanceRequest as isOdometerDistanceRequestTransactionUtils, isScanRequest, @@ -167,6 +168,7 @@ function IOURequestStepConfirmation({ const [policyDraft] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_DRAFTS}${draftPolicyID}`, {canBeMissing: true}); const [policyReal] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${realPolicyID}`, {canBeMissing: true}); const [reportDrafts] = useOnyx(ONYXKEYS.COLLECTION.REPORT_DRAFT, {canBeMissing: true}); + const [gpsDraftDetails] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS, {canBeMissing: true}); const expensifyIcons = useMemoizedLazyExpensifyIcons(['ReplaceReceipt', 'SmartScan']); /* @@ -249,7 +251,8 @@ function IOURequestStepConfirmation({ const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const isManualDistanceRequest = isManualDistanceRequestTransactionUtils(transaction); const isOdometerDistanceRequest = isOdometerDistanceRequestTransactionUtils(transaction); - const transactionDistance = isManualDistanceRequest || isOdometerDistanceRequest ? (transaction?.comment?.customUnit?.quantity ?? undefined) : undefined; + const isGPSDistanceRequest = isGPSDistanceRequestTransactionUtils(transaction); + const transactionDistance = isManualDistanceRequest || isOdometerDistanceRequest || isGPSDistanceRequest ? (transaction?.comment?.customUnit?.quantity ?? undefined) : undefined; const isPerDiemRequest = requestType === CONST.IOU.REQUEST_TYPE.PER_DIEM; const isTimeRequest = requestType === CONST.IOU.REQUEST_TYPE.TIME; const [lastLocationPermissionPrompt] = useOnyx(ONYXKEYS.NVP_LAST_LOCATION_PERMISSION_PROMPT, {canBeMissing: true}); @@ -746,7 +749,7 @@ function IOURequestStepConfirmation({ for (const [index, item] of transactions.entries()) { const isLinkedTrackedExpenseReportArchived = !!item.linkedTrackedExpenseReportID && archivedReportsIdSet.has(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${item.linkedTrackedExpenseReportID}`); - const itemDistance = isManualDistanceRequest || isOdometerDistanceRequest ? (item.comment?.customUnit?.quantity ?? undefined) : undefined; + const itemDistance = isManualDistanceRequest || isOdometerDistanceRequest || isGPSDistanceRequest ? (item.comment?.customUnit?.quantity ?? undefined) : undefined; trackExpenseIOUActions({ report, @@ -786,6 +789,8 @@ function IOURequestStepConfirmation({ isLinkedTrackedExpenseReportArchived, odometerStart: isOdometerDistanceRequest ? item.comment?.odometerStart : undefined, odometerEnd: isOdometerDistanceRequest ? item.comment?.odometerEnd : undefined, + gpsCoordinates: + isGPSDistanceRequest && gpsDraftDetails?.gpsPoints ? JSON.stringify(gpsDraftDetails.gpsPoints.map((val) => ({lng: val.long, lat: val.lat}))) : undefined, }, accountantParams: { accountant: item.accountant, @@ -816,11 +821,13 @@ function IOURequestStepConfirmation({ isDraftPolicy, isManualDistanceRequest, isOdometerDistanceRequest, + isGPSDistanceRequest, archivedReportsIdSet, isASAPSubmitBetaEnabled, introSelected, activePolicyID, quickAction, + gpsDraftDetails?.gpsPoints, ], ); @@ -864,6 +871,7 @@ function IOURequestStepConfirmation({ receipt: isManualDistanceRequest || isOdometerDistanceRequest ? receiptFiles[transaction.transactionID] : undefined, odometerStart: isOdometerDistanceRequest ? transaction.comment?.odometerStart : undefined, odometerEnd: isOdometerDistanceRequest ? transaction.comment?.odometerEnd : undefined, + gpsCoordinates: isGPSDistanceRequest && gpsDraftDetails?.gpsPoints ? JSON.stringify(gpsDraftDetails.gpsPoints.map((val) => ({lng: val.long, lat: val.lat}))) : undefined, }, backToReport, isASAPSubmitBetaEnabled, @@ -895,6 +903,8 @@ function IOURequestStepConfirmation({ transactionViolations, quickAction, policyRecentlyUsedCurrencies, + gpsDraftDetails?.gpsPoints, + isGPSDistanceRequest, ], ); @@ -1419,6 +1429,7 @@ function IOURequestStepConfirmation({ isDistanceRequest={isDistanceRequest} isManualDistanceRequest={isManualDistanceRequest} isOdometerDistanceRequest={isOdometerDistanceRequest} + isGPSDistanceRequest={isGPSDistanceRequest} isPerDiemRequest={isPerDiemRequest} shouldShowSmartScanFields={shouldShowSmartScanFields} action={action} diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx index c14bdcc5d8d3..09aaffb2ead0 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx @@ -2,10 +2,15 @@ import React from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import Text from '@components/Text'; +import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy'; import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import {isPaidGroupPolicy} from '@libs/PolicyUtils'; +import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; +import type {IOUType} from '@src/CONST'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report, Transaction} from '@src/types/onyx'; import TripStatusIndicator from './TripStatusIndicator'; @@ -15,16 +20,27 @@ type DistanceCounterProps = { transaction: OnyxEntry; /** The report corresponding to the reportID in the route params */ report: OnyxEntry; + /** The type of IOU report, i.e. split, request, send, track */ + iouType: IOUType; }; -function DistanceCounter({report, transaction}: DistanceCounterProps) { +function DistanceCounter({report, transaction, iouType}: DistanceCounterProps) { const styles = useThemeStyles(); const policy = usePolicy(report?.policyID); const [gpsDraftDetails] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS, {canBeMissing: true}); - const unit = DistanceRequestUtils.getRate({transaction, policy}).unit; + const defaultExpensePolicy = useDefaultExpensePolicy(); + + const shouldUseDefaultExpensePolicy = + iouType === CONST.IOU.TYPE.CREATE && + isPaidGroupPolicy(defaultExpensePolicy) && + defaultExpensePolicy?.isPolicyExpenseChatEnabled && + !shouldRestrictUserBillableActions(defaultExpensePolicy.id); + + const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy}).unit; const distance = DistanceRequestUtils.convertDistanceUnit(gpsDraftDetails?.distanceInMeters ?? 0, unit).toFixed(1); + const tripInProgressOrStopped = (gpsDraftDetails?.gpsPoints?.length ?? 0) > 0 || gpsDraftDetails?.isTracking; return ( diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index 8e0ceb334118..3243edeef8ae 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -1,15 +1,42 @@ +import reportsSelector from '@selectors/Attributes'; import React, {useState} from 'react'; import {View} from 'react-native'; import DotIndicatorMessage from '@components/DotIndicatorMessage'; import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import useDefaultExpensePolicy from '@hooks/useDefaultExpensePolicy'; import useLocalize from '@hooks/useLocalize'; +import useOnyx from '@hooks/useOnyx'; +import usePermissions from '@hooks/usePermissions'; +import usePersonalPolicy from '@hooks/usePersonalPolicy'; +import usePolicy from '@hooks/usePolicy'; import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; import useThemeStyles from '@hooks/useThemeStyles'; +import { + createDistanceRequest, + getGPSWaypoints, + getMoneyRequestParticipantsFromReport, + setCustomUnitRateID, + setGPSTransactionDraftData, + setMoneyRequestMerchant, + setMoneyRequestParticipantsFromReport, + setMoneyRequestPendingFields, + trackExpense, +} from '@libs/actions/IOU'; +import {setTransactionReport} from '@libs/actions/Transaction'; +import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import {navigateToParticipantPage} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; +import {roundToTwoDecimalPlaces} from '@libs/NumberUtils'; +import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; +import {isPaidGroupPolicy} from '@libs/PolicyUtils'; +import {getPolicyExpenseChat, isArchivedReport, isPolicyExpenseChat as isPolicyExpenseChatUtils} from '@libs/ReportUtils'; +import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound'; import withWritableReportOrNotFound from '@pages/iou/request/step/withWritableReportOrNotFound'; import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import ROUTES from '@src/ROUTES'; import type {Errors} from '@src/types/onyx/OnyxCommon'; import Disclaimer from './Disclaimer'; import DistanceCounter from './DistanceCounter'; @@ -20,22 +47,196 @@ import Waypoints from './Waypoints'; function IOURequestStepDistanceGPS({ report, route: { - params: {action, iouType, reportActionID}, + params: {action, iouType, reportID, transactionID, reportActionID, backToReport}, }, transaction, + currentUserPersonalDetails, }: IOURequestStepDistanceGPSProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); + const {isBetaEnabled} = usePermissions(); + + const [lastSelectedDistanceRates] = useOnyx(ONYXKEYS.NVP_LAST_SELECTED_DISTANCE_RATES, {canBeMissing: true}); + const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`, {canBeMissing: true}); + const [gpsDraftDetails] = useOnyx(ONYXKEYS.GPS_DRAFT_DETAILS, {canBeMissing: true}); + const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`, {canBeMissing: true}); + const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: false}); + const [reportAttributesDerived] = useOnyx(ONYXKEYS.DERIVED.REPORT_ATTRIBUTES, {canBeMissing: true, selector: reportsSelector}); + const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); + const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); + const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); const isEditing = action === CONST.IOU.ACTION.EDIT; const isCreatingNewRequest = !isEditing; // eslint-disable-next-line rulesdir/no-negated-variables const shouldShowNotFoundPage = useShowNotFoundPageInIOUStep(action, iouType, reportActionID, report, transaction); + const defaultExpensePolicy = useDefaultExpensePolicy(); + const personalPolicy = usePersonalPolicy(); + const policy = usePolicy(report?.policyID); + + const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); + const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); + const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); + const currentUserAccountIDParam = currentUserPersonalDetails.accountID; + const currentUserEmailParam = currentUserPersonalDetails.login ?? ''; + + const shouldUseDefaultExpensePolicy = + iouType === CONST.IOU.TYPE.CREATE && + isPaidGroupPolicy(defaultExpensePolicy) && + defaultExpensePolicy?.isPolicyExpenseChatEnabled && + !shouldRestrictUserBillableActions(defaultExpensePolicy.id); + + const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy}).unit; + + const shouldSkipConfirmation: boolean = !skipConfirmation || !report?.reportID ? false : !(isArchivedReport(reportNameValuePairs) || isPolicyExpenseChatUtils(report)); + + const navigateToConfirmationPage = () => { + switch (iouType) { + case CONST.IOU.TYPE.REQUEST: + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.SUBMIT, transactionID, reportID, backToReport)); + break; + case CONST.IOU.TYPE.SEND: + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, CONST.IOU.TYPE.PAY, transactionID, reportID)); + break; + default: + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute(CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, backToReport)); + } + }; + + const navigateToNextStep = () => { + const gpsCoordinates = gpsDraftDetails?.gpsPoints ? JSON.stringify(gpsDraftDetails.gpsPoints.map((val) => ({lng: val.long, lat: val.lat}))) : undefined; + const distanceInMeters = gpsDraftDetails?.distanceInMeters ?? 0; + const convertedDistance = DistanceRequestUtils.convertDistanceUnit(distanceInMeters, unit); + const distance = roundToTwoDecimalPlaces(convertedDistance); + + setGPSTransactionDraftData(transactionID, gpsDraftDetails, distance); + + // If a reportID exists in the report object, use it to set participants and navigate to confirmation + if (report?.reportID && !isArchivedReport(reportNameValuePairs) && iouType !== CONST.IOU.TYPE.CREATE) { + const selectedParticipants = getMoneyRequestParticipantsFromReport(report, currentUserPersonalDetails.accountID); + const participants = selectedParticipants.map((participant) => { + const participantAccountID = participant?.accountID ?? CONST.DEFAULT_NUMBER_ID; + return participantAccountID ? getParticipantsOption(participant, personalDetails) : getReportOption(participant, policy, reportAttributesDerived); + }); + + if (shouldSkipConfirmation) { + setMoneyRequestPendingFields(transactionID, {waypoints: CONST.RED_BRICK_ROAD_PENDING_ACTION.ADD}); + setMoneyRequestMerchant(transactionID, translate('iou.fieldPending'), false); + + const participant = participants.at(0); + const isPolicyExpenseChat = !!participant?.isPolicyExpenseChat; + const customUnitRateID = DistanceRequestUtils.getCustomUnitRateID({reportID: report.reportID, isPolicyExpenseChat, policy, lastSelectedDistanceRates}); + + const validWaypoints = getGPSWaypoints(gpsDraftDetails); + + if (iouType === CONST.IOU.TYPE.TRACK && participant) { + trackExpense({ + report, + isDraftPolicy: false, + participantParams: { + payeeEmail: currentUserEmailParam, + payeeAccountID: currentUserAccountIDParam, + participant, + }, + policyParams: { + policy, + }, + transactionParams: { + amount: 0, + currency: transaction?.currency ?? 'USD', + created: transaction?.created ?? '', + merchant: translate('iou.fieldPending'), + receipt: {}, + billable: false, + customUnitRateID, + attendees: transaction?.comment?.attendees, + distance, + validWaypoints, + gpsCoordinates, + }, + isASAPSubmitBetaEnabled, + currentUserAccountIDParam, + currentUserEmailParam, + introSelected, + activePolicyID, + quickAction, + }); + + return; + } + + createDistanceRequest({ + report, + participants, + currentUserLogin: currentUserEmailParam, + currentUserAccountID: currentUserAccountIDParam, + iouType, + existingTransaction: transaction, + transactionParams: { + amount: 0, + comment: '', + created: transaction?.created ?? '', + currency: transaction?.currency ?? 'USD', + merchant: translate('iou.fieldPending'), + billable: !!policy?.defaultBillable, + reimbursable: !!policy?.defaultReimbursable, + customUnitRateID, + splitShares: transaction?.splitShares, + attendees: transaction?.comment?.attendees, + distance, + validWaypoints, + gpsCoordinates, + }, + backToReport, + isASAPSubmitBetaEnabled, + transactionViolations, + quickAction, + policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], + }); + + return; + } + + setMoneyRequestParticipantsFromReport(transactionID, report, currentUserAccountIDParam).then(() => { + navigateToConfirmationPage(); + }); + + return; + } + + // If there was no reportID, then that means the user started this flow from the global menu + // and an optimistic reportID was generated. In that case, the next step is to select the participants for this expense. + if (shouldUseDefaultExpensePolicy) { + const activePolicyExpenseChat = getPolicyExpenseChat(currentUserPersonalDetails.accountID, defaultExpensePolicy?.id); + const shouldAutoReport = !!defaultExpensePolicy?.autoReporting || !!personalPolicy?.autoReporting; + const transactionReportID = shouldAutoReport ? activePolicyExpenseChat?.reportID : CONST.REPORT.UNREPORTED_REPORT_ID; + const rateID = DistanceRequestUtils.getCustomUnitRateID({ + reportID: transactionReportID, + isPolicyExpenseChat: true, + policy: defaultExpensePolicy, + lastSelectedDistanceRates, + }); + + setTransactionReport(transactionID, {reportID: transactionReportID}, true); + setCustomUnitRateID(transactionID, rateID); + setMoneyRequestParticipantsFromReport(transactionID, activePolicyExpenseChat).then(() => { + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_CONFIRMATION.getRoute( + CONST.IOU.ACTION.CREATE, + iouType === CONST.IOU.TYPE.CREATE ? CONST.IOU.TYPE.SUBMIT : iouType, + transactionID, + activePolicyExpenseChat?.reportID, + ), + ); + }); + } else { + navigateToParticipantPage(iouType, transactionID, reportID); + } + }; const [shouldShowStartError, setShouldShowStartError] = useState(false); const [shouldShowPermissionsError, setShouldShowPermissionsError] = useState(false); - const getError = (): Errors => { if (shouldShowStartError) { return {startError: translate('gps.error.failedToStart')}; @@ -57,6 +258,7 @@ function IOURequestStepDistanceGPS({ @@ -67,8 +269,7 @@ function IOURequestStepDistanceGPS({ type="error" /> {}} + navigateToNextStep={navigateToNextStep} setShouldShowStartError={setShouldShowStartError} setShouldShowPermissionsError={setShouldShowPermissionsError} /> diff --git a/tests/ui/MoneyRequestReportFooter.tsx b/tests/ui/MoneyRequestReportFooter.tsx index ffc66fb4a130..0b491cad2435 100644 --- a/tests/ui/MoneyRequestReportFooter.tsx +++ b/tests/ui/MoneyRequestReportFooter.tsx @@ -87,6 +87,7 @@ const renderMoneyRequestConfirmationListFooter = (transaction: Transaction) => { isCategoryRequired: false, isDistanceRequest: false, isManualDistanceRequest: false, + isGPSDistanceRequest: false, isPerDiemRequest: false, isMerchantEmpty: false, isMerchantRequired: false, From d00a0925c6aeaae8abb0fad8fa57ad494fe069d3 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Tue, 20 Jan 2026 17:18:16 +0100 Subject: [PATCH 02/20] fix spellcheck --- 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 f19d46da4e73..fd1a4d8c8185 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -187,7 +187,7 @@ function isGPSDistanceRequest(transaction: OnyxEntry): boolean { return transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_GPS; } - // todo(@GCyganek): currently there is no way to tell server map transaction object from + // todo: currently there is no way to tell server map transaction object from // server gps transaction object, this will be discussed and updated later // This is the case for transaction objects once they have been saved to the server return hasDistanceCustomUnit(transaction); From 0ee013c0330be69a2855cf5540cc707db6ee362c Mon Sep 17 00:00:00 2001 From: GCyganek Date: Tue, 20 Jan 2026 17:25:57 +0100 Subject: [PATCH 03/20] Fix type error --- .../request/step/IOURequestStepDistanceGPS/index.native.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index 3243edeef8ae..ac7ff5410ca2 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -117,7 +117,9 @@ function IOURequestStepDistanceGPS({ const selectedParticipants = getMoneyRequestParticipantsFromReport(report, currentUserPersonalDetails.accountID); const participants = selectedParticipants.map((participant) => { const participantAccountID = participant?.accountID ?? CONST.DEFAULT_NUMBER_ID; - return participantAccountID ? getParticipantsOption(participant, personalDetails) : getReportOption(participant, policy, reportAttributesDerived); + return participantAccountID + ? getParticipantsOption(participant, personalDetails) + : getReportOption(participant, reportNameValuePairs?.private_isArchived, policy, reportAttributesDerived); }); if (shouldSkipConfirmation) { From 2172c3f7c2aa7a04ce3b84d58cb82db6276940b8 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Tue, 20 Jan 2026 17:40:08 +0100 Subject: [PATCH 04/20] Replace Onyx.connect with params --- Mobile-Expensify | 2 +- src/libs/actions/IOU/Duplicate.ts | 1 + src/libs/actions/IOU/index.ts | 10 ++++------ src/pages/Share/SubmitDetailsPage.tsx | 2 ++ .../iou/request/step/IOURequestStepAmount.tsx | 2 ++ .../request/step/IOURequestStepConfirmation.tsx | 5 +++++ .../iou/request/step/IOURequestStepDistance.tsx | 4 ++++ .../IOURequestStepDistanceGPS/index.native.tsx | 3 +++ .../step/IOURequestStepDistanceManual.tsx | 4 ++++ .../request/step/IOURequestStepDistanceMap.tsx | 4 ++++ .../step/IOURequestStepDistanceOdometer.tsx | 3 +++ .../step/IOURequestStepScan/index.native.tsx | 3 +++ .../request/step/IOURequestStepScan/index.tsx | 3 +++ tests/actions/IOUTest.ts | 17 +++++++++++++++++ tests/ui/UnreadIndicatorsTest.tsx | 1 + tests/unit/GoogleTagManagerTest.tsx | 1 + 16 files changed, 58 insertions(+), 7 deletions(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 1c7f85c2277d..ad7cbdcc99ec 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 1c7f85c2277db71dad45d61b62713370884f1e94 +Subproject commit ad7cbdcc99ec46cf94f8c830e90136565a566484 diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index da8822844c31..b0546fe10445 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -546,6 +546,7 @@ function duplicateExpenseTransaction({ introSelected, activePolicyID, quickAction, + firstCreatedGpsExpenseDateNewDot: undefined, }; return trackExpense(trackExpenseParams); } diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 6a295a581ecb..eea7fd70b501 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -645,6 +645,7 @@ type CreateDistanceRequestInformation = { transactionViolations: OnyxCollection; quickAction: OnyxEntry; policyRecentlyUsedCurrencies: string[]; + firstCreatedGpsExpenseDateNewDot: string | undefined; }; type CreateSplitsTransactionParams = Omit & { @@ -715,6 +716,7 @@ type CreateTrackExpenseParams = { introSelected: OnyxEntry; activePolicyID: string | undefined; quickAction: OnyxEntry; + firstCreatedGpsExpenseDateNewDot: string | undefined; }; type GetTrackExpenseInformationTransactionParams = { @@ -969,12 +971,6 @@ Onyx.connect({ callback: (val) => (recentWaypoints = val ?? []), }); -let firstCreatedGpsExpenseDateNewDot: OnyxEntry; -Onyx.connect({ - key: ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, - callback: (val) => (firstCreatedGpsExpenseDateNewDot = val), -}); - function getAllPersonalDetails(): OnyxTypes.PersonalDetailsList { return allPersonalDetails; } @@ -6582,6 +6578,7 @@ function trackExpense(params: CreateTrackExpenseParams) { introSelected, activePolicyID, quickAction, + firstCreatedGpsExpenseDateNewDot, } = params; const {participant, payeeAccountID, payeeEmail} = participantParams; const {policy, policyCategories, policyTagList} = policyData; @@ -8389,6 +8386,7 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest transactionViolations, quickAction, policyRecentlyUsedCurrencies, + firstCreatedGpsExpenseDateNewDot, } = distanceRequestInformation; const {policy, policyCategories, policyTagList, policyRecentlyUsedCategories, policyRecentlyUsedTags} = policyParams; const parsedComment = getParsedComment(transactionParams.comment); diff --git a/src/pages/Share/SubmitDetailsPage.tsx b/src/pages/Share/SubmitDetailsPage.tsx index b755239bb3b1..d1bc4cd1d921 100644 --- a/src/pages/Share/SubmitDetailsPage.tsx +++ b/src/pages/Share/SubmitDetailsPage.tsx @@ -69,6 +69,7 @@ function SubmitDetailsPage({ const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); + const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const personalPolicy = usePersonalPolicy(); @@ -156,6 +157,7 @@ function SubmitDetailsPage({ activePolicyID, introSelected, quickAction, + firstCreatedGpsExpenseDateNewDot, }); } else { requestMoney({ diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 108ba977497b..adb8a20f4072 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -95,6 +95,7 @@ function IOURequestStepAmount({ const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); + const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const defaultExpensePolicy = useDefaultExpensePolicy(); const personalPolicy = usePersonalPolicy(); const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(transactionID ? [transactionID] : []); @@ -272,6 +273,7 @@ function IOURequestStepAmount({ introSelected, activePolicyID, quickAction, + firstCreatedGpsExpenseDateNewDot, }); return; } diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 5c3478e64936..2e409bfbcf3e 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -208,6 +208,7 @@ function IOURequestStepConfirmation({ const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${realPolicyID}`, {canBeMissing: true}); const [policyRecentlyUsedTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${realPolicyID}`, {canBeMissing: true}); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); + const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION, {canBeMissing: true}); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); @@ -801,6 +802,7 @@ function IOURequestStepConfirmation({ introSelected, activePolicyID, quickAction, + firstCreatedGpsExpenseDateNewDot, }); } }, @@ -827,6 +829,7 @@ function IOURequestStepConfirmation({ activePolicyID, quickAction, gpsDraftDetails?.gpsPoints, + firstCreatedGpsExpenseDateNewDot, ], ); @@ -877,6 +880,7 @@ function IOURequestStepConfirmation({ transactionViolations, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], + firstCreatedGpsExpenseDateNewDot, }); }, [ @@ -904,6 +908,7 @@ function IOURequestStepConfirmation({ policyRecentlyUsedCurrencies, gpsDraftDetails?.gpsPoints, isGPSDistanceRequest, + firstCreatedGpsExpenseDateNewDot, ], ); diff --git a/src/pages/iou/request/step/IOURequestStepDistance.tsx b/src/pages/iou/request/step/IOURequestStepDistance.tsx index 206350a30772..1db4d00ee6f3 100644 --- a/src/pages/iou/request/step/IOURequestStepDistance.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistance.tsx @@ -104,6 +104,7 @@ function IOURequestStepDistance({ const [optimisticWaypoints, setOptimisticWaypoints] = useState(null); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); + const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); const waypoints = useMemo( () => @@ -365,6 +366,7 @@ function IOURequestStepDistance({ introSelected, activePolicyID, quickAction, + firstCreatedGpsExpenseDateNewDot, }); return; } @@ -396,6 +398,7 @@ function IOURequestStepDistance({ transactionViolations, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], + firstCreatedGpsExpenseDateNewDot, }); return; } @@ -467,6 +470,7 @@ function IOURequestStepDistance({ navigateToConfirmationPage, personalPolicy?.autoReporting, reportID, + firstCreatedGpsExpenseDateNewDot, ]); const getError = () => { diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index ac7ff5410ca2..284f6eeb17ec 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -66,6 +66,7 @@ function IOURequestStepDistanceGPS({ const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); + const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const isEditing = action === CONST.IOU.ACTION.EDIT; const isCreatingNewRequest = !isEditing; @@ -163,6 +164,7 @@ function IOURequestStepDistanceGPS({ introSelected, activePolicyID, quickAction, + firstCreatedGpsExpenseDateNewDot, }); return; @@ -195,6 +197,7 @@ function IOURequestStepDistanceGPS({ transactionViolations, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], + firstCreatedGpsExpenseDateNewDot, }); return; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx index fc7ea486d862..c210602ccf06 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx @@ -96,6 +96,7 @@ function IOURequestStepDistanceManual({ const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); + const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.parentReportID)}`, {canBeMissing: true}); const [parentReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${getNonEmptyStringOnyxID(report?.parentReportID)}`, {canBeMissing: true}); @@ -242,6 +243,7 @@ function IOURequestStepDistanceManual({ introSelected, activePolicyID, quickAction, + firstCreatedGpsExpenseDateNewDot, }); return; } @@ -272,6 +274,7 @@ function IOURequestStepDistanceManual({ transactionViolations, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], + firstCreatedGpsExpenseDateNewDot, }); return; } @@ -343,6 +346,7 @@ function IOURequestStepDistanceManual({ defaultExpensePolicy, personalPolicy?.autoReporting, reportID, + firstCreatedGpsExpenseDateNewDot, ], ); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx index efa1d6d6f322..196ca5cdb066 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceMap.tsx @@ -104,6 +104,7 @@ function IOURequestStepDistanceMap({ const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); const [optimisticWaypoints, setOptimisticWaypoints] = useState(null); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); + const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const waypoints = useMemo( () => optimisticWaypoints ?? @@ -365,6 +366,7 @@ function IOURequestStepDistanceMap({ introSelected, activePolicyID, quickAction, + firstCreatedGpsExpenseDateNewDot, }); return; } @@ -396,6 +398,7 @@ function IOURequestStepDistanceMap({ transactionViolations, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], + firstCreatedGpsExpenseDateNewDot, }); return; } @@ -466,6 +469,7 @@ function IOURequestStepDistanceMap({ navigateToConfirmationPage, personalPolicy?.autoReporting, reportID, + firstCreatedGpsExpenseDateNewDot, ]); const getError = () => { diff --git a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx index 2e7cb49d6e8c..dd788cc84de1 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx @@ -103,6 +103,7 @@ function IOURequestStepDistanceOdometer({ const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); + const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`, {canBeMissing: true}); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.parentReportID)}`, {canBeMissing: true}); const [parentReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${getNonEmptyStringOnyxID(report?.parentReportID)}`, {canBeMissing: true}); @@ -416,6 +417,7 @@ function IOURequestStepDistanceOdometer({ introSelected, activePolicyID, quickAction, + firstCreatedGpsExpenseDateNewDot, }); return; } @@ -446,6 +448,7 @@ function IOURequestStepDistanceOdometer({ transactionViolations, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], + firstCreatedGpsExpenseDateNewDot, }); return; } diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index d467d063903e..85c23bb26380 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -138,6 +138,7 @@ function IOURequestStepScan({ const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); + const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const defaultTaxCode = getDefaultTaxCode(policy, initialTransaction); const transactionTaxCode = (initialTransaction?.taxCode ? initialTransaction?.taxCode : defaultTaxCode) ?? ''; @@ -345,6 +346,7 @@ function IOURequestStepScan({ introSelected, activePolicyID, quickAction, + firstCreatedGpsExpenseDateNewDot, }); } else { requestMoney({ @@ -393,6 +395,7 @@ function IOURequestStepScan({ activePolicyID, quickAction, policyRecentlyUsedCurrencies, + firstCreatedGpsExpenseDateNewDot, ], ); diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 522a6c5cd800..e80b393b3877 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -137,6 +137,7 @@ function IOURequestStepScan({ const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); + const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const [optimisticTransactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_DRAFT, { selector: transactionDraftValuesSelector, @@ -401,6 +402,7 @@ function IOURequestStepScan({ introSelected, activePolicyID, quickAction, + firstCreatedGpsExpenseDateNewDot, }); } else { requestMoney({ @@ -450,6 +452,7 @@ function IOURequestStepScan({ activePolicyID, quickAction, policyRecentlyUsedCurrencies, + firstCreatedGpsExpenseDateNewDot, ], ); diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 01b1bbd6ba1f..213e66eb30b2 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -519,6 +519,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); await mockFetch?.resume?.(); @@ -619,6 +620,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); await mockFetch?.resume?.(); @@ -702,6 +704,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -754,6 +757,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -832,6 +836,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -884,6 +889,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -2180,6 +2186,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); mockFetch?.resume?.(); @@ -2245,6 +2252,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -2499,6 +2507,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + firstCreatedGpsExpenseDateNewDot: undefined, }); expect(notifyNewAction).toHaveBeenCalledTimes(0); }); @@ -2520,6 +2529,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + firstCreatedGpsExpenseDateNewDot: undefined, }); expect(notifyNewAction).toHaveBeenCalledTimes(1); }); @@ -2543,6 +2553,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); expect(await getOnyxValue(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE)).toHaveProperty('isFirstQuickAction', true); @@ -2563,6 +2574,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: {action: CONST.QUICK_ACTIONS.SEND_MONEY, chatReportID: '456'}, policyRecentlyUsedCurrencies: [], + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); expect(await getOnyxValue(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE)).toMatchObject({ @@ -2594,6 +2606,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: initialCurrencies, + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -2644,6 +2657,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + firstCreatedGpsExpenseDateNewDot: undefined, }); waitForBatchedUpdates(); @@ -2703,6 +2717,7 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], + firstCreatedGpsExpenseDateNewDot: undefined, }); waitForBatchedUpdates(); @@ -7417,6 +7432,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -9067,6 +9083,7 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); await getOnyxData({ key: ONYXKEYS.COLLECTION.TRANSACTION, diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index c9b27b702b0b..5055cfe5f40e 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -720,6 +720,7 @@ describe('Unread Indicators', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); diff --git a/tests/unit/GoogleTagManagerTest.tsx b/tests/unit/GoogleTagManagerTest.tsx index 2d93af6bfb83..93210c20a722 100644 --- a/tests/unit/GoogleTagManagerTest.tsx +++ b/tests/unit/GoogleTagManagerTest.tsx @@ -197,6 +197,7 @@ describe('GoogleTagManagerTest', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, + firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdatesWithAct(); From 561271e7523add9022648d0e01f87c628c764ebf Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 21 Jan 2026 13:18:00 +0100 Subject: [PATCH 05/20] Revert Mobile-Expensify hash change --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index ad7cbdcc99ec..1e7de7f2db5b 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit ad7cbdcc99ec46cf94f8c830e90136565a566484 +Subproject commit 1e7de7f2db5b65847fa9f5090b784298c1974b54 From c127bfa047d3808bd1087c825ba3bd84adfd3feb Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 21 Jan 2026 13:26:40 +0100 Subject: [PATCH 06/20] do not require passing firstGpsExpenseDate param --- src/libs/actions/IOU/Duplicate.ts | 1 - src/libs/actions/IOU/MoneyRequest.ts | 3 +-- src/libs/actions/IOU/index.ts | 4 ++-- src/pages/Share/SubmitDetailsPage.tsx | 2 -- .../iou/request/step/IOURequestStepAmount.tsx | 2 -- .../step/IOURequestStepDistanceOdometer.tsx | 3 --- tests/actions/IOUTest.ts | 17 ----------------- tests/ui/UnreadIndicatorsTest.tsx | 1 - tests/unit/GoogleTagManagerTest.tsx | 1 - 9 files changed, 3 insertions(+), 31 deletions(-) diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index b0546fe10445..da8822844c31 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -546,7 +546,6 @@ function duplicateExpenseTransaction({ introSelected, activePolicyID, quickAction, - firstCreatedGpsExpenseDateNewDot: undefined, }; return trackExpense(trackExpenseParams); } diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 4b82f8254440..24ff36e255e0 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -186,7 +186,6 @@ function createTransaction({ introSelected, activePolicyID, quickAction, - firstCreatedGpsExpenseDateNewDot: undefined, }); } else { requestMoney({ @@ -580,7 +579,7 @@ function handleMoneyRequestStepDistanceNavigation({ billable: !!policy?.defaultBillable, reimbursable: isManualDistance ? undefined : !!policy?.defaultReimbursable, validWaypoints, - customUnitRateID: DistanceRequestUtils.getCustomUnitRateID({reportID: report.reportID, isPolicyExpenseChat, policy, lastSelectedDistanceRates}), // here? + customUnitRateID: DistanceRequestUtils.getCustomUnitRateID({reportID: report.reportID, isPolicyExpenseChat, policy, lastSelectedDistanceRates}), splitShares: transaction?.splitShares, attendees: transaction?.comment?.attendees, gpsCoordinates, diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 9837adaa5631..9d184d3e7bff 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -645,7 +645,7 @@ type CreateDistanceRequestInformation = { transactionViolations: OnyxCollection; quickAction: OnyxEntry; policyRecentlyUsedCurrencies: string[]; - firstCreatedGpsExpenseDateNewDot: string | undefined; + firstCreatedGpsExpenseDateNewDot?: string; }; type CreateSplitsTransactionParams = Omit & { @@ -716,7 +716,7 @@ type CreateTrackExpenseParams = { introSelected: OnyxEntry; activePolicyID: string | undefined; quickAction: OnyxEntry; - firstCreatedGpsExpenseDateNewDot: string | undefined; + firstCreatedGpsExpenseDateNewDot?: string; }; type GetTrackExpenseInformationTransactionParams = { diff --git a/src/pages/Share/SubmitDetailsPage.tsx b/src/pages/Share/SubmitDetailsPage.tsx index d1bc4cd1d921..b755239bb3b1 100644 --- a/src/pages/Share/SubmitDetailsPage.tsx +++ b/src/pages/Share/SubmitDetailsPage.tsx @@ -69,7 +69,6 @@ function SubmitDetailsPage({ const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); - const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const personalPolicy = usePersonalPolicy(); @@ -157,7 +156,6 @@ function SubmitDetailsPage({ activePolicyID, introSelected, quickAction, - firstCreatedGpsExpenseDateNewDot, }); } else { requestMoney({ diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 2d0354d7dd8c..e6358feae9cf 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -95,7 +95,6 @@ function IOURequestStepAmount({ const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); - const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const defaultExpensePolicy = useDefaultExpensePolicy(); const personalPolicy = usePersonalPolicy(); const {duplicateTransactions, duplicateTransactionViolations} = useDuplicateTransactionsAndViolations(transactionID ? [transactionID] : []); @@ -260,7 +259,6 @@ function IOURequestStepAmount({ introSelected, activePolicyID, quickAction, - firstCreatedGpsExpenseDateNewDot, }); return; } diff --git a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx index dd788cc84de1..2e7cb49d6e8c 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx @@ -103,7 +103,6 @@ function IOURequestStepDistanceOdometer({ const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); - const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const [skipConfirmation] = useOnyx(`${ONYXKEYS.COLLECTION.SKIP_CONFIRMATION}${transactionID}`, {canBeMissing: true}); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(report?.parentReportID)}`, {canBeMissing: true}); const [parentReportNextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${getNonEmptyStringOnyxID(report?.parentReportID)}`, {canBeMissing: true}); @@ -417,7 +416,6 @@ function IOURequestStepDistanceOdometer({ introSelected, activePolicyID, quickAction, - firstCreatedGpsExpenseDateNewDot, }); return; } @@ -448,7 +446,6 @@ function IOURequestStepDistanceOdometer({ transactionViolations, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], - firstCreatedGpsExpenseDateNewDot, }); return; } diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index e4df462ffac1..c4186682b97c 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -519,7 +519,6 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); await mockFetch?.resume?.(); @@ -620,7 +619,6 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); await mockFetch?.resume?.(); @@ -704,7 +702,6 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -757,7 +754,6 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -836,7 +832,6 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -889,7 +884,6 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -2186,7 +2180,6 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); mockFetch?.resume?.(); @@ -2252,7 +2245,6 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -2507,7 +2499,6 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], - firstCreatedGpsExpenseDateNewDot: undefined, }); expect(notifyNewAction).toHaveBeenCalledTimes(0); }); @@ -2529,7 +2520,6 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], - firstCreatedGpsExpenseDateNewDot: undefined, }); expect(notifyNewAction).toHaveBeenCalledTimes(1); }); @@ -2553,7 +2543,6 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); expect(await getOnyxValue(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE)).toHaveProperty('isFirstQuickAction', true); @@ -2574,7 +2563,6 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: {action: CONST.QUICK_ACTIONS.SEND_MONEY, chatReportID: '456'}, policyRecentlyUsedCurrencies: [], - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); expect(await getOnyxValue(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE)).toMatchObject({ @@ -2606,7 +2594,6 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: initialCurrencies, - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -2657,7 +2644,6 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], - firstCreatedGpsExpenseDateNewDot: undefined, }); waitForBatchedUpdates(); @@ -2717,7 +2703,6 @@ describe('actions/IOU', () => { transactionViolations: {}, quickAction: undefined, policyRecentlyUsedCurrencies: [], - firstCreatedGpsExpenseDateNewDot: undefined, }); waitForBatchedUpdates(); @@ -7583,7 +7568,6 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); @@ -9234,7 +9218,6 @@ describe('actions/IOU', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); await getOnyxData({ key: ONYXKEYS.COLLECTION.TRANSACTION, diff --git a/tests/ui/UnreadIndicatorsTest.tsx b/tests/ui/UnreadIndicatorsTest.tsx index 5055cfe5f40e..c9b27b702b0b 100644 --- a/tests/ui/UnreadIndicatorsTest.tsx +++ b/tests/ui/UnreadIndicatorsTest.tsx @@ -720,7 +720,6 @@ describe('Unread Indicators', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdates(); diff --git a/tests/unit/GoogleTagManagerTest.tsx b/tests/unit/GoogleTagManagerTest.tsx index 93210c20a722..2d93af6bfb83 100644 --- a/tests/unit/GoogleTagManagerTest.tsx +++ b/tests/unit/GoogleTagManagerTest.tsx @@ -197,7 +197,6 @@ describe('GoogleTagManagerTest', () => { introSelected: undefined, activePolicyID: undefined, quickAction: undefined, - firstCreatedGpsExpenseDateNewDot: undefined, }); await waitForBatchedUpdatesWithAct(); From 6999d5e42d741fd87d294194b128c21eab0776b8 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 21 Jan 2026 13:28:45 +0100 Subject: [PATCH 07/20] Revert IOURequestStepScan changes --- .../step/IOURequestStepScan/index.native.tsx | 36 +++++++++--------- .../request/step/IOURequestStepScan/index.tsx | 38 +++++++++---------- 2 files changed, 37 insertions(+), 37 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx index 0f46fe070ae5..bac2bb429b38 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.native.tsx @@ -297,32 +297,32 @@ function IOURequestStepScan({ }); }, [ - iouType, - policy, - report, - reportID, - reportAttributesDerived, + backTo, + backToReport, + shouldGenerateTransactionThreadReport, transactions, - initialTransactionID, - initialTransaction?.reportID, - initialTransaction?.currency, initialTransaction?.isFromGlobalCreate, + initialTransaction?.currency, initialTransaction?.participants, - transactionTaxCode, - transactionTaxAmount, - personalDetails, - currentUserPersonalDetails.login, + initialTransaction?.reportID, + isArchived, + iouType, + defaultExpensePolicy, + report, + initialTransactionID, currentUserPersonalDetails.accountID, - backTo, - backToReport, + currentUserPersonalDetails?.login, shouldSkipConfirmation, - defaultExpensePolicy, - shouldGenerateTransactionThreadReport, - isArchived, + personalDetails, + reportAttributesDerived, + reportID, + transactionTaxCode, + transactionTaxAmount, + quickAction, + policy, personalPolicy?.autoReporting, isASAPSubmitBetaEnabled, transactionViolations, - quickAction, policyRecentlyUsedCurrencies, introSelected, activePolicyID, diff --git a/src/pages/iou/request/step/IOURequestStepScan/index.tsx b/src/pages/iou/request/step/IOURequestStepScan/index.tsx index 9bd15d799112..d87664bd01a3 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/index.tsx @@ -350,33 +350,33 @@ function IOURequestStepScan({ }); }, [ - iouType, - policy, - report, - reportID, - reportAttributesDerived, + backTo, + backToReport, + shouldGenerateTransactionThreadReport, transactions, - initialTransactionID, - initialTransaction?.reportID, - initialTransaction?.currency, initialTransaction?.isFromGlobalCreate, + initialTransaction?.currency, initialTransaction?.participants, - transactionTaxCode, - transactionTaxAmount, - personalDetails, - currentUserPersonalDetails.login, + initialTransaction?.reportID, + isArchived, + iouType, + defaultExpensePolicy, + report, + initialTransactionID, currentUserPersonalDetails.accountID, - backTo, - backToReport, + currentUserPersonalDetails?.login, shouldSkipConfirmation, - defaultExpensePolicy, - shouldGenerateTransactionThreadReport, - isArchived, + personalDetails, + reportAttributesDerived, + reportID, + transactionTaxCode, + transactionTaxAmount, + quickAction, + policyRecentlyUsedCurrencies, + policy, personalPolicy?.autoReporting, isASAPSubmitBetaEnabled, transactionViolations, - quickAction, - policyRecentlyUsedCurrencies, introSelected, activePolicyID, ], From ca67a3e14fd61fc5ff1581e848b86a9e04bdb4c4 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 21 Jan 2026 14:57:36 +0100 Subject: [PATCH 08/20] Fix updating distance when changing between participants with different default distance unit --- src/components/MoneyRequestConfirmationList.tsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index b6823c9be574..b37aec17e460 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -24,6 +24,7 @@ import { setIndividualShare, setMoneyRequestAmount, setMoneyRequestCategory, + setMoneyRequestDistance, setMoneyRequestMerchant, setMoneyRequestPendingFields, setMoneyRequestTag, @@ -39,6 +40,7 @@ import {calculateAmount, insertTagIntoTransactionTagsString, isMovingTransaction import Log from '@libs/Log'; import {validateAmount} from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; +import {roundToTwoDecimalPlaces} from '@libs/NumberUtils'; import {getIOUConfirmationOptionsFromPayeePersonalDetail, hasEnabledOptions} from '@libs/OptionsListUtils'; import {getTagLists, isTaxTrackingEnabled} from '@libs/PolicyUtils'; import {isSelectedManagerMcTest} from '@libs/ReportUtils'; @@ -232,7 +234,7 @@ function MoneyRequestConfirmationList({ isDistanceRequest, isManualDistanceRequest, isOdometerDistanceRequest = false, - isGPSDistanceRequest = false, + isGPSDistanceRequest, isPerDiemRequest = false, isPolicyExpenseChat = false, iouCategory = '', @@ -390,6 +392,17 @@ function MoneyRequestConfirmationList({ const distanceRequestAmount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); + // Update distance when recipients changed and have different distance unit + const shouldUpdateDistance = isGPSDistanceRequest && prevUnit !== unit; + useEffect(() => { + if (!shouldUpdateDistance || !transactionID || isReadOnly) { + return; + } + + const distanceWithNewUnit = roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distance, unit)); + setMoneyRequestDistance(transactionID, distanceWithNewUnit, true); + }); + let amountToBeUsed = iouAmount; if (shouldCalculateDistanceAmount) { From 8026c0a3cbffe15d33ef1f3cc4b64653079dbedc Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 21 Jan 2026 15:34:28 +0100 Subject: [PATCH 09/20] GPSDraftDetailsUtils --- src/libs/GPSDraftDetailsUtils.ts | 83 +++++++++++++++++++ src/libs/actions/IOU/index.ts | 47 +---------- .../step/IOURequestStepConfirmation.tsx | 6 +- .../GPSButtons/index.tsx | 3 +- .../index.native.tsx | 10 +-- .../utils/addressFromGpsPoint.ts | 20 ----- .../utils/coordinatesToString.ts | 5 -- .../index.native.ts | 3 +- 8 files changed, 93 insertions(+), 84 deletions(-) create mode 100644 src/libs/GPSDraftDetailsUtils.ts delete mode 100644 src/pages/iou/request/step/IOURequestStepDistanceGPS/utils/addressFromGpsPoint.ts delete mode 100644 src/pages/iou/request/step/IOURequestStepDistanceGPS/utils/coordinatesToString.ts diff --git a/src/libs/GPSDraftDetailsUtils.ts b/src/libs/GPSDraftDetailsUtils.ts new file mode 100644 index 000000000000..58f9056c66f7 --- /dev/null +++ b/src/libs/GPSDraftDetailsUtils.ts @@ -0,0 +1,83 @@ +import {reverseGeocodeAsync} from 'expo-location'; +import type {GpsDraftDetails} from '@src/types/onyx'; +import type {Unit} from '@src/types/onyx/Policy'; +import type {Routes, WaypointCollection} from '@src/types/onyx/Transaction'; +import DistanceRequestUtils from './DistanceRequestUtils'; +import {roundToTwoDecimalPlaces} from './NumberUtils'; + +function getGPSWaypoints(gpsDraftDetails: GpsDraftDetails | undefined): WaypointCollection { + const gpsCoordinates = gpsDraftDetails?.gpsPoints ?? []; + const firstPoint = gpsCoordinates.at(0); + const lastPoint = gpsCoordinates.at(-1); + + return { + ...(firstPoint + ? { + waypoint0: { + lat: firstPoint.lat, + lng: firstPoint.long, + address: gpsDraftDetails?.startAddress.value ?? '', + name: gpsDraftDetails?.startAddress.value ?? '', + }, + } + : {}), + ...(lastPoint + ? { + waypoint1: { + lat: lastPoint.lat, + lng: lastPoint.long, + address: gpsDraftDetails?.endAddress.value ?? '', + name: gpsDraftDetails?.endAddress.value ?? '', + }, + } + : {}), + }; +} + +function getGPSRoutes(gpsDraftDetails: GpsDraftDetails | undefined): Routes { + const distanceInMeters = parseFloat((gpsDraftDetails?.distanceInMeters ?? 0).toFixed(2)); + const gpsCoordinates = gpsDraftDetails?.gpsPoints ?? []; + + return { + route0: { + distance: distanceInMeters, + geometry: { + type: 'LineString', + coordinates: gpsCoordinates.map(({lat, long}) => [long, lat]), + }, + }, + }; +} + +function getGPSCoordinates(gpsDraftDetails: GpsDraftDetails | undefined): string | undefined { + return gpsDraftDetails?.gpsPoints ? JSON.stringify(gpsDraftDetails.gpsPoints.map((val) => ({lng: val.long, lat: val.lat}))) : undefined; +} + +function getGPSConvertedDistance(gpsDraftDetails: GpsDraftDetails | undefined, unit: Unit): number { + const distanceInMeters = gpsDraftDetails?.distanceInMeters ?? 0; + const convertedDistance = DistanceRequestUtils.convertDistanceUnit(distanceInMeters, unit); + return roundToTwoDecimalPlaces(convertedDistance); +} + +async function addressFromGpsPoint(gpsPoint: {lat: number; long: number}): Promise { + try { + const [location] = await reverseGeocodeAsync({latitude: gpsPoint.lat, longitude: gpsPoint.long}); + + if (!location) { + return null; + } + + const address: string = location?.formattedAddress ?? [location?.name, location?.city, location?.region].filter(Boolean).join(', '); + + return address; + } catch (error) { + console.error('[GPS distance request] Failed to reverse geocode location to postal address: ', error); + return null; + } +} + +function coordinatesToString(gpsPoint: {lat: number; long: number}): string { + return `${gpsPoint.lat},${gpsPoint.long}`; +} + +export {getGPSRoutes, getGPSWaypoints, getGPSConvertedDistance, getGPSCoordinates, addressFromGpsPoint, coordinatesToString}; diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 9d184d3e7bff..5f80186be6fd 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -51,6 +51,7 @@ import {getMicroSecondOnyxErrorObject, getMicroSecondOnyxErrorWithTranslationKey import {readFileAsync} from '@libs/fileDownload/FileUtils'; import type {MinimalTransaction} from '@libs/Formula'; import GoogleTagManager from '@libs/GoogleTagManager'; +import {getGPSRoutes, getGPSWaypoints} from '@libs/GPSDraftDetailsUtils'; import { calculateAmount as calculateIOUAmount, formatCurrentUserToAttendee, @@ -1384,50 +1385,6 @@ function setCustomUnitRateID(transactionID: string, customUnitRateID: string | u }); } -function getGPSWaypoints(gpsDraftDetails: OnyxTypes.GpsDraftDetails | undefined): WaypointCollection { - const gpsCoordinates = gpsDraftDetails?.gpsPoints ?? []; - const firstPoint = gpsCoordinates.at(0); - const lastPoint = gpsCoordinates.at(-1); - - return { - ...(firstPoint - ? { - waypoint0: { - lat: firstPoint.lat, - lng: firstPoint.long, - address: gpsDraftDetails?.startAddress.value ?? '', - name: gpsDraftDetails?.startAddress.value ?? '', - }, - } - : {}), - ...(lastPoint - ? { - waypoint1: { - lat: lastPoint.lat, - lng: lastPoint.long, - address: gpsDraftDetails?.endAddress.value ?? '', - name: gpsDraftDetails?.endAddress.value ?? '', - }, - } - : {}), - }; -} - -function getGPSRoutes(gpsDraftDetails: OnyxTypes.GpsDraftDetails | undefined): Routes { - const distanceInMeters = parseFloat((gpsDraftDetails?.distanceInMeters ?? 0).toFixed(2)); - const gpsCoordinates = gpsDraftDetails?.gpsPoints ?? []; - - return { - route0: { - distance: distanceInMeters, - geometry: { - type: 'LineString', - coordinates: gpsCoordinates.map(({lat, long}) => [long, lat]), - }, - }, - }; -} - function setGPSTransactionDraftData(transactionID: string, gpsDraftDetails: OnyxTypes.GpsDraftDetails | undefined, distance: number) { const waypoints = getGPSWaypoints(gpsDraftDetails); const routes = getGPSRoutes(gpsDraftDetails); @@ -14574,8 +14531,6 @@ export { resetDraftTransactionsCustomUnit, savePreferredPaymentMethod, setCustomUnitRateID, - getGPSWaypoints, - getGPSRoutes, setGPSTransactionDraftData, setCustomUnitID, removeSubrate, diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 2e409bfbcf3e..de24fd032a16 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -37,6 +37,7 @@ import {isLocalFile as isLocalFileFileUtils} from '@libs/fileDownload/FileUtils' import validateReceiptFile from '@libs/fileDownload/validateReceiptFile'; import getCurrentPosition from '@libs/getCurrentPosition'; import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; +import {getGPSCoordinates} from '@libs/GPSDraftDetailsUtils'; import { isMovingTransactionFromTrackExpense as isMovingTransactionFromTrackExpenseIOUUtils, navigateToStartMoneyRequestStep, @@ -789,8 +790,7 @@ function IOURequestStepConfirmation({ isLinkedTrackedExpenseReportArchived, odometerStart: isOdometerDistanceRequest ? item.comment?.odometerStart : undefined, odometerEnd: isOdometerDistanceRequest ? item.comment?.odometerEnd : undefined, - gpsCoordinates: - isGPSDistanceRequest && gpsDraftDetails?.gpsPoints ? JSON.stringify(gpsDraftDetails.gpsPoints.map((val) => ({lng: val.long, lat: val.lat}))) : undefined, + gpsCoordinates: isGPSDistanceRequest ? getGPSCoordinates(gpsDraftDetails) : undefined, }, accountantParams: { accountant: item.accountant, @@ -873,7 +873,7 @@ function IOURequestStepConfirmation({ receipt: isManualDistanceRequest || isOdometerDistanceRequest ? receiptFiles[transaction.transactionID] : undefined, odometerStart: isOdometerDistanceRequest ? transaction.comment?.odometerStart : undefined, odometerEnd: isOdometerDistanceRequest ? transaction.comment?.odometerEnd : undefined, - gpsCoordinates: isGPSDistanceRequest && gpsDraftDetails?.gpsPoints ? JSON.stringify(gpsDraftDetails.gpsPoints.map((val) => ({lng: val.long, lat: val.lat}))) : undefined, + gpsCoordinates: isGPSDistanceRequest ? getGPSCoordinates(gpsDraftDetails) : undefined, }, backToReport, isASAPSubmitBetaEnabled, diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx index c893b1e45d10..161500c21db5 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/GPSButtons/index.tsx @@ -9,10 +9,9 @@ import useLocalize from '@hooks/useLocalize'; import useOnyx from '@hooks/useOnyx'; import useThemeStyles from '@hooks/useThemeStyles'; import {initGpsDraft, resetGPSDraftDetails, setEndAddress, setIsTracking} from '@libs/actions/GPSDraftDetails'; +import {addressFromGpsPoint, coordinatesToString} from '@libs/GPSDraftDetailsUtils'; import BackgroundLocationPermissionsFlow from '@pages/iou/request/step/IOURequestStepDistanceGPS/BackgroundLocationPermissionsFlow'; import {BACKGROUND_LOCATION_TRACKING_TASK_NAME, getBackgroundLocationTaskOptions} from '@pages/iou/request/step/IOURequestStepDistanceGPS/const'; -import addressFromGpsPoint from '@pages/iou/request/step/IOURequestStepDistanceGPS/utils/addressFromGpsPoint'; -import coordinatesToString from '@pages/iou/request/step/IOURequestStepDistanceGPS/utils/coordinatesToString'; import ONYXKEYS from '@src/ONYXKEYS'; import openSettings from './openSettings'; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index e2aa743abb38..024219a36988 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -11,11 +11,11 @@ import usePersonalPolicy from '@hooks/usePersonalPolicy'; import usePolicy from '@hooks/usePolicy'; import useShowNotFoundPageInIOUStep from '@hooks/useShowNotFoundPageInIOUStep'; import useThemeStyles from '@hooks/useThemeStyles'; -import {getGPSWaypoints, setGPSTransactionDraftData} from '@libs/actions/IOU'; +import {setGPSTransactionDraftData} from '@libs/actions/IOU'; import {handleMoneyRequestStepDistanceNavigation} from '@libs/actions/IOU/MoneyRequest'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import {getGPSConvertedDistance, getGPSCoordinates, getGPSWaypoints} from '@libs/GPSDraftDetailsUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {roundToTwoDecimalPlaces} from '@libs/NumberUtils'; import {isPaidGroupPolicy} from '@libs/PolicyUtils'; import {isArchivedReport, isPolicyExpenseChat as isPolicyExpenseChatUtils} from '@libs/ReportUtils'; import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; @@ -83,10 +83,8 @@ function IOURequestStepDistanceGPS({ const shouldSkipConfirmation = !skipConfirmation || !report?.reportID ? false : !(isArchived || isPolicyExpenseChatUtils(report)); const navigateToNextStep = () => { - const gpsCoordinates = gpsDraftDetails?.gpsPoints ? JSON.stringify(gpsDraftDetails.gpsPoints.map((val) => ({lng: val.long, lat: val.lat}))) : undefined; - const distanceInMeters = gpsDraftDetails?.distanceInMeters ?? 0; - const convertedDistance = DistanceRequestUtils.convertDistanceUnit(distanceInMeters, unit); - const distance = roundToTwoDecimalPlaces(convertedDistance); + const gpsCoordinates = getGPSCoordinates(gpsDraftDetails); + const distance = getGPSConvertedDistance(gpsDraftDetails, unit); setGPSTransactionDraftData(transactionID, gpsDraftDetails, distance); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/utils/addressFromGpsPoint.ts b/src/pages/iou/request/step/IOURequestStepDistanceGPS/utils/addressFromGpsPoint.ts deleted file mode 100644 index 403e077a0995..000000000000 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/utils/addressFromGpsPoint.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {reverseGeocodeAsync} from 'expo-location'; - -async function addressFromGpsPoint(gpsPoint: {lat: number; long: number}): Promise { - try { - const [location] = await reverseGeocodeAsync({latitude: gpsPoint.lat, longitude: gpsPoint.long}); - - if (!location) { - return null; - } - - const address: string = location?.formattedAddress ?? [location?.name, location?.city, location?.region].filter(Boolean).join(', '); - - return address; - } catch (error) { - console.error('[GPS distance request] Failed to reverse geocode location to postal address: ', error); - return null; - } -} - -export default addressFromGpsPoint; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/utils/coordinatesToString.ts b/src/pages/iou/request/step/IOURequestStepDistanceGPS/utils/coordinatesToString.ts deleted file mode 100644 index 9e049c5046d2..000000000000 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/utils/coordinatesToString.ts +++ /dev/null @@ -1,5 +0,0 @@ -function coordinatesToString(gpsPoint: {lat: number; long: number}): string { - return `${gpsPoint.lat},${gpsPoint.long}`; -} - -export default coordinatesToString; diff --git a/src/setup/backgroundLocationTrackingTask/index.native.ts b/src/setup/backgroundLocationTrackingTask/index.native.ts index f80f1380981f..0660d9ff87f7 100644 --- a/src/setup/backgroundLocationTrackingTask/index.native.ts +++ b/src/setup/backgroundLocationTrackingTask/index.native.ts @@ -2,9 +2,8 @@ import type {LocationObject} from 'expo-location'; import {defineTask} from 'expo-task-manager'; import OnyxUtils from 'react-native-onyx/dist/OnyxUtils'; import {addGpsPoints, setStartAddress} from '@libs/actions/GPSDraftDetails'; +import {addressFromGpsPoint, coordinatesToString} from '@libs/GPSDraftDetailsUtils'; import {BACKGROUND_LOCATION_TRACKING_TASK_NAME} from '@pages/iou/request/step/IOURequestStepDistanceGPS/const'; -import addressFromGpsPoint from '@pages/iou/request/step/IOURequestStepDistanceGPS/utils/addressFromGpsPoint'; -import coordinatesToString from '@pages/iou/request/step/IOURequestStepDistanceGPS/utils/coordinatesToString'; import ONYXKEYS from '@src/ONYXKEYS'; type BackgroundLocationTrackingTaskData = {locations: LocationObject[]}; From 327a4eb85ff0bc7ede0691ffb3d41f6e47c3d1ff Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 21 Jan 2026 16:56:48 +0100 Subject: [PATCH 10/20] Fix eslint warnings --- .../step/IOURequestStepConfirmation.tsx | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index de24fd032a16..dec668164163 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -807,28 +807,28 @@ function IOURequestStepConfirmation({ } }, [ - report, transactions, - receiptFiles, + privateIsArchivedMap, + isManualDistanceRequest, + isOdometerDistanceRequest, + isGPSDistanceRequest, + report, + isDraftPolicy, + action, currentUserPersonalDetails.login, currentUserPersonalDetails.accountID, - transactionTaxCode, - transactionTaxAmount, policy, - policyTags, policyCategories, - action, + policyTags, + receiptFiles, + transactionTaxCode, + transactionTaxAmount, customUnitRateID, - isDraftPolicy, - isManualDistanceRequest, - isOdometerDistanceRequest, - privateIsArchivedMap, + gpsDraftDetails, isASAPSubmitBetaEnabled, - isGPSDistanceRequest, introSelected, activePolicyID, quickAction, - gpsDraftDetails?.gpsPoints, firstCreatedGpsExpenseDateNewDot, ], ); @@ -890,24 +890,24 @@ function IOURequestStepConfirmation({ currentUserPersonalDetails.accountID, iouType, policy, - isOdometerDistanceRequest, policyCategories, policyTags, policyRecentlyUsedCategories, policyRecentlyUsedTags, - isManualDistanceRequest, transactionDistance, transactionTaxCode, transactionTaxAmount, customUnitRateID, + isManualDistanceRequest, + isOdometerDistanceRequest, receiptFiles, + isGPSDistanceRequest, + gpsDraftDetails, backToReport, isASAPSubmitBetaEnabled, transactionViolations, quickAction, policyRecentlyUsedCurrencies, - gpsDraftDetails?.gpsPoints, - isGPSDistanceRequest, firstCreatedGpsExpenseDateNewDot, ], ); From 78cc50853b73f6ddea491fb29e2563acdd8a9739 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Wed, 21 Jan 2026 23:05:22 +0100 Subject: [PATCH 11/20] Changes according to feedback --- src/components/ReportActionItem/MoneyRequestView.tsx | 3 ++- src/hooks/useFetchRoute.ts | 4 ++-- src/libs/GPSDraftDetailsUtils.ts | 12 +++++++----- src/pages/iou/SplitBillDetailsPage.tsx | 3 ++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 0a4996171c9f..282eb57d554b 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -94,6 +94,7 @@ import { isExpenseUnreported as isExpenseUnreportedTransactionUtils, isGPSDistanceRequest as isGPSDistanceRequestTransactionUtils, isManualDistanceRequest as isManualDistanceRequestTransactionUtils, + isMapDistanceRequest as isMapDistanceRequestTransactionUtils, isOdometerDistanceRequest as isOdometerDistanceRequestTransactionUtils, isPerDiemRequest as isPerDiemRequestTransactionUtils, isScanning, @@ -285,7 +286,7 @@ function MoneyRequestView({ const isManualDistanceRequest = isManualDistanceRequestTransactionUtils(transaction, !!mergeTransactionID); const isGPSDistanceRequest = isGPSDistanceRequestTransactionUtils(transaction); const isOdometerDistanceRequest = isOdometerDistanceRequestTransactionUtils(transaction); - const isMapDistanceRequest = isDistanceRequest && !isManualDistanceRequest && !isGPSDistanceRequest; + const isMapDistanceRequest = isMapDistanceRequestTransactionUtils(transaction); const isTransactionScanning = isScanning(updatedTransaction ?? transaction); const hasRoute = hasRouteTransactionUtils(transactionBackup ?? transaction, isDistanceRequest); diff --git a/src/hooks/useFetchRoute.ts b/src/hooks/useFetchRoute.ts index a384bb82b4e8..7d66a21f0cac 100644 --- a/src/hooks/useFetchRoute.ts +++ b/src/hooks/useFetchRoute.ts @@ -2,7 +2,7 @@ import {deepEqual} from 'fast-equals'; import {useEffect} from 'react'; import type {OnyxEntry} from 'react-native-onyx'; import {getRoute} from '@libs/actions/Transaction'; -import {getValidWaypoints, hasRoute as hasRouteTransactionUtils, isDistanceRequest, isGPSDistanceRequest, isManualDistanceRequest} from '@libs/TransactionUtils'; +import {getValidWaypoints, hasRoute as hasRouteTransactionUtils, isMapDistanceRequest as isMapDistanceRequestTransactionUtils} from '@libs/TransactionUtils'; import type {IOUAction} from '@src/CONST'; import CONST from '@src/CONST'; import type {Transaction} from '@src/types/onyx'; @@ -25,7 +25,7 @@ export default function useFetchRoute( const validatedWaypoints = getValidWaypoints(waypoints); const previousValidatedWaypoints = usePrevious(validatedWaypoints); const haveValidatedWaypointsChanged = !deepEqual(previousValidatedWaypoints, validatedWaypoints); - const isMapDistanceRequest = isDistanceRequest(transaction) && !isManualDistanceRequest(transaction) && !isGPSDistanceRequest(transaction); + const isMapDistanceRequest = isMapDistanceRequestTransactionUtils(transaction); const shouldFetchRoute = isMapDistanceRequest && (isRouteAbsentWithoutErrors || haveValidatedWaypointsChanged) && !isLoadingRoute && Object.keys(validatedWaypoints).length > 1; useEffect(() => { diff --git a/src/libs/GPSDraftDetailsUtils.ts b/src/libs/GPSDraftDetailsUtils.ts index 58f9056c66f7..d03336d833cd 100644 --- a/src/libs/GPSDraftDetailsUtils.ts +++ b/src/libs/GPSDraftDetailsUtils.ts @@ -9,6 +9,8 @@ function getGPSWaypoints(gpsDraftDetails: GpsDraftDetails | undefined): Waypoint const gpsCoordinates = gpsDraftDetails?.gpsPoints ?? []; const firstPoint = gpsCoordinates.at(0); const lastPoint = gpsCoordinates.at(-1); + const startAddress = gpsDraftDetails?.startAddress.value ?? ''; + const endAddress = gpsDraftDetails?.endAddress.value ?? ''; return { ...(firstPoint @@ -16,8 +18,8 @@ function getGPSWaypoints(gpsDraftDetails: GpsDraftDetails | undefined): Waypoint waypoint0: { lat: firstPoint.lat, lng: firstPoint.long, - address: gpsDraftDetails?.startAddress.value ?? '', - name: gpsDraftDetails?.startAddress.value ?? '', + address: startAddress, + name: startAddress, }, } : {}), @@ -26,8 +28,8 @@ function getGPSWaypoints(gpsDraftDetails: GpsDraftDetails | undefined): Waypoint waypoint1: { lat: lastPoint.lat, lng: lastPoint.long, - address: gpsDraftDetails?.endAddress.value ?? '', - name: gpsDraftDetails?.endAddress.value ?? '', + address: endAddress, + name: endAddress, }, } : {}), @@ -35,7 +37,7 @@ function getGPSWaypoints(gpsDraftDetails: GpsDraftDetails | undefined): Waypoint } function getGPSRoutes(gpsDraftDetails: GpsDraftDetails | undefined): Routes { - const distanceInMeters = parseFloat((gpsDraftDetails?.distanceInMeters ?? 0).toFixed(2)); + const distanceInMeters = roundToTwoDecimalPlaces(gpsDraftDetails?.distanceInMeters ?? 0); const gpsCoordinates = gpsDraftDetails?.gpsPoints ?? []; return { diff --git a/src/pages/iou/SplitBillDetailsPage.tsx b/src/pages/iou/SplitBillDetailsPage.tsx index ed044835f64b..c4da57b186f1 100644 --- a/src/pages/iou/SplitBillDetailsPage.tsx +++ b/src/pages/iou/SplitBillDetailsPage.tsx @@ -30,6 +30,7 @@ import { isDistanceRequest as isDistanceRequestUtil, isGPSDistanceRequest as isGPSDistanceRequestUtil, isManualDistanceRequest as isManualDistanceRequestUtil, + isMapDistanceRequest as isMapDistanceRequestUtil, isScanning, } from '@libs/TransactionUtils'; import withReportAndReportActionOrNotFound from '@pages/home/report/withReportAndReportActionOrNotFound'; @@ -84,7 +85,7 @@ function SplitBillDetailsPage({route, report, reportAction}: SplitBillDetailsPag session?.accountID === actorAccountID && (areRequiredFieldsEmpty(transaction, transactionReport) || (transaction?.amount === 0 && !hasReceipt(transaction))) && !isDistanceRequest; const isManualDistanceRequest = isManualDistanceRequestUtil(transaction); const isGPSDistanceRequest = isGPSDistanceRequestUtil(transaction); - const isMapDistanceRequest = isDistanceRequest && !isGPSDistanceRequest && !isManualDistanceRequest; + const isMapDistanceRequest = isMapDistanceRequestUtil(transaction); const [isConfirmed, setIsConfirmed] = useState(false); const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); From 092445e67989b4fe8d0c02de1b154778869bcef0 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Thu, 22 Jan 2026 10:51:07 +0100 Subject: [PATCH 12/20] Revert mobile-expensify hash change --- Mobile-Expensify | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Mobile-Expensify b/Mobile-Expensify index 1e7de7f2db5b..056474e9aae4 160000 --- a/Mobile-Expensify +++ b/Mobile-Expensify @@ -1 +1 @@ -Subproject commit 1e7de7f2db5b65847fa9f5090b784298c1974b54 +Subproject commit 056474e9aae4453a822e51aab49bc7f3391e1a42 From 2de4064277d77428221d8431738bb9fe93a26d8a Mon Sep 17 00:00:00 2001 From: GCyganek Date: Thu, 22 Jan 2026 11:20:53 +0100 Subject: [PATCH 13/20] Add allBetas --- .../iou/request/step/IOURequestStepDistanceGPS/index.native.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index 024219a36988..99b2e7959f69 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -68,6 +68,7 @@ function IOURequestStepDistanceGPS({ const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); + const [allBetas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: false}); const currentUserAccountIDParam = currentUserPersonalDetails.accountID; const currentUserEmailParam = currentUserPersonalDetails.login ?? ''; @@ -120,6 +121,7 @@ function IOURequestStepDistanceGPS({ gpsCoordinates, gpsDistance: distance, firstCreatedGpsExpenseDateNewDot, + allBetas, }); }; From 72b96c577bdcb12a01b70d1c404b372e504ef5cd Mon Sep 17 00:00:00 2001 From: GCyganek Date: Thu, 22 Jan 2026 11:56:56 +0100 Subject: [PATCH 14/20] Temporary fix to tell GPS expense from Map expense --- src/libs/GPSDraftDetailsUtils.ts | 2 ++ src/libs/TransactionUtils/index.ts | 25 +++++++++++++++++++++---- 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/libs/GPSDraftDetailsUtils.ts b/src/libs/GPSDraftDetailsUtils.ts index d03336d833cd..0ac118243692 100644 --- a/src/libs/GPSDraftDetailsUtils.ts +++ b/src/libs/GPSDraftDetailsUtils.ts @@ -16,6 +16,7 @@ function getGPSWaypoints(gpsDraftDetails: GpsDraftDetails | undefined): Waypoint ...(firstPoint ? { waypoint0: { + keyForList: 'gps_start', // temporary for hasGPSWaypoints() lat: firstPoint.lat, lng: firstPoint.long, address: startAddress, @@ -26,6 +27,7 @@ function getGPSWaypoints(gpsDraftDetails: GpsDraftDetails | undefined): Waypoint ...(lastPoint ? { waypoint1: { + keyForList: 'gps_stop', // temporary for hasGPSWaypoints() lat: lastPoint.lat, lng: lastPoint.long, address: endAddress, diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 345a29e91647..171387a74576 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -170,6 +170,25 @@ function isDistanceTypeRequest(transaction: OnyxEntry): boolean { return hasDistanceCustomUnit(transaction); } +/** + * todo: Currently there is no way to tell server map transaction object from + * server GPS transaction object, this will be discussed and updated later. + * To fix this temporarily we set keyForList of GPS waypoints to 'gps_start' and 'gps_end' + * and use that to determine if it's a GPS or Map transaction. This should be changed before + * the first GPS release. + */ +function hasGPSWaypoints(transaction: OnyxEntry) { + const waypoints = transaction?.comment?.waypoints; + + if (!waypoints) { + return false; + } + + const waypoint = Object.values(waypoints).at(0); + + return !!waypoint?.keyForList?.startsWith('gps'); +} + function isMapDistanceRequest(transaction: OnyxEntry): boolean { // This is used during the expense creation flow before the transaction has been saved to the server if (lodashHas(transaction, 'iouRequestType')) { @@ -177,7 +196,7 @@ function isMapDistanceRequest(transaction: OnyxEntry): boolean { } // This is the case for transaction objects once they have been saved to the server - return hasDistanceCustomUnit(transaction); + return hasDistanceCustomUnit(transaction) && !hasGPSWaypoints(transaction); } function isGPSDistanceRequest(transaction: OnyxEntry): boolean { @@ -186,10 +205,8 @@ function isGPSDistanceRequest(transaction: OnyxEntry): boolean { return transaction?.iouRequestType === CONST.IOU.REQUEST_TYPE.DISTANCE_GPS; } - // todo: currently there is no way to tell server map transaction object from - // server gps transaction object, this will be discussed and updated later // This is the case for transaction objects once they have been saved to the server - return hasDistanceCustomUnit(transaction); + return hasGPSWaypoints(transaction); } function isManualDistanceRequest(transaction: OnyxEntry, isUpdatedMergeTransaction = false): boolean { From 94e49e65e647e8794365fb4e440b6f3f7e126854 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Thu, 22 Jan 2026 13:26:07 +0100 Subject: [PATCH 15/20] Add missing dependency array --- src/components/MoneyRequestConfirmationList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 54c93b3263cb..2f2df819f849 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -403,7 +403,7 @@ function MoneyRequestConfirmationList({ const distanceWithNewUnit = roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distance, unit)); setMoneyRequestDistance(transactionID, distanceWithNewUnit, true); - }); + }, [distance, isReadOnly, shouldUpdateDistance, transactionID, unit]); let amountToBeUsed = iouAmount; From f662e0c024c6c4471262e4fb693e052f0c12e3f3 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Thu, 22 Jan 2026 17:56:40 +0100 Subject: [PATCH 16/20] shouldUseDefaultExpensePolicy --- src/libs/actions/IOU/MoneyRequest.ts | 17 +++-------------- src/libs/shouldUseDefaultExpensePolicy.ts | 16 ++++++++++++++++ .../iou/request/step/IOURequestStepAmount.tsx | 10 ++-------- .../DistanceCounter/index.tsx | 10 ++-------- .../IOURequestStepDistanceGPS/index.native.tsx | 11 ++--------- .../step/IOURequestStepDistanceManual.tsx | 12 ++---------- .../step/IOURequestStepDistanceOdometer.tsx | 12 ++---------- 7 files changed, 29 insertions(+), 59 deletions(-) create mode 100644 src/libs/shouldUseDefaultExpensePolicy.ts diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index 24ff36e255e0..8ac1b6087a07 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -5,10 +5,9 @@ import {navigateToConfirmationPage, navigateToParticipantPage} from '@libs/IOUUt import Log from '@libs/Log'; import Navigation from '@libs/Navigation/Navigation'; import {getManagerMcTestParticipant, getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; -import {isPaidGroupPolicy} from '@libs/PolicyUtils'; import {findSelfDMReportID, generateReportID, getPolicyExpenseChat} from '@libs/ReportUtils'; import type {OptionData} from '@libs/ReportUtils'; -import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; +import shouldUseDefaultExpensePolicy from '@libs/shouldUseDefaultExpensePolicy'; import {getValidWaypoints} from '@libs/TransactionUtils'; import type {ReceiptFile} from '@pages/iou/request/step/IOURequestStepScan/types'; import {setTransactionReport} from '@userActions/Transaction'; @@ -411,12 +410,7 @@ function handleMoneyRequestStepScanParticipants({ // If there was no reportID, then that means the user started this flow from the global + menu // and an optimistic reportID was generated. In that case, the next step is to select the participants for this expense. - if ( - iouType === CONST.IOU.TYPE.CREATE && - isPaidGroupPolicy(defaultExpensePolicy) && - defaultExpensePolicy?.isPolicyExpenseChatEnabled && - !shouldRestrictUserBillableActions(defaultExpensePolicy.id) - ) { + if (shouldUseDefaultExpensePolicy(iouType, defaultExpensePolicy)) { const activePolicyExpenseChat = getPolicyExpenseChat(currentUserAccountID, defaultExpensePolicy?.id); const shouldAutoReport = !!defaultExpensePolicy?.autoReporting || isAutoReporting; const transactionReportID = shouldAutoReport ? activePolicyExpenseChat?.reportID : CONST.REPORT.UNREPORTED_REPORT_ID; @@ -601,12 +595,7 @@ function handleMoneyRequestStepDistanceNavigation({ // If there was no reportID, then that means the user started this flow from the global menu // and an optimistic reportID was generated. In that case, the next step is to select the participants for this expense. - if ( - iouType === CONST.IOU.TYPE.CREATE && - isPaidGroupPolicy(defaultExpensePolicy) && - defaultExpensePolicy?.isPolicyExpenseChatEnabled && - !shouldRestrictUserBillableActions(defaultExpensePolicy.id) - ) { + if (shouldUseDefaultExpensePolicy(iouType, defaultExpensePolicy)) { const activePolicyExpenseChat = getPolicyExpenseChat(currentUserAccountID, defaultExpensePolicy?.id); const shouldAutoReport = !!defaultExpensePolicy?.autoReporting || isAutoReporting; const transactionReportID = shouldAutoReport ? activePolicyExpenseChat?.reportID : CONST.REPORT.UNREPORTED_REPORT_ID; diff --git a/src/libs/shouldUseDefaultExpensePolicy.ts b/src/libs/shouldUseDefaultExpensePolicy.ts new file mode 100644 index 000000000000..47c058d2e687 --- /dev/null +++ b/src/libs/shouldUseDefaultExpensePolicy.ts @@ -0,0 +1,16 @@ +import type {IOUType} from '@src/CONST'; +import CONST from '@src/CONST'; +import type {OnyxInputOrEntry, Policy} from '@src/types/onyx'; +import {isPaidGroupPolicy} from './PolicyUtils'; +import {shouldRestrictUserBillableActions} from './SubscriptionUtils'; + +function shouldUseDefaultExpensePolicy(iouType: IOUType, defaultExpensePolicy: OnyxInputOrEntry) { + return ( + iouType === CONST.IOU.TYPE.CREATE && + isPaidGroupPolicy(defaultExpensePolicy) && + defaultExpensePolicy?.isPolicyExpenseChatEnabled && + !shouldRestrictUserBillableActions(defaultExpensePolicy.id) + ); +} + +export default shouldUseDefaultExpensePolicy; diff --git a/src/pages/iou/request/step/IOURequestStepAmount.tsx b/src/pages/iou/request/step/IOURequestStepAmount.tsx index 96c373133a6b..e93ef4c44381 100644 --- a/src/pages/iou/request/step/IOURequestStepAmount.tsx +++ b/src/pages/iou/request/step/IOURequestStepAmount.tsx @@ -21,9 +21,8 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {isMovingTransactionFromTrackExpense, navigateToConfirmationPage, navigateToParticipantPage, shouldRequireMerchant} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; -import {isPaidGroupPolicy} from '@libs/PolicyUtils'; import {getPolicyExpenseChat, getReportOrDraftReport, getTransactionDetails, isMoneyRequestReport, isPolicyExpenseChat, isSelfDM, shouldEnableNegative} from '@libs/ReportUtils'; -import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; +import shouldUseDefaultExpensePolicy from '@libs/shouldUseDefaultExpensePolicy'; import {calculateTaxAmount, getAmount, getCurrency, getDefaultTaxCode, getRequestType, getTaxValue, isDistanceRequest, isExpenseUnreported} from '@libs/TransactionUtils'; import MoneyRequestAmountForm from '@pages/iou/MoneyRequestAmountForm'; import { @@ -296,12 +295,7 @@ function IOURequestStepAmount({ // Starting from global + menu means no participant context exists yet, // so we need to handle participant selection based on available workspace settings - if ( - iouType === CONST.IOU.TYPE.CREATE && - isPaidGroupPolicy(defaultExpensePolicy) && - defaultExpensePolicy?.isPolicyExpenseChatEnabled && - !shouldRestrictUserBillableActions(defaultExpensePolicy.id) - ) { + if (shouldUseDefaultExpensePolicy(iouType, defaultExpensePolicy)) { const activePolicyExpenseChat = getPolicyExpenseChat(currentUserAccountIDParam, defaultExpensePolicy?.id); const shouldAutoReport = !!defaultExpensePolicy?.autoReporting || !!personalPolicy?.autoReporting; const transactionReportID = shouldAutoReport ? activePolicyExpenseChat?.reportID : CONST.REPORT.UNREPORTED_REPORT_ID; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx index 09aaffb2ead0..9e6fbae534b0 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/DistanceCounter/index.tsx @@ -7,10 +7,8 @@ import useOnyx from '@hooks/useOnyx'; import usePolicy from '@hooks/usePolicy'; import useThemeStyles from '@hooks/useThemeStyles'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; -import {isPaidGroupPolicy} from '@libs/PolicyUtils'; -import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; +import shouldUseDefaultExpensePolicyUtil from '@libs/shouldUseDefaultExpensePolicy'; import type {IOUType} from '@src/CONST'; -import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {Report, Transaction} from '@src/types/onyx'; import TripStatusIndicator from './TripStatusIndicator'; @@ -31,11 +29,7 @@ function DistanceCounter({report, transaction, iouType}: DistanceCounterProps) { const defaultExpensePolicy = useDefaultExpensePolicy(); - const shouldUseDefaultExpensePolicy = - iouType === CONST.IOU.TYPE.CREATE && - isPaidGroupPolicy(defaultExpensePolicy) && - defaultExpensePolicy?.isPolicyExpenseChatEnabled && - !shouldRestrictUserBillableActions(defaultExpensePolicy.id); + const shouldUseDefaultExpensePolicy = shouldUseDefaultExpensePolicyUtil(iouType, defaultExpensePolicy); const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy}).unit; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index 99b2e7959f69..231d302775b9 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -16,9 +16,8 @@ import {handleMoneyRequestStepDistanceNavigation} from '@libs/actions/IOU/MoneyR import DistanceRequestUtils from '@libs/DistanceRequestUtils'; import {getGPSConvertedDistance, getGPSCoordinates, getGPSWaypoints} from '@libs/GPSDraftDetailsUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {isPaidGroupPolicy} from '@libs/PolicyUtils'; import {isArchivedReport, isPolicyExpenseChat as isPolicyExpenseChatUtils} from '@libs/ReportUtils'; -import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; +import shouldUseDefaultExpensePolicyUtil from '@libs/shouldUseDefaultExpensePolicy'; import {getRateID} from '@libs/TransactionUtils'; import StepScreenWrapper from '@pages/iou/request/step/StepScreenWrapper'; import withFullTransactionOrNotFound from '@pages/iou/request/step/withFullTransactionOrNotFound'; @@ -68,15 +67,10 @@ function IOURequestStepDistanceGPS({ const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT); const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED, {canBeMissing: true}); const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID, {canBeMissing: true}); - const [allBetas] = useOnyx(ONYXKEYS.BETAS, {canBeMissing: false}); const currentUserAccountIDParam = currentUserPersonalDetails.accountID; const currentUserEmailParam = currentUserPersonalDetails.login ?? ''; - const shouldUseDefaultExpensePolicy = - iouType === CONST.IOU.TYPE.CREATE && - isPaidGroupPolicy(defaultExpensePolicy) && - defaultExpensePolicy?.isPolicyExpenseChatEnabled && - !shouldRestrictUserBillableActions(defaultExpensePolicy.id); + const shouldUseDefaultExpensePolicy = shouldUseDefaultExpensePolicyUtil(iouType, defaultExpensePolicy); const customUnitRateID = getRateID(transaction); const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy}).unit; @@ -121,7 +115,6 @@ function IOURequestStepDistanceGPS({ gpsCoordinates, gpsDistance: distance, firstCreatedGpsExpenseDateNewDot, - allBetas, }); }; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx index 44374463af6b..ed28529ac776 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceManual.tsx @@ -25,9 +25,8 @@ import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID'; import {shouldUseTransactionDraft} from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import {roundToTwoDecimalPlaces} from '@libs/NumberUtils'; -import {isPaidGroupPolicy} from '@libs/PolicyUtils'; import {isArchivedReport, isPolicyExpenseChat as isPolicyExpenseChatUtils} from '@libs/ReportUtils'; -import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; +import shouldUseDefaultExpensePolicyUtil from '@libs/shouldUseDefaultExpensePolicy'; import {getRateID} from '@libs/TransactionUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; @@ -94,14 +93,7 @@ function IOURequestStepDistanceManual({ const currentUserAccountIDParam = currentUserPersonalDetails.accountID; const currentUserEmailParam = currentUserPersonalDetails.login ?? ''; - const shouldUseDefaultExpensePolicy = useMemo( - () => - iouType === CONST.IOU.TYPE.CREATE && - isPaidGroupPolicy(defaultExpensePolicy) && - defaultExpensePolicy?.isPolicyExpenseChatEnabled && - !shouldRestrictUserBillableActions(defaultExpensePolicy.id), - [iouType, defaultExpensePolicy], - ); + const shouldUseDefaultExpensePolicy = useMemo(() => shouldUseDefaultExpensePolicyUtil(iouType, defaultExpensePolicy), [iouType, defaultExpensePolicy]); const customUnitRateID = getRateID(transaction); const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy}).unit; diff --git a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx index 2e7cb49d6e8c..3e168c20723b 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceOdometer.tsx @@ -37,9 +37,8 @@ import {navigateToParticipantPage, shouldUseTransactionDraft} from '@libs/IOUUti import Navigation from '@libs/Navigation/Navigation'; import {roundToTwoDecimalPlaces} from '@libs/NumberUtils'; import {getParticipantsOption, getReportOption} from '@libs/OptionsListUtils'; -import {isPaidGroupPolicy} from '@libs/PolicyUtils'; import {getPolicyExpenseChat, isArchivedReport, isPolicyExpenseChat as isPolicyExpenseChatUtils} from '@libs/ReportUtils'; -import {shouldRestrictUserBillableActions} from '@libs/SubscriptionUtils'; +import shouldUseDefaultExpensePolicyUtil from '@libs/shouldUseDefaultExpensePolicy'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -121,14 +120,7 @@ function IOURequestStepDistanceOdometer({ const currentUserEmailParam = currentUserPersonalDetails.login ?? ''; const [shouldEnableDiscardConfirmation, setShouldEnableDiscardConfirmation] = useState(!isEditingConfirmation && !isEditing); - const shouldUseDefaultExpensePolicy = useMemo( - () => - iouType === CONST.IOU.TYPE.CREATE && - isPaidGroupPolicy(defaultExpensePolicy) && - defaultExpensePolicy?.isPolicyExpenseChatEnabled && - !shouldRestrictUserBillableActions(defaultExpensePolicy.id), - [iouType, defaultExpensePolicy], - ); + const shouldUseDefaultExpensePolicy = useMemo(() => shouldUseDefaultExpensePolicyUtil(iouType, defaultExpensePolicy), [iouType, defaultExpensePolicy]); const unit = DistanceRequestUtils.getRate({transaction, policy: shouldUseDefaultExpensePolicy ? defaultExpensePolicy : policy}).unit; From 7c22f9d15f40cf9b80a0e78fc881123cd11ddbdf Mon Sep 17 00:00:00 2001 From: GCyganek Date: Fri, 23 Jan 2026 09:33:43 +0100 Subject: [PATCH 17/20] Typescript error fix --- src/libs/actions/IOU/MoneyRequest.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index fa7e57d5e8f4..ab0feaf45223 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -595,7 +595,7 @@ function handleMoneyRequestStepDistanceNavigation({ // If there was no reportID, then that means the user started this flow from the global menu // and an optimistic reportID was generated. In that case, the next step is to select the participants for this expense. - if (shouldUseDefaultExpensePolicy(iouType, defaultExpensePolicy)) { + if (defaultExpensePolicy && shouldUseDefaultExpensePolicy(iouType, defaultExpensePolicy)) { const activePolicyExpenseChat = getPolicyExpenseChat(currentUserAccountID, defaultExpensePolicy?.id); const shouldAutoReport = !!defaultExpensePolicy?.autoReporting || isAutoReporting; const transactionReportID = shouldAutoReport ? activePolicyExpenseChat?.reportID : CONST.REPORT.UNREPORTED_REPORT_ID; From 9a4746887d77cb8cc3043b7be07167695f7fc0e5 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Fri, 23 Jan 2026 12:30:55 +0100 Subject: [PATCH 18/20] Fix distance conversion when no default participant --- src/components/MoneyRequestConfirmationList.tsx | 16 +++++++++------- src/libs/GPSDraftDetailsUtils.ts | 9 ++++++--- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/src/components/MoneyRequestConfirmationList.tsx b/src/components/MoneyRequestConfirmationList.tsx index 65aab3db0f1d..d5bbfee50444 100755 --- a/src/components/MoneyRequestConfirmationList.tsx +++ b/src/components/MoneyRequestConfirmationList.tsx @@ -36,11 +36,11 @@ import {getIsMissingAttendeesViolation} from '@libs/AttendeeUtils'; import {isCategoryDescriptionRequired} from '@libs/CategoryUtils'; import {convertToBackendAmount, convertToDisplayString, convertToDisplayStringWithoutCurrency, getCurrencyDecimals} from '@libs/CurrencyUtils'; import DistanceRequestUtils from '@libs/DistanceRequestUtils'; +import {calculateGPSDistance} from '@libs/GPSDraftDetailsUtils'; import {calculateAmount, insertTagIntoTransactionTagsString, isMovingTransactionFromTrackExpense as isMovingTransactionFromTrackExpenseUtil} from '@libs/IOUUtils'; import Log from '@libs/Log'; import {validateAmount} from '@libs/MoneyRequestUtils'; import Navigation from '@libs/Navigation/Navigation'; -import {roundToTwoDecimalPlaces} from '@libs/NumberUtils'; import {getIOUConfirmationOptionsFromPayeePersonalDetail, hasEnabledOptions} from '@libs/OptionsListUtils'; import {getTagLists, isTaxTrackingEnabled} from '@libs/PolicyUtils'; import {isSelectedManagerMcTest} from '@libs/ReportUtils'; @@ -394,16 +394,18 @@ function MoneyRequestConfirmationList({ const distanceRequestAmount = DistanceRequestUtils.getDistanceRequestAmount(distance, unit ?? CONST.CUSTOM_UNITS.DISTANCE_UNIT_MILES, rate ?? 0); - // Update distance when recipients changed and have different distance unit - const shouldUpdateDistance = isGPSDistanceRequest && prevUnit !== unit; + // Update GPS distance whenever the current distance unit differs from the one that was used + // to calculate the distance stored in transaction.comment.customUnit.quantity + const gpsDistance = transaction?.comment?.customUnit?.quantity; + const gpsDistanceWithCurrentDistanceUnit = calculateGPSDistance(distance, unit); + const shouldUpdateGpsDistance = gpsDistance !== gpsDistanceWithCurrentDistanceUnit; useEffect(() => { - if (!shouldUpdateDistance || !transactionID || isReadOnly) { + if (!shouldUpdateGpsDistance || !transactionID || isReadOnly) { return; } - const distanceWithNewUnit = roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distance, unit)); - setMoneyRequestDistance(transactionID, distanceWithNewUnit, true); - }, [distance, isReadOnly, shouldUpdateDistance, transactionID, unit]); + setMoneyRequestDistance(transactionID, gpsDistanceWithCurrentDistanceUnit, true); + }, [shouldUpdateGpsDistance, transactionID, isReadOnly, gpsDistanceWithCurrentDistanceUnit]); let amountToBeUsed = iouAmount; diff --git a/src/libs/GPSDraftDetailsUtils.ts b/src/libs/GPSDraftDetailsUtils.ts index 0ac118243692..c9ed1e9041d0 100644 --- a/src/libs/GPSDraftDetailsUtils.ts +++ b/src/libs/GPSDraftDetailsUtils.ts @@ -57,10 +57,13 @@ function getGPSCoordinates(gpsDraftDetails: GpsDraftDetails | undefined): string return gpsDraftDetails?.gpsPoints ? JSON.stringify(gpsDraftDetails.gpsPoints.map((val) => ({lng: val.long, lat: val.lat}))) : undefined; } +function calculateGPSDistance(distanceInMeters: number, unit: Unit): number { + return roundToTwoDecimalPlaces(DistanceRequestUtils.convertDistanceUnit(distanceInMeters, unit)); +} + function getGPSConvertedDistance(gpsDraftDetails: GpsDraftDetails | undefined, unit: Unit): number { const distanceInMeters = gpsDraftDetails?.distanceInMeters ?? 0; - const convertedDistance = DistanceRequestUtils.convertDistanceUnit(distanceInMeters, unit); - return roundToTwoDecimalPlaces(convertedDistance); + return calculateGPSDistance(distanceInMeters, unit); } async function addressFromGpsPoint(gpsPoint: {lat: number; long: number}): Promise { @@ -84,4 +87,4 @@ function coordinatesToString(gpsPoint: {lat: number; long: number}): string { return `${gpsPoint.lat},${gpsPoint.long}`; } -export {getGPSRoutes, getGPSWaypoints, getGPSConvertedDistance, getGPSCoordinates, addressFromGpsPoint, coordinatesToString}; +export {getGPSRoutes, getGPSWaypoints, getGPSConvertedDistance, getGPSCoordinates, addressFromGpsPoint, coordinatesToString, calculateGPSDistance}; From caba976b8b71aba16de1068d88ef17e226111d14 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Fri, 23 Jan 2026 13:42:25 +0100 Subject: [PATCH 19/20] revert firstCreatedGpsExpenseDateNewDot changes for now --- src/libs/actions/IOU/MoneyRequest.ts | 4 ---- src/libs/actions/IOU/index.ts | 20 ------------------- .../step/IOURequestStepConfirmation.tsx | 5 ----- .../index.native.tsx | 2 -- 4 files changed, 31 deletions(-) diff --git a/src/libs/actions/IOU/MoneyRequest.ts b/src/libs/actions/IOU/MoneyRequest.ts index ab0feaf45223..2d38b0e8583c 100644 --- a/src/libs/actions/IOU/MoneyRequest.ts +++ b/src/libs/actions/IOU/MoneyRequest.ts @@ -128,7 +128,6 @@ type MoneyRequestStepDistanceNavigationParams = { introSelected?: IntroSelected; activePolicyID?: string; privateIsArchived?: string; - firstCreatedGpsExpenseDateNewDot?: string; gpsCoordinates?: string; gpsDistance?: number; }; @@ -480,7 +479,6 @@ function handleMoneyRequestStepDistanceNavigation({ introSelected, activePolicyID, privateIsArchived, - firstCreatedGpsExpenseDateNewDot, gpsCoordinates, gpsDistance, }: MoneyRequestStepDistanceNavigationParams) { @@ -550,7 +548,6 @@ function handleMoneyRequestStepDistanceNavigation({ introSelected, activePolicyID, quickAction, - firstCreatedGpsExpenseDateNewDot, }); return; } @@ -583,7 +580,6 @@ function handleMoneyRequestStepDistanceNavigation({ transactionViolations, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], - firstCreatedGpsExpenseDateNewDot, }); return; } diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 261ac34e1740..8758a0a94dbd 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -638,7 +638,6 @@ type CreateDistanceRequestInformation = { transactionViolations: OnyxCollection; quickAction: OnyxEntry; policyRecentlyUsedCurrencies: string[]; - firstCreatedGpsExpenseDateNewDot?: string; }; type CreateSplitsTransactionParams = Omit & { @@ -709,7 +708,6 @@ type CreateTrackExpenseParams = { introSelected: OnyxEntry; activePolicyID: string | undefined; quickAction: OnyxEntry; - firstCreatedGpsExpenseDateNewDot?: string; }; type GetTrackExpenseInformationTransactionParams = { @@ -6500,7 +6498,6 @@ function trackExpense(params: CreateTrackExpenseParams) { introSelected, activePolicyID, quickAction, - firstCreatedGpsExpenseDateNewDot, } = params; const {participant, payeeAccountID, payeeEmail} = participantParams; const {policy, policyCategories, policyTagList} = policyData; @@ -6658,14 +6655,6 @@ function trackExpense(params: CreateTrackExpenseParams) { }); } - if (isGPSDistanceRequest && !firstCreatedGpsExpenseDateNewDot) { - onyxData?.optimisticData?.push({ - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, - value: DateUtils.getDBTime(), - }); - } - const mileageRate = isCustomUnitRateIDForP2P(transaction) ? undefined : customUnitRateID; if (shouldPlaySound) { playSound(SOUNDS.DONE); @@ -7440,7 +7429,6 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest transactionViolations, quickAction, policyRecentlyUsedCurrencies, - firstCreatedGpsExpenseDateNewDot, } = distanceRequestInformation; const {policy, policyCategories, policyTagList, policyRecentlyUsedCategories, policyRecentlyUsedTags} = policyParams; const parsedComment = getParsedComment(transactionParams.comment); @@ -7614,14 +7602,6 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest }); } - if (isGPSDistanceRequest && !firstCreatedGpsExpenseDateNewDot) { - onyxData?.optimisticData?.push({ - onyxMethod: Onyx.METHOD.SET, - key: ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, - value: DateUtils.getDBTime(), - }); - } - parameters = { comment, iouReportID: iouReport.reportID, diff --git a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx index 1f2003efc099..e5e50bbb2f58 100644 --- a/src/pages/iou/request/step/IOURequestStepConfirmation.tsx +++ b/src/pages/iou/request/step/IOURequestStepConfirmation.tsx @@ -207,7 +207,6 @@ function IOURequestStepConfirmation({ const [policyTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${realPolicyID}`, {canBeMissing: true}); const [policyRecentlyUsedTags] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_TAGS}${realPolicyID}`, {canBeMissing: true}); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); - const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const [userLocation] = useOnyx(ONYXKEYS.USER_LOCATION, {canBeMissing: true}); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); @@ -800,7 +799,6 @@ function IOURequestStepConfirmation({ introSelected, activePolicyID, quickAction, - firstCreatedGpsExpenseDateNewDot, }); } }, @@ -827,7 +825,6 @@ function IOURequestStepConfirmation({ introSelected, activePolicyID, quickAction, - firstCreatedGpsExpenseDateNewDot, ], ); @@ -878,7 +875,6 @@ function IOURequestStepConfirmation({ transactionViolations, quickAction, policyRecentlyUsedCurrencies: policyRecentlyUsedCurrencies ?? [], - firstCreatedGpsExpenseDateNewDot, }); }, [ @@ -906,7 +902,6 @@ function IOURequestStepConfirmation({ transactionViolations, quickAction, policyRecentlyUsedCurrencies, - firstCreatedGpsExpenseDateNewDot, ], ); diff --git a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx index 231d302775b9..635668891478 100644 --- a/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepDistanceGPS/index.native.tsx @@ -53,7 +53,6 @@ function IOURequestStepDistanceGPS({ const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); const [policyRecentlyUsedCurrencies] = useOnyx(ONYXKEYS.RECENTLY_USED_CURRENCIES, {canBeMissing: true}); const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {canBeMissing: true}); - const [firstCreatedGpsExpenseDateNewDot] = useOnyx(ONYXKEYS.NVP_FIRST_CREATED_GPS_EXPENSE_DATE_NEW_DOT, {canBeMissing: true}); const isEditing = action === CONST.IOU.ACTION.EDIT; const isCreatingNewRequest = !isEditing; @@ -114,7 +113,6 @@ function IOURequestStepDistanceGPS({ privateIsArchived: reportNameValuePairs?.private_isArchived, gpsCoordinates, gpsDistance: distance, - firstCreatedGpsExpenseDateNewDot, }); }; From ab947463139f36e1b93c0b30bdcbb56586bf9356 Mon Sep 17 00:00:00 2001 From: GCyganek Date: Fri, 23 Jan 2026 14:40:44 +0100 Subject: [PATCH 20/20] ESLint fix --- src/libs/actions/IOU/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index a0666c7b6983..ccb97d8f22cb 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -7462,7 +7462,6 @@ function createDistanceRequest(distanceRequestInformation: CreateDistanceRequest // 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 = isMoneyRequestReportReportUtils(report); - const isGPSDistanceRequest = !!gpsCoordinates; const currentChatReport = isMoneyRequestReport ? getReportOrDraftReport(report?.chatReportID) : report; const moneyRequestReportID = isMoneyRequestReport ? report?.reportID : ''; const isManualDistanceRequest = isEmptyObject(validWaypoints);