Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {FlatList, View} from 'react-native';
import type {LayoutChangeEvent, ListRenderItemInfo, ViewToken} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Animated, {useAnimatedStyle, useSharedValue, withDelay, withSpring, withTiming} from 'react-native-reanimated';
import type {LayoutRectangle} from 'react-native/Libraries/Types/CoreEventTypes';
import Button from '@components/Button';
Expand All @@ -21,7 +20,9 @@ import {showContextMenuForReport} from '@components/ShowContextMenuContext';
import Text from '@components/Text';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useOnyx from '@hooks/useOnyx';
import usePaymentAnimations from '@hooks/usePaymentAnimations';
import useReportIsArchived from '@hooks/useReportIsArchived';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
Expand Down Expand Up @@ -135,6 +136,7 @@ function MoneyRequestReportPreviewContent({
const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false);
const [requestType, setRequestType] = useState<ActionHandledType>();
const [paymentType, setPaymentType] = useState<PaymentMethodType>();
const isIouReportArchived = useReportIsArchived(iouReportID);

const getCanIOUBePaid = useCallback(
(shouldShowOnlyPayElsewhere = false, shouldCheckApprovedState = true) =>
Expand Down Expand Up @@ -191,10 +193,7 @@ function MoneyRequestReportPreviewContent({
// The submit button should be success green color only if the user is submitter and the policy does not have Scheduled Submit turned on
const isWaitingForSubmissionFromCurrentUser = useMemo(() => isWaitingForSubmissionFromCurrentUserReportUtils(chatReport, policy), [chatReport, policy]);
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);

const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${iouReportID}`, {canBeMissing: true});
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, {canBeMissing: true});

const confirmPayment = useCallback(
(type: PaymentMethodType | undefined, payAsBusiness?: boolean) => {
if (!type) {
Expand Down Expand Up @@ -456,8 +455,8 @@ function MoneyRequestReportPreviewContent({
if (isPaidAnimationRunning) {
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY;
}
return getReportPreviewAction(violations, iouReport, policy, transactions, reportNameValuePairs, reportActions);
}, [isPaidAnimationRunning, violations, iouReport, policy, transactions, reportNameValuePairs, reportActions]);
return getReportPreviewAction(violations, iouReport, policy, transactions, isIouReportArchived, reportActions);
}, [isPaidAnimationRunning, violations, iouReport, policy, transactions, isIouReportArchived, reportActions]);

const reportPreviewActions = {
[CONST.REPORT.REPORT_PREVIEW_ACTIONS.SUBMIT]: (
Expand Down
6 changes: 3 additions & 3 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ function ReportPreview({
const policy = usePolicy(policyID);
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, {canBeMissing: false});
const [iouReport, transactions, violations] = useReportWithTransactionsAndViolations(iouReportID);
const [reportNameValuePairs] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${iouReport?.reportID}`, {canBeMissing: true});
const isIouReportArchived = useReportIsArchived(iouReportID);
const lastTransaction = transactions?.at(0);
const transactionIDList = transactions?.map((reportTransaction) => reportTransaction.transactionID) ?? [];
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: false});
Expand Down Expand Up @@ -514,8 +514,8 @@ function ReportPreview({
if (isPaidAnimationRunning) {
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY;
}
return getReportPreviewAction(violations, iouReport, policy, transactions, reportNameValuePairs);
}, [isPaidAnimationRunning, violations, iouReport, policy, transactions, reportNameValuePairs]);
return getReportPreviewAction(violations, iouReport, policy, transactions, isIouReportArchived);
}, [isPaidAnimationRunning, violations, iouReport, policy, transactions, isIouReportArchived]);

