diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index d5e44ba9fc93f..41968155a8509 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -509,8 +509,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea if (!moneyRequestReport) { return []; } - return getSecondaryReportActions(moneyRequestReport, transactions, violations, policy, reportActions); - }, [moneyRequestReport, policy, transactions, violations, reportActions]); + return getSecondaryReportActions(moneyRequestReport, transactions, violations, policy, reportNameValuePairs, reportActions); + }, [moneyRequestReport, policy, transactions, violations, reportNameValuePairs, reportActions]); const secondaryActionsImplemenation: Record, DropdownOption>> = { [CONST.REPORT.SECONDARY_ACTIONS.VIEW_DETAILS]: { diff --git a/src/components/MoneyReportHeaderOld.tsx b/src/components/MoneyReportHeaderOld.tsx index 4e6dcd41beb29..989404bd01dbd 100644 --- a/src/components/MoneyReportHeaderOld.tsx +++ b/src/components/MoneyReportHeaderOld.tsx @@ -231,7 +231,7 @@ function MoneyReportHeaderOld({policy, report: moneyRequestReport, transactionTh const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; const filteredTransactions = transactions?.filter((t) => t) ?? []; - const shouldShowSubmitButton = canSubmitReport(moneyRequestReport, policy, filteredTransactions, violations); + const shouldShowSubmitButton = canSubmitReport(moneyRequestReport, policy, filteredTransactions, violations, isArchivedReport); const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && !!connectedIntegration && isAdmin && canBeExported(moneyRequestReport); diff --git a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx index bad88a4ea309f..291733d4d9f43 100644 --- a/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx @@ -230,7 +230,7 @@ function MoneyRequestReportPreviewContent({ }; const shouldShowApproveButton = useMemo(() => canApproveIOU(iouReport, policy, transactions), [iouReport, policy, transactions]) || isApprovedAnimationRunning; - const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions, violations); + const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions, violations, isIouReportArchived); const shouldShowSettlementButton = !shouldShowSubmitButton && (shouldShowPayButton || shouldShowApproveButton) && !shouldShowRTERViolationMessage && !shouldShowBrokenConnectionViolation; const previewMessage = useMemo(() => { diff --git a/src/components/ReportActionItem/ReportPreviewOld.tsx b/src/components/ReportActionItem/ReportPreviewOld.tsx index 16bb8801fb4ec..8d932dd7db1c4 100644 --- a/src/components/ReportActionItem/ReportPreviewOld.tsx +++ b/src/components/ReportActionItem/ReportPreviewOld.tsx @@ -257,10 +257,10 @@ function ReportPreviewOld({ formattedMerchant = null; } - const isArchived = useReportIsArchived(iouReport?.reportID); + const isIouReportArchived = useReportIsArchived(iouReport?.reportID); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; const filteredTransactions = transactions?.filter((transaction) => transaction) ?? []; - const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions, violations); + const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions, violations, isIouReportArchived); const shouldDisableSubmitButton = shouldShowSubmitButton && !isAllowedToSubmitDraftExpenseReport(iouReport); // The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on @@ -419,10 +419,10 @@ function ReportPreviewOld({ const getPendingMessageProps: () => PendingMessageProps = () => { if (isPayAtEndExpense) { - if (!isArchived) { + if (!isIouReportArchived) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.bookingPending')}; } - if (isArchived && archiveReason === CONST.REPORT.ARCHIVE_REASON.BOOKING_END_DATE_HAS_PASSED) { + if (isIouReportArchived && archiveReason === CONST.REPORT.ARCHIVE_REASON.BOOKING_END_DATE_HAS_PASSED) { return { shouldShow: true, messageIcon: Expensicons.Box, diff --git a/src/libs/ReportPrimaryActionUtils.ts b/src/libs/ReportPrimaryActionUtils.ts index f989b60064f9c..5f8966f71dcb2 100644 --- a/src/libs/ReportPrimaryActionUtils.ts +++ b/src/libs/ReportPrimaryActionUtils.ts @@ -52,7 +52,11 @@ function isAddExpenseAction(report: Report, reportTransactions: Transaction[]) { return isExpenseReport && canAddTransaction && isReportSubmitter && reportTransactions.length === 0; } -function isSubmitAction(report: Report, reportTransactions: Transaction[], policy?: Policy) { +function isSubmitAction(report: Report, reportTransactions: Transaction[], policy?: Policy, reportNameValuePairs?: ReportNameValuePairs) { + if (isArchivedReport(reportNameValuePairs)) { + return false; + } + const isExpenseReport = isExpenseReportUtils(report); const isReportSubmitter = isCurrentUserSubmitter(report.reportID); const isOpenReport = isOpenReportUtils(report); @@ -122,9 +126,7 @@ function isPayAction(report: Report, policy?: Policy, reportNameValuePairs?: Rep const isReportFinished = (isReportApproved && !report.isWaitingOnBankAccount) || isSubmittedWithoutApprovalsEnabled || isReportClosed; const {reimbursableSpend} = getMoneyRequestSpendBreakdown(report); - const isChatReportArchived = isArchivedReport(reportNameValuePairs); - - if (isChatReportArchived) { + if (isArchivedReport(reportNameValuePairs)) { return false; } @@ -283,7 +285,7 @@ function getReportPrimaryAction( return CONST.REPORT.PRIMARY_ACTIONS.REMOVE_HOLD; } - if (isSubmitAction(report, reportTransactions, policy)) { + if (isSubmitAction(report, reportTransactions, policy, reportNameValuePairs)) { return CONST.REPORT.PRIMARY_ACTIONS.SUBMIT; } diff --git a/src/libs/ReportSecondaryActionUtils.ts b/src/libs/ReportSecondaryActionUtils.ts index fea79ad921225..c5f7c205e2eff 100644 --- a/src/libs/ReportSecondaryActionUtils.ts +++ b/src/libs/ReportSecondaryActionUtils.ts @@ -1,7 +1,7 @@ import type {OnyxCollection} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; -import type {Policy, Report, ReportAction, Transaction, TransactionViolation} from '@src/types/onyx'; +import type {Policy, Report, ReportAction, ReportNameValuePairs, Transaction, TransactionViolation} from '@src/types/onyx'; import {isApprover as isApproverUtils} from './actions/Policy/Member'; import {getCurrentUserAccountID, getCurrentUserEmail} from './actions/Report'; import { @@ -19,6 +19,7 @@ import { import {getIOUActionForReportID, getReportActions, isPayAction} from './ReportActionsUtils'; import { canAddTransaction, + isArchivedReport, isClosedReport as isClosedReportUtils, isCurrentUserSubmitter, isExpenseReport as isExpenseReportUtils, @@ -45,7 +46,11 @@ function isAddExpenseAction(report: Report, reportTransactions: Transaction[]) { return canAddTransaction(report); } -function isSubmitAction(report: Report, reportTransactions: Transaction[], policy?: Policy): boolean { +function isSubmitAction(report: Report, reportTransactions: Transaction[], policy?: Policy, reportNameValuePairs?: ReportNameValuePairs): boolean { + if (isArchivedReport(reportNameValuePairs)) { + return false; + } + const transactionAreComplete = reportTransactions.every((transaction) => transaction.amount !== 0 || transaction.modifiedAmount !== 0); if (!transactionAreComplete) { @@ -408,6 +413,7 @@ function getSecondaryReportActions( reportTransactions: Transaction[], violations: OnyxCollection, policy?: Policy, + reportNameValuePairs?: ReportNameValuePairs, reportActions?: ReportAction[], ): Array> { const options: Array> = []; @@ -416,7 +422,7 @@ function getSecondaryReportActions( options.push(CONST.REPORT.SECONDARY_ACTIONS.ADD_EXPENSE); } - if (isSubmitAction(report, reportTransactions, policy)) { + if (isSubmitAction(report, reportTransactions, policy, reportNameValuePairs)) { options.push(CONST.REPORT.SECONDARY_ACTIONS.SUBMIT); } diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 1c48d8040500e..fd9cd7f418f77 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -50,6 +50,7 @@ import { hasOnlyHeldExpenses, hasViolations, isAllowedToApproveExpenseReport as isAllowedToApproveExpenseReportUtils, + isArchivedReport, isClosedReport, isInvoiceReport, isMoneyRequestReport, @@ -428,13 +429,15 @@ function getAction(data: OnyxTypes.SearchResults['data'], key: string): SearchTr return CONST.SEARCH.ACTION_TYPES.APPROVE; } + const reportNVP = data[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`] ?? undefined; + const isArchived = isArchivedReport(reportNVP); + // We check for isAllowedToApproveExpenseReport because if the policy has preventSelfApprovals enabled, we disable the Submit action and in that case we want to show the View action instead - if (canSubmitReport(report, policy, allReportTransactions, allViolations) && isAllowedToApproveExpenseReport) { + if (canSubmitReport(report, policy, allReportTransactions, allViolations, isArchived) && isAllowedToApproveExpenseReport) { return CONST.SEARCH.ACTION_TYPES.SUBMIT; } - const reportRNVP = data[`${ONYXKEYS.COLLECTION.REPORT_NAME_VALUE_PAIRS}${report?.reportID}`] ?? undefined; - if (reportRNVP?.exportFailedTime) { + if (reportNVP?.exportFailedTime) { return CONST.SEARCH.ACTION_TYPES.REVIEW; } diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 3cb32446a235f..592e66282bccb 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -125,7 +125,6 @@ import { getOutstandingChildRequest, getParsedComment, getPersonalDetailsForAccountID, - getReportNameValuePairs, getReportNotificationPreference, getReportOrDraftReport, getReportTransactions, @@ -8902,15 +8901,10 @@ function canSubmitReport( policy: OnyxEntry | SearchPolicy, transactions: OnyxTypes.Transaction[] | SearchTransaction[], allViolations: OnyxCollection | undefined, + isReportArchived = false, ) { const currentUserAccountID = getCurrentUserAccountID(); const isOpenExpenseReport = isOpenExpenseReportReportUtils(report); - - // This will get removed as part of https://github.com/Expensify/App/issues/59961 - // eslint-disable-next-line deprecation/deprecation - const reportNameValuePairs = getReportNameValuePairs(report?.reportID); - - const isArchived = isArchivedReport(reportNameValuePairs); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; const transactionIDList = transactions.map((transaction) => transaction.transactionID); const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList, allViolations); @@ -8922,7 +8916,7 @@ function canSubmitReport( return ( transactions.length > 0 && isOpenExpenseReport && - !isArchived && + !isReportArchived && !hasOnlyPendingCardOrScanFailTransactions && !hasAllPendingRTERViolations && hasTransactionWithoutRTERViolation && diff --git a/tests/unit/IOUUtilsTest.ts b/tests/unit/IOUUtilsTest.ts index c74fdd10b511c..8960a92340e96 100644 --- a/tests/unit/IOUUtilsTest.ts +++ b/tests/unit/IOUUtilsTest.ts @@ -1,5 +1,7 @@ +import {renderHook} from '@testing-library/react-native'; import Onyx from 'react-native-onyx'; import type {OnyxCollection} from 'react-native-onyx'; +import useReportIsArchived from '@hooks/useReportIsArchived'; import DateUtils from '@libs/DateUtils'; import {canSubmitReport} from '@userActions/IOU'; import CONST from '@src/CONST'; @@ -16,6 +18,9 @@ import createRandomTransaction from '../utils/collections/transaction'; import waitForBatchedUpdates from '../utils/waitForBatchedUpdates'; import currencyList from './currencyList.json'; +// This keeps the error "@rnmapbox/maps native code not available." from causing the tests to fail +jest.mock('@components/ConfirmedRoute.tsx'); + const testDate = DateUtils.getDBTime(); const currentUserAccountID = 5; @@ -324,6 +329,31 @@ describe('canSubmitReport', () => { expect(canSubmitReport(expenseReport, fakePolicy, [], undefined)).toBe(false); }); + + it('returns false if the report is archived', async () => { + const policy: Policy = { + ...createRandomPolicy(7), + ownerAccountID: currentUserAccountID, + areRulesEnabled: true, + preventSelfApproval: false, + }; + const report: Report = { + ...createRandomReport(7), + type: CONST.REPORT.TYPE.EXPENSE, + managerID: currentUserAccountID, + ownerAccountID: currentUserAccountID, + policyID: policy.id, + }; + + // 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(), + }); + + // Simulate how components call canModifyTask() by using the hook useReportIsArchived() to see if the report is archived + const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.reportID)); + expect(canSubmitReport(report, policy, [], undefined, isReportArchived.current)).toBe(false); + }); }); describe('Check valid amount for IOU/Expense request', () => {