diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index b5e658f878993..9e0a08860c0d4 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -371,6 +371,7 @@ function MoneyReportHeader({ const canTriggerAutomaticPDFDownload = useRef(false); const hasFinishedPDFDownload = reportPDFFilename && reportPDFFilename !== CONST.REPORT_DETAILS_MENU_ITEM.ERROR; + const [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS, {canBeMissing: true}); const [quickAction] = useOnyx(ONYXKEYS.NVP_QUICK_ACTION_GLOBAL_CREATE, {canBeMissing: true}); const [isSelfTourViewed = false] = useOnyx(ONYXKEYS.NVP_ONBOARDING, {canBeMissing: true, selector: hasSeenTourSelector}); @@ -711,6 +712,7 @@ function MoneyReportHeader({ targetReport: activePolicyExpenseChat, betas, personalDetails, + recentWaypoints, }); } }, @@ -727,6 +729,7 @@ function MoneyReportHeader({ isSelfTourViewed, betas, personalDetails, + recentWaypoints, ], ); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index fc20bc095429f..530cae40915b4 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -161,6 +161,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre 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 [recentWaypoints] = useOnyx(ONYXKEYS.NVP_RECENT_WAYPOINTS, {canBeMissing: true}); const {isDelegateAccessRestricted} = useDelegateNoAccessState(); const {showDelegateNoAccessModal} = useDelegateNoAccessActions(); @@ -217,6 +218,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre targetReport: activePolicyExpenseChat, betas, personalDetails, + recentWaypoints, }); } }, @@ -233,6 +235,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre isSelfTourViewed, betas, personalDetails, + recentWaypoints, ], ); diff --git a/src/libs/actions/IOU/Duplicate.ts b/src/libs/actions/IOU/Duplicate.ts index da50ad37aa49c..f936826c6bd67 100644 --- a/src/libs/actions/IOU/Duplicate.ts +++ b/src/libs/actions/IOU/Duplicate.ts @@ -35,7 +35,6 @@ import { getCurrentUserEmail, getMoneyRequestParticipantsFromReport, getPolicyTags, - getRecentWaypoints, getUserAccountID, requestMoney, submitPerDiemExpense, @@ -505,6 +504,7 @@ type DuplicateExpenseTransactionParams = { targetReport?: OnyxTypes.Report; betas: OnyxEntry; personalDetails: OnyxEntry; + recentWaypoints: OnyxEntry; }; function duplicateExpenseTransaction({ @@ -523,6 +523,7 @@ function duplicateExpenseTransaction({ targetReport, betas, personalDetails, + recentWaypoints, }: DuplicateExpenseTransactionParams) { if (!transaction) { return; @@ -530,7 +531,6 @@ function duplicateExpenseTransaction({ const userAccountID = getUserAccountID(); const currentUserEmail = getCurrentUserEmail(); - const recentWaypoints = getRecentWaypoints(); const participants = getMoneyRequestParticipantsFromReport(targetReport, userAccountID); const transactionDetails = getTransactionDetails(transaction); diff --git a/tests/actions/IOUTest/DuplicateTest.ts b/tests/actions/IOUTest/DuplicateTest.ts index 828a0b9a35d55..4d32df4bb0ade 100644 --- a/tests/actions/IOUTest/DuplicateTest.ts +++ b/tests/actions/IOUTest/DuplicateTest.ts @@ -16,7 +16,7 @@ import IntlStore from '@src/languages/IntlStore'; import OnyxUpdateManager from '@src/libs/actions/OnyxUpdateManager'; import * as API from '@src/libs/API'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {OriginalMessageIOU, Report, ReportActions} from '@src/types/onyx'; +import type {OriginalMessageIOU, RecentWaypoint, Report, ReportActions} from '@src/types/onyx'; import type ReportAction from '@src/types/onyx/ReportAction'; import type {ReportActionsCollectionDataSet} from '@src/types/onyx/ReportAction'; import type Transaction from '@src/types/onyx/Transaction'; @@ -890,6 +890,7 @@ describe('actions/Duplicate', () => { describe('duplicateExpenseTransaction', () => { let writeSpy: jest.SpyInstance; + let recentWaypoints: RecentWaypoint[] = []; const mockOptimisticChatReportID = '789'; const mockOptimisticIOUReportID = '987'; @@ -900,7 +901,7 @@ describe('actions/Duplicate', () => { const policyExpenseChat = createRandomReport(1, CONST.REPORT.CHAT_TYPE.POLICY_EXPENSE_CHAT); const fakePolicyCategories = createRandomPolicyCategories(3); - beforeEach(() => { + beforeEach(async () => { jest.clearAllMocks(); global.fetch = getGlobalFetchMock(); // eslint-disable-next-line rulesdir/no-multiple-api-calls @@ -917,6 +918,7 @@ describe('actions/Duplicate', () => { } return Promise.resolve(); }); + recentWaypoints = (await getOnyxValue(ONYXKEYS.NVP_RECENT_WAYPOINTS)) ?? []; return Onyx.clear(); }); @@ -952,6 +954,7 @@ describe('actions/Duplicate', () => { targetReport: policyExpenseChat, betas: [CONST.BETAS.ALL], personalDetails: {}, + recentWaypoints, }); await waitForBatchedUpdates(); @@ -1011,6 +1014,7 @@ describe('actions/Duplicate', () => { targetReport: policyExpenseChat, betas: [CONST.BETAS.ALL], personalDetails: {}, + recentWaypoints, }); await waitForBatchedUpdates(); @@ -1070,6 +1074,7 @@ describe('actions/Duplicate', () => { targetReport: policyExpenseChat, betas: [CONST.BETAS.ALL], personalDetails: {}, + recentWaypoints, }); await waitForBatchedUpdates(); @@ -1146,6 +1151,7 @@ describe('actions/Duplicate', () => { targetReport: policyExpenseChat, betas: [CONST.BETAS.ALL], personalDetails: {}, + recentWaypoints, }); await waitForBatchedUpdates(); @@ -1161,6 +1167,118 @@ describe('actions/Duplicate', () => { const apiParams = requestMoneyCall?.[1] as Record; expect(apiParams?.transactionThreadReportID).not.toBe(existingLinkedReportActionChildReportID); }); + + it('should call trackExpense API when targetPolicy is not provided', async () => { + const {waypoints, ...restOfComment} = mockTransaction.comment ?? {}; + const mockCashExpenseTransaction = { + ...mockTransaction, + amount: mockTransaction.amount * -1, + comment: { + ...restOfComment, + }, + }; + + await Onyx.clear(); + + // When duplicating the transaction without targetPolicy + duplicateExpenseTransaction({ + transaction: mockCashExpenseTransaction, + optimisticChatReportID: mockOptimisticChatReportID, + optimisticIOUReportID: mockOptimisticIOUReportID, + isASAPSubmitBetaEnabled: mockIsASAPSubmitBetaEnabled, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + policyRecentlyUsedCurrencies: [], + isSelfTourViewed: false, + customUnitPolicyID: '', + targetPolicy: undefined, + targetPolicyCategories: undefined, + targetReport: undefined, + betas: [CONST.BETAS.ALL], + personalDetails: {}, + recentWaypoints, + }); + + await waitForBatchedUpdates(); + + // Then the API should have been called with TRACK_EXPENSE instead of REQUEST_MONEY + const trackExpenseCall = writeSpy.mock.calls.find((call: [string, Record]) => call[0] === WRITE_COMMANDS.TRACK_EXPENSE); + const requestMoneyCall = writeSpy.mock.calls.find((call: [string, Record]) => call[0] === WRITE_COMMANDS.REQUEST_MONEY); + + expect(trackExpenseCall).toBeDefined(); + expect(requestMoneyCall).toBeUndefined(); + + // Then a transaction should be created successfully + let duplicatedTransaction: OnyxEntry; + + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (allTransactions) => { + duplicatedTransaction = Object.values(allTransactions ?? {}).find((t) => !!t); + }, + }); + + expect(duplicatedTransaction).toBeDefined(); + expect(duplicatedTransaction?.transactionID).not.toBe(mockCashExpenseTransaction.transactionID); + }); + + it('should preserve all transaction fields when duplicating Cash expense', async () => { + // Given a transaction with all fields populated using mockTransaction values + const {waypoints, ...restOfComment} = mockTransaction.comment ?? {}; + const mockCashExpense: Transaction = { + ...mockTransaction, + amount: mockTransaction.amount * -1, + comment: { + ...restOfComment, + }, + }; + + await Onyx.clear(); + + // When duplicating the transaction + duplicateExpenseTransaction({ + transaction: mockCashExpense, + optimisticChatReportID: mockOptimisticChatReportID, + optimisticIOUReportID: mockOptimisticIOUReportID, + isASAPSubmitBetaEnabled: mockIsASAPSubmitBetaEnabled, + introSelected: undefined, + activePolicyID: undefined, + quickAction: undefined, + policyRecentlyUsedCurrencies: [], + isSelfTourViewed: false, + customUnitPolicyID: '', + targetPolicy: mockPolicy, + targetPolicyCategories: fakePolicyCategories, + targetReport: policyExpenseChat, + betas: [CONST.BETAS.ALL], + personalDetails: {}, + recentWaypoints, + }); + + await waitForBatchedUpdates(); + + // The duplicated transaction should have all fields preserved + let duplicatedTransaction: OnyxEntry; + + await getOnyxData({ + key: ONYXKEYS.COLLECTION.TRANSACTION, + waitForCollectionCallback: true, + callback: (allTransactions) => { + duplicatedTransaction = Object.values(allTransactions ?? {}).find((t) => !!t); + }, + }); + + expect(duplicatedTransaction).toBeDefined(); + expect(duplicatedTransaction?.transactionID).not.toBe(mockCashExpense.transactionID); + expect(duplicatedTransaction?.category).toBe(mockTransaction.category); + expect(duplicatedTransaction?.tag).toBe(mockTransaction.tag); + expect(duplicatedTransaction?.billable).toBe(mockTransaction.billable); + expect(duplicatedTransaction?.reimbursable).toBe(mockTransaction.reimbursable); + expect(duplicatedTransaction?.currency).toBe(mockTransaction.currency); + expect(Math.abs(duplicatedTransaction?.amount ?? 0)).toBe(Math.abs(mockTransaction.amount)); + }); }); describe('resolveDuplicate', () => {