diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts index 67f46be40cc5..01d0aa7a5159 100644 --- a/src/libs/ReportPreviewActionUtils.ts +++ b/src/libs/ReportPreviewActionUtils.ts @@ -38,7 +38,7 @@ import { isSettled, } from './ReportUtils'; import {getSession} from './SessionUtils'; -import {allHavePendingRTERViolation, isScanning, shouldShowBrokenConnectionViolationForMultipleTransactions} from './TransactionUtils'; +import {allHavePendingRTERViolation, isPending, isScanning, shouldShowBrokenConnectionViolationForMultipleTransactions} from './TransactionUtils'; function canSubmit( report: Report, @@ -60,6 +60,10 @@ function canSubmit( const hasBeenReopened = hasReportBeenReopened(reportActions); const isManualSubmitEnabled = getCorrectedAutoReportingFrequency(policy) === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL; + if (!!transactions && transactions?.length > 0 && transactions.every((transaction) => isPending(transaction))) { + return false; + } + const hasAnyViolations = hasMissingSmartscanFields(report.reportID, transactions) || hasViolations(report.reportID, violations) || diff --git a/src/libs/ReportPrimaryActionUtils.ts b/src/libs/ReportPrimaryActionUtils.ts index ff5474aa80cc..597a9a595ecc 100644 --- a/src/libs/ReportPrimaryActionUtils.ts +++ b/src/libs/ReportPrimaryActionUtils.ts @@ -41,6 +41,7 @@ import { hasPendingRTERViolation as hasPendingRTERViolationTransactionUtils, isDuplicate, isOnHold as isOnHoldTransactionUtils, + isPending, isScanning, shouldShowBrokenConnectionViolationForMultipleTransactions, shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils, @@ -81,6 +82,10 @@ function isSubmitAction(report: Report, reportTransactions: Transaction[], polic const isManualSubmitEnabled = getCorrectedAutoReportingFrequency(policy) === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL; const transactionAreComplete = reportTransactions.every((transaction) => transaction.amount !== 0 || transaction.modifiedAmount !== 0); + if (reportTransactions.length > 0 && reportTransactions.every((transaction) => isPending(transaction))) { + return false; + } + const isAnyReceiptBeingScanned = reportTransactions?.some((transaction) => isScanning(transaction)); const hasReportBeenReopened = hasReportBeenReopenedUtils(reportActions); diff --git a/src/libs/ReportSecondaryActionUtils.ts b/src/libs/ReportSecondaryActionUtils.ts index 6c2323d95c41..fb4cc78a3f25 100644 --- a/src/libs/ReportSecondaryActionUtils.ts +++ b/src/libs/ReportSecondaryActionUtils.ts @@ -119,6 +119,10 @@ function isSubmitAction( return false; } + if (reportTransactions.length > 0 && reportTransactions.every((transaction) => isPending(transaction))) { + return false; + } + const isExpenseReport = isExpenseReportUtils(report); if (!isExpenseReport || report?.total === 0) { diff --git a/tests/actions/ReportPreviewActionUtilsTest.ts b/tests/actions/ReportPreviewActionUtilsTest.ts index 4d56363f8913..09bf80d8d6b2 100644 --- a/tests/actions/ReportPreviewActionUtilsTest.ts +++ b/tests/actions/ReportPreviewActionUtilsTest.ts @@ -97,6 +97,36 @@ describe('getReportPreviewAction', () => { expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.SUBMIT); }); + it('canSubmit should return false for expense preview report with only pending transactions', async () => { + const report: Report = { + ...createRandomReport(REPORT_ID), + type: CONST.REPORT.TYPE.EXPENSE, + ownerAccountID: CURRENT_USER_ACCOUNT_ID, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + isWaitingOnBankAccount: false, + }; + + const policy = createRandomPolicy(0); + policy.autoReportingFrequency = CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE; + policy.type = CONST.POLICY.TYPE.CORPORATE; + if (policy.harvesting) { + policy.harvesting.enabled = false; + } + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + const transaction = { + reportID: `${REPORT_ID}`, + status: CONST.TRANSACTION.STATUS.PENDING, + amount: 10, + merchant: 'Merchant', + date: '2025-01-01', + } as unknown as Transaction; + + // Simulate how components use a hook to pass the isReportArchived parameter + const {result: isReportArchived} = renderHook(() => useReportIsArchived(report?.parentReportID)); + expect(getReportPreviewAction(VIOLATIONS, report, policy, [transaction], isReportArchived.current)).toBe(CONST.REPORT.REPORT_PREVIEW_ACTIONS.VIEW); + }); + describe('canApprove', () => { it('should return true for report being processed', async () => { const report = { diff --git a/tests/unit/ReportPrimaryActionUtilsTest.ts b/tests/unit/ReportPrimaryActionUtilsTest.ts index 398f9f75e11b..a80af247c262 100644 --- a/tests/unit/ReportPrimaryActionUtilsTest.ts +++ b/tests/unit/ReportPrimaryActionUtilsTest.ts @@ -76,6 +76,30 @@ describe('getPrimaryAction', () => { expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction], violations: {}, policy: policy as Policy})).toBe(CONST.REPORT.PRIMARY_ACTIONS.SUBMIT); }); + it('should not return SUBMIT option for admin with only pending transactions', async () => { + const report = { + reportID: REPORT_ID, + type: CONST.REPORT.TYPE.EXPENSE, + ownerAccountID: CURRENT_USER_ACCOUNT_ID, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + } as unknown as Report; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + + const policy = { + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.IMMEDIATE, + }; + const transaction = { + reportID: `${REPORT_ID}`, + status: CONST.TRANSACTION.STATUS.PENDING, + amount: 10, + merchant: 'Merchant', + date: '2025-01-01', + } as unknown as Transaction; + + expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction], violations: {}, policy: policy as Policy})).toBe(''); + }); + it('should return Approve for report being processed', async () => { const report = { reportID: REPORT_ID, diff --git a/tests/unit/ReportSecondaryActionUtilsTest.ts b/tests/unit/ReportSecondaryActionUtilsTest.ts index 8e3cd860fa08..602b78114d0d 100644 --- a/tests/unit/ReportSecondaryActionUtilsTest.ts +++ b/tests/unit/ReportSecondaryActionUtilsTest.ts @@ -118,6 +118,35 @@ describe('getSecondaryAction', () => { expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.SUBMIT)).toBe(true); }); + it('should not include SUBMIT option for admin with only pending transactions', async () => { + const report = { + reportID: REPORT_ID, + type: CONST.REPORT.TYPE.EXPENSE, + stateNum: CONST.REPORT.STATE_NUM.OPEN, + statusNum: CONST.REPORT.STATUS_NUM.OPEN, + total: 10, + } as unknown as Report; + const policy = { + autoReportingFrequency: CONST.POLICY.AUTO_REPORTING_FREQUENCIES.INSTANT, + harvesting: { + enabled: true, + }, + role: CONST.POLICY.ROLE.ADMIN, + } as unknown as Policy; + await Onyx.merge(`${ONYXKEYS.COLLECTION.REPORT}${REPORT_ID}`, report); + + const transaction = { + transactionID: 'TRANSACTION_ID', + status: CONST.TRANSACTION.STATUS.PENDING, + amount: 10, + merchant: 'Merchant', + date: '2025-01-01', + } as unknown as Transaction; + + const result = getSecondaryReportActions({report, chatReport, reportTransactions: [transaction], violations: {}, policy}); + expect(result.includes(CONST.REPORT.SECONDARY_ACTIONS.SUBMIT)).toBe(false); + }); + it('includes APPROVE option for approver and report with duplicates', async () => { const report = { reportID: REPORT_ID,