const reportPreviewActions = {
[CONST.REPORT.REPORT_PREVIEW_ACTIONS.SUBMIT]: (
Expand Down
13 changes: 5 additions & 8 deletions src/libs/ReportPreviewActionUtils.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import type {Policy, Report, ReportAction, ReportActions, ReportNameValuePairs, Transaction, TransactionViolation} from '@src/types/onyx';
import type {Policy, Report, ReportAction, ReportActions, Transaction, TransactionViolation} from '@src/types/onyx';
import {isApprover as isApproverMember} from './actions/Policy/Member';
import {getCurrentUserAccountID} from './actions/Report';
import {
Expand All @@ -23,7 +23,6 @@ import {
hasNoticeTypeViolations,
hasViolations,
hasWarningTypeViolations,
isArchivedReport,
isClosedReport,
isCurrentUserSubmitter,
isExpenseReport,
Expand Down Expand Up @@ -86,10 +85,8 @@ function canApprove(report: Report, violations: OnyxCollection<TransactionViolat
return isExpense && isApprover && isProcessing && isApprovalEnabled && !hasAnyViolations && reportTransactions.length > 0 && isCurrentUserManager;
}

function canPay(report: Report, violations: OnyxCollection<TransactionViolation[]>, policy?: Policy, reportNameValuePairs?: ReportNameValuePairs) {
const isChatReportArchived = isArchivedReport(reportNameValuePairs);

if (isChatReportArchived) {
function canPay(report: Report, violations: OnyxCollection<TransactionViolation[]>, policy?: Policy, isReportArchived = false) {
if (isReportArchived) {
return false;
}

Expand Down Expand Up @@ -201,7 +198,7 @@ function getReportPreviewAction(
report?: Report,
policy?: Policy,
transactions?: Transaction[],
reportNameValuePairs?: ReportNameValuePairs,
isReportArchived = false,
reportActions?: OnyxEntry<ReportActions> | ReportAction[],
): ValueOf<typeof CONST.REPORT.REPORT_PREVIEW_ACTIONS> {
if (!report) {
Expand All @@ -213,7 +210,7 @@ function getReportPreviewAction(
if (canApprove(report, violations, policy, transactions)) {
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.APPROVE;
}
if (canPay(report, violations, policy, reportNameValuePairs)) {
if (canPay(report, violations, policy, isReportArchived)) {
return CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY;
}
if (canExport(report, violations, policy, reportActions)) {
Expand Down
49 changes: 43 additions & 6 deletions tests/actions/ReportPreviewActionUtilsTest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import {renderHook} from '@testing-library/react-native';
import type {OnyxCollection} from 'react-native-onyx';
import Onyx from 'react-native-onyx';
import useReportIsArchived from '@hooks/useReportIsArchived';
// eslint-disable-next-line no-restricted-syntax
import type * as PolicyUtils from '@libs/PolicyUtils';
import getReportPreviewAction from '@libs/ReportPreviewActionUtils';
Expand Down Expand Up @@ -29,6 +31,9 @@ const REPORT_ID = 1;
const TRANSACTION_ID = 1;
const VIOLATIONS: OnyxCollection<TransactionViolation[]> = {};

// This keeps the error "@rnmapbox/maps native code not available." from causing the tests to fail
jest.mock('@components/ConfirmedRoute.tsx');

jest.mock('@libs/ReportUtils', () => ({
...jest.requireActual<typeof ReportUtils>('@libs/ReportUtils'),
hasViolations: jest.fn().mockReturnValue(false),
Expand Down Expand Up @@ -70,7 +75,9 @@ describe('getReportPreviewAction', () => {
}
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);

expect(getReportPreviewAction(VIOLATIONS, report, policy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.SUBMIT);
// Simulate how components use a hook to pass the isReportArchived parameter
const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.parentReportID));
expect(getReportPreviewAction(VIOLATIONS, report, policy, undefined, isReportArchived.current)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.SUBMIT);
});

it('canApprove should return true for report being processed', async () => {
Expand All @@ -91,7 +98,8 @@ describe('getReportPreviewAction', () => {

await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);

expect(getReportPreviewAction(VIOLATIONS, report, policy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.APPROVE);
const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.parentReportID));
expect(getReportPreviewAction(VIOLATIONS, report, policy, undefined, isReportArchived.current)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.APPROVE);
});

it('canPay should return true for expense report with payments enabled', async () => {
Expand All @@ -110,7 +118,8 @@ describe('getReportPreviewAction', () => {

await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);

expect(getReportPreviewAction(VIOLATIONS, report, policy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY);
const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.parentReportID));
expect(getReportPreviewAction(VIOLATIONS, report, policy, undefined, isReportArchived.current)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY);
});

it('canPay should return true for submitted invoice', async () => {
Expand All @@ -129,7 +138,33 @@ describe('getReportPreviewAction', () => {

await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);

expect(getReportPreviewAction(VIOLATIONS, report, policy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY);
const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.parentReportID));
expect(getReportPreviewAction(VIOLATIONS, report, policy, undefined, isReportArchived.current)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY);
});

it('canPay should return false for archived invoice', async () => {
const report = {
...createRandomReport(REPORT_ID),
type: CONST.REPORT.TYPE.INVOICE,
ownerAccountID: CURRENT_USER_ACCOUNT_ID,
statusNum: CONST.REPORT.STATUS_NUM.SUBMITTED,
stateNum: CONST.REPORT.STATE_NUM.SUBMITTED,
};

const policy = createRandomPolicy(0);
policy.role = CONST.POLICY.ROLE.ADMIN;
policy.type = CONST.POLICY.TYPE.CORPORATE;
policy.reimbursementChoice = CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO;

await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);

// This is what indicates that a report is archived (see ReportUtils.isArchivedReport())
await Onyx.set(`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report.reportID}`, {
private_isArchived: new Date().toString(),
});

const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.parentReportID));
expect(getReportPreviewAction(VIOLATIONS, report, policy, undefined, isReportArchived.current)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY);
});

it('canExport should return true for finished reports', async () => {
Expand All @@ -146,7 +181,8 @@ describe('getReportPreviewAction', () => {
policy.reimbursementChoice = CONST.POLICY.REIMBURSEMENT_CHOICES.REIMBURSEMENT_NO;
await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report);

expect(getReportPreviewAction(VIOLATIONS, report, policy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.EXPORT_TO_ACCOUNTING);
const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.parentReportID));
expect(getReportPreviewAction(VIOLATIONS, report, policy, undefined, isReportArchived.current)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.EXPORT_TO_ACCOUNTING);
});

it('canReview should return true for reports where there are violations, user is submitter or approver and Workflows are enabled', async () => {
Expand All @@ -172,7 +208,8 @@ describe('getReportPreviewAction', () => {
} as TransactionViolation,
]);

expect(getReportPreviewAction(VIOLATIONS, report, policy)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW);
const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.parentReportID));
expect(getReportPreviewAction(VIOLATIONS, report, policy, undefined, isReportArchived.current)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.REVIEW);
});

it('canReview should return true for reports with RTER violations regardless of workspace workflow configuration', async () => {
Expand Down
1 change: 1 addition & 0 deletions tests/actions/TaskTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {ReportActionsCollectionDataSet} from '@src/types/onyx/ReportAction'
import * as LHNTestUtils from '../utils/LHNTestUtils';
import waitForBatchedUpdates from '../utils/waitForBatchedUpdates';

// This keeps the error "@rnmapbox/maps native code not available." from causing the tests to fail
jest.mock('@components/ConfirmedRoute.tsx');

OnyxUpdateManager();
Expand Down