Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Route} from '@src/ROUTES';
import {personalDetailsLoginSelector} from '@src/selectors/PersonalDetails';
import {createMoveExpenseReportNVPSelector} from '@src/selectors/Report';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';

import type {ValueOf} from 'type-fest';
Expand Down Expand Up @@ -106,9 +107,11 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${getNonEmptyStringOnyxID(moneyRequestReport?.chatReportID)}`);
const [submitterLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(moneyRequestReport?.ownerAccountID)});
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${moneyRequestReport?.reportID}`);
const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${moneyRequestReport?.reportID}`);
const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID);
const [moveExpenseReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {
selector: createMoveExpenseReportNVPSelector(outstandingReportsByPolicyID, moneyRequestReport?.reportID),
});
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [introSelected] = useOnyx(ONYXKEYS.NVP_INTRO_SELECTED);
const [betas] = useOnyx(ONYXKEYS.BETAS);
Expand Down Expand Up @@ -380,7 +383,7 @@ function MoneyReportHeaderSecondaryActionsInner({reportID, primaryAction, isRepo
violations,
bankAccountList,
policy,
reportNameValuePairs,
moveExpenseReportNameValuePairs,
reportActions,
reportMetadata,
policies,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Route} from '@src/ROUTES';
import {personalDetailsLoginSelector} from '@src/selectors/PersonalDetails';
import {createMoveExpenseReportNVPSelector} from '@src/selectors/Report';

import type {StyleProp, ViewStyle} from 'react-native';
import type {ValueOf} from 'type-fest';
Expand Down Expand Up @@ -76,10 +77,12 @@ function MoneyReportHeaderSelectionDropdown({reportID, primaryAction, isReportIn
const [session] = useOnyx(ONYXKEYS.SESSION);
const [bankAccountList] = useOnyx(ONYXKEYS.BANK_ACCOUNT_LIST);
const [allPolicies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${getNonEmptyStringOnyxID(moneyRequestReport?.reportID)}`);
const [reportMetadata] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_METADATA}${getNonEmptyStringOnyxID(moneyRequestReport?.reportID)}`);
const [dismissedRejectUseExplanation] = useOnyx(ONYXKEYS.NVP_DISMISSED_REJECT_USE_EXPLANATION);
const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID);
const [moveExpenseReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {
selector: createMoveExpenseReportNVPSelector(outstandingReportsByPolicyID, moneyRequestReport?.reportID),
});
const [invoiceReceiverPolicy] = useOnyx(
`${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : undefined}`,
);
Expand Down Expand Up @@ -160,7 +163,7 @@ function MoneyReportHeaderSelectionDropdown({reportID, primaryAction, isReportIn
violations,
bankAccountList,
policy,
reportNameValuePairs,
moveExpenseReportNameValuePairs,
reportActions,
reportMetadata,
policies: allPolicies,
Expand Down
6 changes: 5 additions & 1 deletion src/hooks/useSelectionModeReportActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {getNonHeldAndFullAmount, hasOnlyHeldExpenses as hasOnlyHeldExpensesRepor
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {personalDetailsLoginSelector} from '@src/selectors/PersonalDetails';
import {createMoveExpenseReportNVPSelector} from '@src/selectors/Report';
import type * as OnyxTypes from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';

Expand Down Expand Up @@ -66,6 +67,9 @@ function useSelectionModeReportActions({
const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const [policies] = useOnyx(ONYXKEYS.COLLECTION.POLICY);
const [outstandingReportsByPolicyID] = useOnyx(ONYXKEYS.DERIVED.OUTSTANDING_REPORTS_BY_POLICY_ID);
const [moveExpenseReportNameValuePairs] = useOnyx(ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS, {
selector: createMoveExpenseReportNVPSelector(outstandingReportsByPolicyID, report?.reportID),
});
const [submitterLogin] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {selector: personalDetailsLoginSelector(report?.ownerAccountID)});
const [invoiceReceiverPolicy] = useOnyx(
`${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : undefined}`,
Expand Down Expand Up @@ -161,7 +165,7 @@ function useSelectionModeReportActions({
violations: allTransactionViolations,
bankAccountList,
policy,
reportNameValuePairs,
moveExpenseReportNameValuePairs,
reportActions,
reportMetadata,
policies,
Expand Down
6 changes: 4 additions & 2 deletions src/libs/ReportSecondaryActionUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -927,7 +927,7 @@ function getSecondaryReportActions({
violations,
bankAccountList,
policy,
reportNameValuePairs,
moveExpenseReportNameValuePairs,
reportActions,
reportMetadata,
policies,
Expand All @@ -947,7 +947,7 @@ function getSecondaryReportActions({
violations: OnyxCollection<TransactionViolation[]>;
bankAccountList: OnyxEntry<BankAccountList>;
policy?: Policy;
reportNameValuePairs?: ReportNameValuePairs;
moveExpenseReportNameValuePairs?: OnyxCollection<ReportNameValuePairs>;
reportActions?: ReportAction[];
reportMetadata?: OnyxEntry<ReportMetadata>;
policies?: OnyxCollection<Policy>;
Expand All @@ -960,6 +960,7 @@ function getSecondaryReportActions({
isOffline?: boolean;
}): Array<ValueOf<typeof CONST.REPORT.SECONDARY_ACTIONS>> {
const options: Array<ValueOf<typeof CONST.REPORT.SECONDARY_ACTIONS>> = [];
const reportNameValuePairs = moveExpenseReportNameValuePairs?.[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`];

