diff --git a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx index 13ea5be38596..744c5183366f 100644 --- a/src/components/ReportActionItem/MoneyRequestReceiptView.tsx +++ b/src/components/ReportActionItem/MoneyRequestReceiptView.tsx @@ -175,6 +175,7 @@ function MoneyRequestReceiptView({ }, [parentReportAction]); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(linkedTransactionID)}`); + const [transactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); const [policy] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY}${moneyRequestReport?.policyID}`); const [cardList] = useOnyx(ONYXKEYS.CARD_LIST); const transactionViolations = useTransactionViolations(transaction?.transactionID); @@ -568,6 +569,7 @@ function MoneyRequestReceiptView({ transactionPolicyCategories: policyCategories, transactionPolicyTagList: policyTagList, transactionViolations: rawTransactionViolations, + transactionReport, }); }; diff --git a/src/libs/actions/IOU/Receipt.ts b/src/libs/actions/IOU/Receipt.ts index 148af029a7ea..eed344c0de18 100644 --- a/src/libs/actions/IOU/Receipt.ts +++ b/src/libs/actions/IOU/Receipt.ts @@ -27,7 +27,6 @@ import type {ValueOf} from 'type-fest'; import Onyx from 'react-native-onyx'; -import {getAllReports} from '.'; import {getReceiptError} from './MoneyRequestBuilder'; type ReplaceReceipt = { @@ -40,23 +39,22 @@ type ReplaceReceipt = { isSameReceipt?: boolean; transactionPolicyTagList?: OnyxEntry; transactionViolations?: OnyxEntry; + transactionReport: OnyxEntry; }; -type ReplaceReceiptRetryParams = Omit & {transactionID: string}; +type ReplaceReceiptRetryParams = Omit & {transactionID: string}; function detachReceipt( transaction: OnyxEntry, transactionPolicy: OnyxEntry, transactionPolicyTagList: OnyxEntry, transactionViolations: OnyxEntry, + transactionReport: OnyxEntry, transactionPolicyCategories?: OnyxEntry, ) { const transactionID = transaction?.transactionID; if (!transactionID) { return; } - const allReports = getAllReports(); - - const expenseReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`] ?? null; const newTransaction = transaction ? { ...transaction, @@ -115,7 +113,7 @@ function detachReceipt( policyTagList: transactionPolicyTagList ?? {}, policyCategories: transactionPolicyCategories ?? {}, hasDependentTags: hasDependentTags(transactionPolicy, transactionPolicyTagList ?? {}), - isInvoiceTransaction: isInvoiceReportReportUtils(expenseReport), + isInvoiceTransaction: isInvoiceReportReportUtils(transactionReport), ownerLogin: undefined, }); optimisticData.push(violationsOnyxData); @@ -126,7 +124,7 @@ function detachReceipt( }); } - const updatedReportAction = buildOptimisticDetachReceipt(expenseReport?.reportID, transactionID, transaction?.merchant); + const updatedReportAction = buildOptimisticDetachReceipt(transactionReport?.reportID, transactionID, transaction?.merchant); optimisticData.push({ onyxMethod: Onyx.METHOD.MERGE, @@ -147,20 +145,20 @@ function detachReceipt( onyxMethod: Onyx.METHOD.MERGE, key: `${ONYXKEYS.COLLECTION.REPORT}${updatedReportAction?.reportID}`, value: { - lastVisibleActionCreated: expenseReport?.lastVisibleActionCreated, - lastReadTime: expenseReport?.lastReadTime, + lastVisibleActionCreated: transactionReport?.lastVisibleActionCreated, + lastReadTime: transactionReport?.lastReadTime, }, }); successData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionReport?.reportID}`, value: { [updatedReportAction.reportActionID]: {pendingAction: null}, }, }); failureData.push({ onyxMethod: Onyx.METHOD.MERGE, - key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${expenseReport?.reportID}`, + key: `${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transactionReport?.reportID}`, value: { [updatedReportAction.reportActionID]: { ...(updatedReportAction as OnyxTypes.ReportAction), @@ -181,7 +179,18 @@ function detachReceipt( ); } -function replaceReceipt({transaction, file, source, state, transactionPolicy, transactionPolicyCategories, isSameReceipt, transactionPolicyTagList, transactionViolations}: ReplaceReceipt) { +function replaceReceipt({ + transaction, + file, + source, + state, + transactionPolicy, + transactionPolicyCategories, + isSameReceipt, + transactionPolicyTagList, + transactionViolations, + transactionReport, +}: ReplaceReceipt) { const transactionID = transaction?.transactionID; if (!file || !transactionID) { @@ -190,9 +199,7 @@ function replaceReceipt({transaction, file, source, state, transactionPolicy, tr const receiptTraceId = mintAndStampReceiptTraceId(file); logReceiptCaptured({file, captureSource: 'replace', receiptTraceId}); - const allReports = getAllReports(); - const expenseReport = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`] ?? null; const oldReceipt = transaction?.receipt ?? {}; const receiptOptimistic = { source, @@ -262,7 +269,7 @@ function replaceReceipt({transaction, file, source, state, transactionPolicy, tr policyTagList: transactionPolicyTagList ?? {}, policyCategories: transactionPolicyCategories ?? {}, hasDependentTags: hasDependentTags(transactionPolicy, transactionPolicyTagList ?? {}), - isInvoiceTransaction: isInvoiceReportReportUtils(expenseReport), + isInvoiceTransaction: isInvoiceReportReportUtils(transactionReport), ownerLogin: undefined, }); optimisticData.push(violationsOnyxData); diff --git a/src/pages/inbox/report/ReportActionCompose/useReceiptDrop.ts b/src/pages/inbox/report/ReportActionCompose/useReceiptDrop.ts index 1f385749dc74..5dea32383b87 100644 --- a/src/pages/inbox/report/ReportActionCompose/useReceiptDrop.ts +++ b/src/pages/inbox/report/ReportActionCompose/useReceiptDrop.ts @@ -47,6 +47,7 @@ function useReceiptDrop({reportID, report, shouldAddOrReplaceReceipt, transactio const [policyTagList] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policy?.id}`); const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${getNonEmptyStringOnyxID(transactionID)}`); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`); + const [transactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); const onFilesValidated = (files: FileObject[]) => { if (files.length === 0) { @@ -63,6 +64,7 @@ function useReceiptDrop({reportID, report, shouldAddOrReplaceReceipt, transactio transactionPolicyCategories: policyCategories, transactionPolicyTagList: policyTagList, transactionViolations, + transactionReport, }); return; } diff --git a/src/pages/iou/request/step/IOURequestStepScan/components/ScanEditReceipt.tsx b/src/pages/iou/request/step/IOURequestStepScan/components/ScanEditReceipt.tsx index 67928519b9d3..1a4217c21a4b 100644 --- a/src/pages/iou/request/step/IOURequestStepScan/components/ScanEditReceipt.tsx +++ b/src/pages/iou/request/step/IOURequestStepScan/components/ScanEditReceipt.tsx @@ -45,6 +45,7 @@ function ScanEditReceipt({report, transactionID, backTo, isEditing}: ScanEditRec const [policyTagList] = useOnyx(`${ONYXKEYS.COLLECTION.POLICY_TAGS}${policy?.id}`); const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${getNonEmptyStringOnyxID(transactionID)}`); + const [transactionReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`); const {setIsLoaderVisible} = useFullScreenLoaderActions(); @@ -73,6 +74,7 @@ function ScanEditReceipt({report, transactionID, backTo, isEditing}: ScanEditRec transactionPolicyCategories: policyCategories, transactionPolicyTagList: policyTagList, transactionViolations, + transactionReport, }); } else { setMoneyRequestReceipt(transactionID, source, file.name ?? '', true, file.type); diff --git a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx index 5b19fe363e09..d5f4474031d2 100644 --- a/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx +++ b/src/pages/media/AttachmentModalScreen/routes/TransactionReceiptModalContent.tsx @@ -266,9 +266,9 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre * Detach the receipt and close the modal. */ const deleteReceiptAndClose = useCallback(() => { - detachReceipt(transaction, policy, policyTagList, transactionViolations, policyCategories); + detachReceipt(transaction, policy, policyTagList, transactionViolations, transactionReport, policyCategories); navigation.goBack(); - }, [transaction, policy, policyTagList, transactionViolations, policyCategories, navigation]); + }, [transaction, policy, policyTagList, transactionViolations, transactionReport, policyCategories, navigation]); /** * Remove odometer image and close the modal. @@ -313,12 +313,13 @@ function TransactionReceiptModalContent({navigation, route}: AttachmentModalScre transactionPolicy: policy, transactionPolicyTagList: policyTagList, transactionViolations, + transactionReport, ...(isSameReceipt ? {state: transaction?.receipt?.state, isSameReceipt: true} : {}), }); } }); }, - [transaction, isDraftTransaction, isOdometerImage, isEditingConfirmation, imageType, fileType, policyCategories, policy, policyTagList, transactionViolations], + [transaction, isDraftTransaction, isOdometerImage, isEditingConfirmation, imageType, fileType, policyCategories, policy, policyTagList, transactionViolations, transactionReport], ); const rotateReceipt = useCallback(() => { diff --git a/tests/actions/IOUTest/ReceiptTest.ts b/tests/actions/IOUTest/ReceiptTest.ts index 4117535cbf35..8c888fb74b5e 100644 --- a/tests/actions/IOUTest/ReceiptTest.ts +++ b/tests/actions/IOUTest/ReceiptTest.ts @@ -165,7 +165,7 @@ describe('actions/IOU/Receipt', () => { const transaction = await setupTransactionWithSnapshot(transactionID, {receipt: {source: 'original.jpg'}}); // When replaceReceipt is called without a file - replaceReceipt({transaction, file: undefined, source, transactionPolicy: undefined, transactionPolicyTagList: undefined}); + replaceReceipt({transaction, file: undefined, source, transactionPolicy: undefined, transactionPolicyTagList: undefined, transactionReport: undefined}); await waitForBatchedUpdates(); // Then the receipt source remains unchanged @@ -178,7 +178,7 @@ describe('actions/IOU/Receipt', () => { const transaction = await setupTransactionWithSnapshot(transactionID, {receipt: {source: 'test1'}}); // When replaceReceipt is called with a new file - replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined}); + replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined, transactionReport: undefined}); await waitForBatchedUpdates(); // Then both the transaction and its snapshot entry reflect the new receipt @@ -196,7 +196,15 @@ describe('actions/IOU/Receipt', () => { const transaction = await setupTransactionWithSnapshot(transactionID, {receipt: {source: 'test1', state: CONST.IOU.RECEIPT_STATE.SCAN_READY}}); // When replaceReceipt is called with the same state explicitly passed - replaceReceipt({transaction, file: createFile(), source, state: CONST.IOU.RECEIPT_STATE.SCAN_READY, transactionPolicy: undefined, transactionPolicyTagList: undefined}); + replaceReceipt({ + transaction, + file: createFile(), + source, + state: CONST.IOU.RECEIPT_STATE.SCAN_READY, + transactionPolicy: undefined, + transactionPolicyTagList: undefined, + transactionReport: undefined, + }); await waitForBatchedUpdates(); // Then the new receipt retains the provided state instead of falling back to OPEN @@ -214,7 +222,7 @@ describe('actions/IOU/Receipt', () => { const transaction = await setupTransactionWithSnapshot(transactionID, {receipt: null}); // When replaceReceipt is called - replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined}); + replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined, transactionReport: undefined}); await waitForBatchedUpdates(); // Then the receipt is created with the new source on both the transaction and snapshot @@ -234,7 +242,7 @@ describe('actions/IOU/Receipt', () => { try { // When replaceReceipt is called - replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined}); + replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined, transactionReport: undefined}); await waitForBatchedUpdates(); // Then the optimisticData marks the receipt field as pending UPDATE @@ -258,7 +266,7 @@ describe('actions/IOU/Receipt', () => { try { // When replaceReceipt is called - replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined}); + replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined, transactionReport: undefined}); await waitForBatchedUpdates(); // Then API.write is invoked with the REPLACE_RECEIPT command and the correct transactionID @@ -296,7 +304,7 @@ describe('actions/IOU/Receipt', () => { await waitForBatchedUpdates(); // When replaceReceipt is called with the paid group policy - replaceReceipt({transaction, file: createFile(), source, transactionPolicy: policy, transactionPolicyTagList: undefined}); + replaceReceipt({transaction, file: createFile(), source, transactionPolicy: policy, transactionPolicyTagList: undefined, transactionReport: undefined}); await waitForBatchedUpdates(); // Then transaction violations are computed and stored @@ -312,7 +320,7 @@ describe('actions/IOU/Receipt', () => { try { // When replaceReceipt is called - replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined}); + replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined, transactionReport: undefined}); await waitForBatchedUpdates(); // Then the failureData restores the original receipt, clears pendingFields, and attaches errors @@ -337,7 +345,7 @@ describe('actions/IOU/Receipt', () => { try { // When replaceReceipt is called - replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined}); + replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined, transactionReport: undefined}); await waitForBatchedUpdates(); // Then the failureData sets receipt to null since there was nothing to restore @@ -361,7 +369,7 @@ describe('actions/IOU/Receipt', () => { try { // When replaceReceipt is called - replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined}); + replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined, transactionReport: undefined}); await waitForBatchedUpdates(); // Then the successData clears the pending field for the receipt @@ -383,7 +391,7 @@ describe('actions/IOU/Receipt', () => { try { // When replaceReceipt is called - replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined}); + replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined, transactionReport: undefined}); await waitForBatchedUpdates(); // Then no snapshot updates are included in either optimisticData or failureData @@ -404,7 +412,7 @@ describe('actions/IOU/Receipt', () => { try { // When replaceReceipt is called - replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined}); + replaceReceipt({transaction, file: createFile(), source, transactionPolicy: undefined, transactionPolicyTagList: undefined, transactionReport: undefined}); await waitForBatchedUpdates(); // Then the failureData restores the original receipt inside the snapshot entry @@ -431,6 +439,7 @@ describe('actions/IOU/Receipt', () => { state: CONST.IOU.RECEIPT_STATE.SCAN_READY, transactionPolicy: undefined, transactionPolicyTagList: undefined, + transactionReport: undefined, isSameReceipt: true, }); await waitForBatchedUpdates(); @@ -481,7 +490,15 @@ describe('actions/IOU/Receipt', () => { // When replaceReceipt is called with the paid group policy const writeSpy = mockApiWrite(); try { - replaceReceipt({transaction, file: createFile(), source, transactionPolicy: policy, transactionPolicyTagList: undefined, transactionViolations: existingViolations}); + replaceReceipt({ + transaction, + file: createFile(), + source, + transactionPolicy: policy, + transactionPolicyTagList: undefined, + transactionReport: undefined, + transactionViolations: existingViolations, + }); await waitForBatchedUpdates(); // Then the failureData restores the original violations @@ -535,7 +552,7 @@ describe('actions/IOU/Receipt', () => { it('should do nothing when transactionID is undefined', async () => { const transactionsBefore = await getOnyxValue(ONYXKEYS.COLLECTION.TRANSACTION); - detachReceipt(undefined, undefined, undefined, undefined); + detachReceipt(undefined, undefined, undefined, undefined, undefined); await waitForBatchedUpdates(); const transactionsAfter = await getOnyxValue(ONYXKEYS.COLLECTION.TRANSACTION); @@ -548,7 +565,7 @@ describe('actions/IOU/Receipt', () => { await seedOnyx(); try { - detachReceipt(transaction, undefined, undefined, undefined); + detachReceipt(transaction, undefined, undefined, undefined, undefined); await waitForBatchedUpdates(); const onyxData = writeSpy.mock.calls.at(0)?.at(2) as {optimisticData?: Array<{key: string; value: unknown}>}; @@ -567,7 +584,7 @@ describe('actions/IOU/Receipt', () => { it('should create an optimistic report action and update report timestamps', async () => { await seedOnyx(); - detachReceipt(transaction, undefined, undefined, undefined); + detachReceipt(transaction, undefined, undefined, undefined, report); await waitForBatchedUpdates(); // Then a new report action should be created on the report @@ -586,7 +603,7 @@ describe('actions/IOU/Receipt', () => { await seedOnyx(); try { - detachReceipt(transaction, undefined, undefined, undefined); + detachReceipt(transaction, undefined, undefined, undefined, undefined); await waitForBatchedUpdates(); expect(writeSpy).toHaveBeenCalledWith(WRITE_COMMANDS.DETACH_RECEIPT, expect.objectContaining({transactionID}), expect.anything(), expect.anything()); @@ -598,7 +615,7 @@ describe('actions/IOU/Receipt', () => { it('should compute violations when policy is paid group', async () => { await seedOnyx(); - detachReceipt(transaction, policy, policyTagList, undefined); + detachReceipt(transaction, policy, policyTagList, undefined, undefined); await waitForBatchedUpdates(); const violations = await getOnyxValue(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`);