From 967b716e54fb871b720386a96a4d79f8cb3ca57b Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Sun, 22 Mar 2026 13:48:48 +0530 Subject: [PATCH 1/7] Refactor `MoneyRequestOdometerImage` functions --- src/libs/actions/IOU/index.ts | 12 +++++++----- .../IOURequestStepOdometerImage/index.native.tsx | 5 +++-- .../step/IOURequestStepOdometerImage/index.tsx | 5 +++-- .../routes/TransactionReceiptModalContent.tsx | 4 ++-- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 107604a346f6..e66470861c76 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -1686,7 +1686,7 @@ function revokeOdometerImageUri(image: FileObject | string | null | undefined, n * @param file - The image file (File object on web, URI string on native) * @param isDraft - Whether this is a draft transaction */ -function setMoneyRequestOdometerImage(transactionID: string, imageType: OdometerImageType, file: FileObject | string, isDraft: boolean) { +function setMoneyRequestOdometerImage(transaction: OnyxEntry, imageType: OdometerImageType, file: FileObject | string, isDraft: boolean) { const imageKey = imageType === CONST.IOU.ODOMETER_IMAGE_TYPE.START ? 'odometerStartImage' : 'odometerEndImage'; const normalizedFile: FileObject | string = typeof file === 'string' @@ -1697,7 +1697,7 @@ function setMoneyRequestOdometerImage(transactionID: string, imageType: Odometer type: file.type, size: file.size, }; - const transaction = isDraft ? allTransactionDrafts[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`] : allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; + const transactionID = transaction?.transactionID ; const existingImage = transaction?.comment?.[imageKey]; revokeOdometerImageUri(existingImage, normalizedFile); Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { @@ -1713,12 +1713,14 @@ function setMoneyRequestOdometerImage(transactionID: string, imageType: Odometer * @param imageType - 'start' or 'end' * @param isDraft - Whether this is a draft transaction */ -function removeMoneyRequestOdometerImage(transactionID: string, imageType: OdometerImageType, isDraft: boolean) { +function removeMoneyRequestOdometerImage(transaction: OnyxEntry, imageType: OdometerImageType, isDraft: boolean) { + if (!transaction?.transactionID) { + return; + } const imageKey = imageType === CONST.IOU.ODOMETER_IMAGE_TYPE.START ? 'odometerStartImage' : 'odometerEndImage'; - const transaction = isDraft ? allTransactionDrafts[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`] : allTransactions[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; const existingImage = transaction?.comment?.[imageKey]; revokeOdometerImageUri(existingImage); - Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { + Onyx.merge(`${isDraft ? ONYXKEYS.COLLECTION.TRANSACTION_DRAFT : ONYXKEYS.COLLECTION.TRANSACTION}${transaction?.transactionID}`, { comment: { [imageKey]: null, }, diff --git a/src/pages/iou/request/step/IOURequestStepOdometerImage/index.native.tsx b/src/pages/iou/request/step/IOURequestStepOdometerImage/index.native.tsx index d0c9a512fb85..6abc6b999a52 100644 --- a/src/pages/iou/request/step/IOURequestStepOdometerImage/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepOdometerImage/index.native.tsx @@ -67,6 +67,7 @@ function IOURequestStepOdometerImage({ route: { params: {action, iouType, transactionID, reportID, backToReport, imageType, isEditingConfirmation}, }, + transaction, }: IOURequestStepOdometerImageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -186,7 +187,7 @@ function IOURequestStepOdometerImage({ if (!file) { return; } - setMoneyRequestOdometerImage(transactionID, imageType, file, isTransactionDraft); + setMoneyRequestOdometerImage(transaction, imageType, file, isTransactionDraft); navigateBack(); }; @@ -241,7 +242,7 @@ function IOURequestStepOdometerImage({ cropImageToAspectRatio(imageObject, viewfinderLayout.current?.width, viewfinderLayout.current?.height, undefined, photo.orientation) .then(({file, filename, source}) => { setMoneyRequestOdometerImage( - transactionID, + transaction, imageType, { uri: source, diff --git a/src/pages/iou/request/step/IOURequestStepOdometerImage/index.tsx b/src/pages/iou/request/step/IOURequestStepOdometerImage/index.tsx index 3c5d9a94e4ec..effcf21909b1 100644 --- a/src/pages/iou/request/step/IOURequestStepOdometerImage/index.tsx +++ b/src/pages/iou/request/step/IOURequestStepOdometerImage/index.tsx @@ -46,6 +46,7 @@ function IOURequestStepOdometerImage({ route: { params: {action, iouType, transactionID, reportID, backToReport, imageType, isEditingConfirmation}, }, + transaction, }: IOURequestStepOdometerImageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); @@ -88,7 +89,7 @@ function IOURequestStepOdometerImage({ }; const handleImageSelected = (file: FileObject) => { - setMoneyRequestOdometerImage(transactionID, imageType, file as File, isTransactionDraft); + setMoneyRequestOdometerImage(transaction, imageType, file as File, isTransactionDraft); shouldRevokeOnUnmountRef.current = false; navigateBack(); }; @@ -230,7 +231,7 @@ function IOURequestStepOdometerImage({ if (source !== imageObject.source) { URL.revokeObjectURL(imageObject.source); } - setMoneyRequestOdometerImage(transactionID, imageType, file ?? source, isTransactionDraft); + setMoneyRequestOdometerImage(transaction, imageType, file ?? source, isTransactionDraft); navigateBack(); }) .catch((error: unknown) => { diff --git a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx index ca25690930bd..c07c69516b0a 100644 --- a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx +++ b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx @@ -304,7 +304,7 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre const rotatedFilename = file.name ?? receiptFilename; if (isOdometerImage) { - setMoneyRequestOdometerImage(transaction.transactionID, imageType, file, isDraftTransaction); + setMoneyRequestOdometerImage(transaction, imageType, file, isDraftTransaction); } else if (isDraftTransaction) { setMoneyRequestReceipt(transaction.transactionID, imageUriResult, rotatedFilename, isDraftTransaction, fileType); } else { @@ -391,7 +391,7 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre const croppedFilename = file.name ?? receiptFilename; if (isOdometerImage) { - setMoneyRequestOdometerImage(transaction.transactionID, imageType, file, isDraftTransaction); + setMoneyRequestOdometerImage(transaction, imageType, file, isDraftTransaction); } else if (isDraftTransaction) { setMoneyRequestReceipt(transaction.transactionID, imageUriResult, croppedFilename, isDraftTransaction, fileType); } else { From 34a708dbf891951ba3a79ddd1c889520691ea401 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 1 Apr 2026 00:02:03 +0530 Subject: [PATCH 2/7] cleanup --- src/libs/actions/IOU/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 666da67d987f..d3eb2aa927ac 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -1728,7 +1728,7 @@ function setMoneyRequestOdometerImage(transaction: OnyxEntry Date: Wed, 1 Apr 2026 00:14:37 +0530 Subject: [PATCH 3/7] fixes --- .../routes/TransactionReceiptModalContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx index c7c7e3296181..547d7dc2e588 100644 --- a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx +++ b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx @@ -266,14 +266,14 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre if (!transaction?.transactionID || !imageType) { return; } - removeMoneyRequestOdometerImage(transaction.transactionID, imageType, isDraftTransaction); + removeMoneyRequestOdometerImage(transaction, imageType, isDraftTransaction); const odometerGoBackRoute = isOdometerImage && (isEditingConfirmation === true ? ROUTES.MONEY_REQUEST_STEP_DISTANCE_ODOMETER.getRoute(action ?? CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID) : ROUTES.DISTANCE_REQUEST_CREATE_TAB_ODOMETER.getRoute(action ?? CONST.IOU.ACTION.CREATE, iouType, transactionID, reportID, backToReport)); Navigation.goBack(odometerGoBackRoute || undefined); - }, [transaction?.transactionID, imageType, isOdometerImage, isDraftTransaction, isEditingConfirmation, action, iouType, transactionID, reportID, backToReport]); + }, [transaction, imageType, isDraftTransaction, isOdometerImage, isEditingConfirmation, action, iouType, transactionID, reportID, backToReport]); const onDownloadAttachment = useDownloadAttachment({ isAuthTokenRequired, From 87ece269a4b7102a2f8d25bb9bee99b5ce59a0b4 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 1 Apr 2026 00:16:39 +0530 Subject: [PATCH 4/7] fixes --- .../routes/TransactionReceiptModalContent.tsx | 18 ++---------------- 1 file changed, 2 insertions(+), 16 deletions(-) diff --git a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx index 547d7dc2e588..278ed94d91c9 100644 --- a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx +++ b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx @@ -328,7 +328,7 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre .catch(() => { setIsRotating(false); }); - }, [transaction?.transactionID, isDraftTransaction, isOdometerImage, imageType, sourceUri, isImage, receiptFilename, fileName, fileType, policyCategories, transaction?.receipt, policy]); + }, [transaction, sourceUri, isImage, fileName, fileType, receiptFilename, isOdometerImage, isDraftTransaction, imageType, policyCategories, policy]); const shouldShowRotateAndCropReceiptButton = useMemo( () => @@ -414,21 +414,7 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre .catch(() => { setIsCropSaving(false); }); - }, [ - transaction?.transactionID, - isDraftTransaction, - isOdometerImage, - imageType, - sourceUri, - isImage, - cropRect, - receiptFilename, - fileName, - fileType, - policyCategories, - policy, - exitCropMode, - ]); + }, [transaction, sourceUri, isImage, cropRect, fileName, fileType, exitCropMode, receiptFilename, isOdometerImage, isDraftTransaction, imageType, policyCategories, policy]); const threeDotsMenuItems: ThreeDotsMenuItemFactory = useCallback( ({file, source: innerSource, isLocalSource}) => { From d0d7c6c020336386c6a1f668adf769ea32c0a06b Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 1 Apr 2026 00:30:07 +0530 Subject: [PATCH 5/7] cleanup --- .../request/step/IOURequestStepOdometerImage/index.native.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/iou/request/step/IOURequestStepOdometerImage/index.native.tsx b/src/pages/iou/request/step/IOURequestStepOdometerImage/index.native.tsx index 6abc6b999a52..6ef24aa6c869 100644 --- a/src/pages/iou/request/step/IOURequestStepOdometerImage/index.native.tsx +++ b/src/pages/iou/request/step/IOURequestStepOdometerImage/index.native.tsx @@ -364,7 +364,7 @@ function IOURequestStepOdometerImage({ From d0922259a1e5e9d4df89b2449be6945ab64928b5 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Wed, 1 Apr 2026 23:49:59 +0530 Subject: [PATCH 6/7] prettier --- src/libs/actions/IOU/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/libs/actions/IOU/index.ts b/src/libs/actions/IOU/index.ts index 236079fa3150..201eed6d02ba 100644 --- a/src/libs/actions/IOU/index.ts +++ b/src/libs/actions/IOU/index.ts @@ -1704,7 +1704,13 @@ function setMoneyRequestOdometerReading(transactionID: string, startReading: num * @param isDraft - Whether this is a draft transaction * @param shouldRevokeOldImage - Whether to revoke the previous blob URL immediately (always false on native where blob URLs don't exist; false on web when a backup transaction exists making the caller responsible for revoking) */ -function setMoneyRequestOdometerImage(transaction: OnyxEntry, imageType: OdometerImageType, file: FileObject | string, isDraft: boolean, shouldRevokeOldImage: boolean) { +function setMoneyRequestOdometerImage( + transaction: OnyxEntry, + imageType: OdometerImageType, + file: FileObject | string, + isDraft: boolean, + shouldRevokeOldImage: boolean, +) { const imageKey = imageType === CONST.IOU.ODOMETER_IMAGE_TYPE.START ? 'odometerStartImage' : 'odometerEndImage'; const normalizedFile: FileObject | string = typeof file === 'string' @@ -1734,7 +1740,7 @@ function setMoneyRequestOdometerImage(transaction: OnyxEntry, imageType: OdometerImageType, isDraft: boolean, shouldRevokeOldImage: boolean) { +function removeMoneyRequestOdometerImage(transaction: OnyxEntry, imageType: OdometerImageType, isDraft: boolean, shouldRevokeOldImage: boolean) { if (!transaction?.transactionID) { return; } From c324951f59a77387cfb698000873579af43c0d50 Mon Sep 17 00:00:00 2001 From: Rajat Parashar Date: Thu, 2 Apr 2026 00:15:57 +0530 Subject: [PATCH 7/7] Added unit test --- tests/actions/IOUTest.ts | 83 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 83 insertions(+) diff --git a/tests/actions/IOUTest.ts b/tests/actions/IOUTest.ts index 4b28d2b742f4..51b064407208 100644 --- a/tests/actions/IOUTest.ts +++ b/tests/actions/IOUTest.ts @@ -35,12 +35,14 @@ import { payMoneyRequest, rejectExpenseReport, rejectMoneyRequest, + removeMoneyRequestOdometerImage, replaceReceipt, requestMoney, resetDraftTransactionsCustomUnit, retractReport, setMoneyRequestCategory, setMoneyRequestDistanceRate, + setMoneyRequestOdometerImage, shouldOptimisticallyUpdateSearch, submitReport, trackExpense, @@ -17516,4 +17518,85 @@ describe('actions/IOU', () => { expect(updatedTransaction?.comment?.customUnit?.quantity).toBe(100); }); }); + + describe('setMoneyRequestOdometerImage and removeMoneyRequestOdometerImage', () => { + beforeEach(() => { + jest.mock('@libs/OdometerImageUtils', () => ({ + // eslint-disable-next-line @typescript-eslint/naming-convention + __esModule: true, + default: jest.fn(), + })); + }); + + afterEach(() => { + jest.unmock('@libs/OdometerImageUtils'); + }); + it('should set odometer start image on a draft transaction', async () => { + const transaction = createRandomTransaction(1); + const transactionID = transaction.transactionID; + const file = {uri: 'image.uri', name: 'image.jpg', type: 'image/jpeg', size: 1234}; + const imageType = CONST.IOU.ODOMETER_IMAGE_TYPE.START; + + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, transaction); + + setMoneyRequestOdometerImage(transaction, imageType, file, true, false); + await waitForBatchedUpdates(); + + const draftTransaction = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`); + expect(draftTransaction?.comment?.odometerStartImage).toEqual(file); + }); + + it('should set odometer end image on a non-draft transaction', async () => { + const transaction = createRandomTransaction(1); + const transactionID = transaction.transactionID; + const file = {uri: 'image.uri', name: 'image.jpg', type: 'image/jpeg', size: 1234}; + const imageType = CONST.IOU.ODOMETER_IMAGE_TYPE.END; + + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction); + + setMoneyRequestOdometerImage(transaction, imageType, file, false, false); + await waitForBatchedUpdates(); + + const updatedTransaction = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); + expect(updatedTransaction?.comment?.odometerEndImage).toEqual(file); + }); + + it('should remove odometer start image from a draft transaction', async () => { + const transaction = { + ...createRandomTransaction(1), + comment: { + odometerStartImage: {uri: 'image.uri'}, + }, + } as Transaction; + const transactionID = transaction.transactionID; + const imageType = CONST.IOU.ODOMETER_IMAGE_TYPE.START; + + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`, transaction); + + removeMoneyRequestOdometerImage(transaction, imageType, true, false); + await waitForBatchedUpdates(); + + const draftTransaction = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`); + expect(draftTransaction?.comment?.odometerStartImage).toBeUndefined(); + }); + + it('should remove odometer end image from a non-draft transaction', async () => { + const transaction = { + ...createRandomTransaction(1), + comment: { + odometerEndImage: {uri: 'image.uri'}, + }, + } as Transaction; + const transactionID = transaction.transactionID; + const imageType = CONST.IOU.ODOMETER_IMAGE_TYPE.END; + + await Onyx.set(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction); + + removeMoneyRequestOdometerImage(transaction, imageType, false, false); + await waitForBatchedUpdates(); + + const updatedTransaction = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); + expect(updatedTransaction?.comment?.odometerEndImage).toBeUndefined(); + }); + }); });