const isExported = isExportedUtils(reportActions, report);
const hasExportError = hasExportErrorUtils(reportActions, report);
Expand Down Expand Up @@ -1104,6 +1105,7 @@ function getSecondaryReportActions({
fieldToEdit: CONST.EDIT_REQUEST_FIELD.REPORT,
isChatReportArchived,
outstandingReportsByPolicyID,
reportNameValuePairs: moveExpenseReportNameValuePairs,
transaction,
});
const canUserPerformWriteAction = canUserPerformWriteActionReportUtils(report, isChatReportArchived);
Expand Down
37 changes: 36 additions & 1 deletion src/selectors/Report.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {PersonalDetailsList, Report, ReportActions, ReportNameValuePairs, Transaction} from '@src/types/onyx';
import type {OutstandingReportsByPolicyIDDerivedValue, PersonalDetailsList, Report, ReportActions, ReportNameValuePairs, Transaction} from '@src/types/onyx';

import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {TupleToUnion, ValueOf} from 'type-fest';
Expand Down Expand Up @@ -80,6 +80,40 @@ const policyChatRoomsSelector =
return list;
};

/**
* Selects archived report NVPs for the current report and possible "Move expense" destination reports.
* This limits updates to data used to determine whether each destination report is archived.
*/
const createMoveExpenseReportNVPSelector = (outstandingReportsByPolicyID: OnyxEntry<OutstandingReportsByPolicyIDDerivedValue>, currentReportID: string | undefined) => {
Comment thread
ShridharGoel marked this conversation as resolved.
Comment thread
ShridharGoel marked this conversation as resolved.
const moveExpenseReportIDs = new Set<string>();
if (currentReportID) {
moveExpenseReportIDs.add(currentReportID);
}
for (const outstandingReports of Object.values(outstandingReportsByPolicyID ?? {})) {
for (const outstandingReport of Object.values(outstandingReports ?? {})) {
if (outstandingReport?.reportID) {
moveExpenseReportIDs.add(outstandingReport.reportID);
}
}
}

return (reportNameValuePairs: OnyxCollection<ReportNameValuePairs>): OnyxCollection<ReportNameValuePairs> => {
const moveExpenseReportNVPs: OnyxCollection<ReportNameValuePairs> = {};

for (const reportID of moveExpenseReportIDs) {
const key = `${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${reportID}` as const;
const reportNVP = reportNameValuePairs?.[key];
if (!isArchivedReport(reportNVP)) {
continue;
}

moveExpenseReportNVPs[key] = {private_isArchived: reportNVP?.private_isArchived};
}

return moveExpenseReportNVPs;
};
};

function openExpenseReportIDsSelector(reports: OnyxCollection<Report>): OpenExpenseReportIDMap {
if (!reports) {
return {};
Expand Down Expand Up @@ -217,6 +251,7 @@ export {
policyIDsWithEmptyReportsSelector,
canShowReportRecipientLocalTimeSelector,
policyChatRoomsSelector,
createMoveExpenseReportNVPSelector,
openExpenseReportIDsSelector,
getStableReportSelector,
isDraftReportSelector,
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/ReportSecondaryActionUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3673,6 +3673,9 @@ describe('getSecondaryAction', () => {
});
const reportActions = [actionR14932];
const policy = createMock<Policy>({});
const moveExpenseReportNameValuePairs = {
[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${REPORT_ID}`]: createMock<ReportNameValuePairs>({}),
};

jest.spyOn(ReportUtils, 'canEditFieldOfMoneyRequest').mockReturnValue(true);
jest.spyOn(ReportUtils, 'canUserPerformWriteAction').mockReturnValue(true);
Expand All @@ -3689,10 +3692,17 @@ describe('getSecondaryAction', () => {
bankAccountList: {},
policy,
reportActions,
moveExpenseReportNameValuePairs,

isProduction: false,
});
expect(result).toContain(CONST.REPORT.SECONDARY_ACTIONS.MOVE_EXPENSE);
expect(ReportUtils.canEditFieldOfMoneyRequest).toHaveBeenCalledWith(
expect.objectContaining({
isChatReportArchived: false,
reportNameValuePairs: moveExpenseReportNameValuePairs,
}),
);
});

it('does not include MOVE_EXPENSE option when canEditFieldOfMoneyRequest returns false', async () => {
Expand Down
32 changes: 31 additions & 1 deletion tests/unit/ReportSelectorTest.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import {policyChatRoomsSelector} from '@src/selectors/Report';
import {createMoveExpenseReportNVPSelector, policyChatRoomsSelector} from '@src/selectors/Report';
import type {Report} from '@src/types/onyx';

describe('policyChatRoomsSelector', () => {
Expand Down Expand Up @@ -97,3 +97,33 @@ describe('policyChatRoomsSelector', () => {
expect(result).toEqual([policyAdmins]);
});
});

describe('createMoveExpenseReportNVPSelector', () => {
const currentReport = {reportID: '1'} as Report;
const outstandingReport = {reportID: '2'} as Report;
const nonArchivedOutstandingReport = {reportID: '3'} as Report;
const archivedAt = '2024-01-01';
const outstandingReportsByPolicyID = {
policy1: {
[`${ONYXKEYS.COLLECTION.REPORT}${outstandingReport.reportID}`]: outstandingReport,
[`${ONYXKEYS.COLLECTION.REPORT}${nonArchivedOutstandingReport.reportID}`]: nonArchivedOutstandingReport,
},
};

it('selects archived NVPs for current and outstanding reports only', () => {
const currentReportNVPKey = `${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${currentReport.reportID}`;
const outstandingReportNVPKey = `${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${outstandingReport.reportID}`;
const unrelatedReportNVPKey = `${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}4`;
const reportNameValuePairs = {
[currentReportNVPKey]: {private_isArchived: archivedAt},
[outstandingReportNVPKey]: {private_isArchived: archivedAt},
[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${nonArchivedOutstandingReport.reportID}`]: {},
[unrelatedReportNVPKey]: {private_isArchived: archivedAt},
};

expect(createMoveExpenseReportNVPSelector(outstandingReportsByPolicyID, currentReport.reportID)(reportNameValuePairs)).toEqual({
[currentReportNVPKey]: {private_isArchived: archivedAt},
[outstandingReportNVPKey]: {private_isArchived: archivedAt},
});
});
});
Loading