diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index ed037f7a42ff..5aee85bf1033 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -28,7 +28,7 @@ import {getThreadReportIDsForTransactions, getTotalAmountForIOUReportPreviewButt import Navigation, {navigationRef} from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {ReportsSplitNavigatorParamList, SearchFullscreenNavigatorParamList, SearchReportParamList} from '@libs/Navigation/types'; -import {getReportNextStep} from '@libs/NextStepUtils'; +import {buildOptimisticNextStepForPreventSelfApprovalsEnabled} from '@libs/NextStepUtils'; import {isSecondaryActionAPaymentOption, selectPaymentType} from '@libs/PaymentUtils'; import type {KYCFlowEvent, TriggerKYCFlow} from '@libs/PaymentUtils'; import {getConnectedIntegration, getValidConnectedIntegration} from '@libs/PolicyUtils'; @@ -41,6 +41,7 @@ import { getArchiveReason, getIntegrationExportIcon, getIntegrationNameFromExportMessage as getIntegrationNameFromExportMessageUtils, + getNextApproverAccountID, getNonHeldAndFullAmount, getTransactionsWithReceipts, hasHeldExpenses as hasHeldExpensesReportUtils, @@ -50,6 +51,7 @@ import { isExported as isExportedUtils, isInvoiceReport as isInvoiceReportUtil, isProcessingReport, + isReportOwner, navigateOnDeleteExpense, navigateToDetailsPage, } from '@libs/ReportUtils'; @@ -361,7 +363,12 @@ function MoneyReportHeader({ const shouldShowStatusBar = hasAllPendingRTERViolations || shouldShowBrokenConnectionViolation || hasOnlyHeldExpenses || hasScanningReceipt || isPayAtEndExpense || hasOnlyPendingTransactions || hasDuplicates; - const optimisticNextStep = getReportNextStep(nextStep, moneyRequestReport, transactions, policy); + // When prevent self-approval is enabled & the current user is submitter AND they're submitting to themselves, we need to show the optimistic next step + // We should always show this optimistic message for policies with preventSelfApproval + // to avoid any flicker during transitions between online/offline states + const nextApproverAccountID = getNextApproverAccountID(moneyRequestReport); + const isSubmitterSameAsNextApprover = isReportOwner(moneyRequestReport) && nextApproverAccountID === moneyRequestReport?.ownerAccountID; + const optimisticNextStep = isSubmitterSameAsNextApprover && policy?.preventSelfApproval ? buildOptimisticNextStepForPreventSelfApprovalsEnabled() : nextStep; const shouldShowNextStep = isFromPaidPolicy && !isInvoiceReport && !shouldShowStatusBar; const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(moneyRequestReport, shouldShowPayButton); diff --git a/src/libs/NextStepUtils.ts b/src/libs/NextStepUtils.ts index ba8e9e428493..a05acb1cd0b2 100644 --- a/src/libs/NextStepUtils.ts +++ b/src/libs/NextStepUtils.ts @@ -5,7 +5,7 @@ import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, Policy, Report, ReportNextStep, Transaction, TransactionViolations} from '@src/types/onyx'; +import type {Beta, Policy, Report, ReportNextStep, TransactionViolations} from '@src/types/onyx'; import type {Message} from '@src/types/onyx/ReportNextStep'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; import EmailUtils from './EmailUtils'; @@ -20,12 +20,8 @@ import { hasViolations as hasViolationsReportUtils, isExpenseReport, isInvoiceReport, - isOpenExpenseReport, isPayer, - isProcessingReport, - isReportOwner, } from './ReportUtils'; -import {isPendingCardOrIncompleteTransaction, isPendingCardOrScanningTransaction} from './TransactionUtils'; let currentUserAccountID = -1; let currentUserEmail = ''; @@ -128,53 +124,6 @@ function buildOptimisticNextStepForPreventSelfApprovalsEnabled() { return optimisticNextStep; } -function buildOptimisticFixIssueNextStep() { - const optimisticNextStep: ReportNextStep = { - type: 'neutral', - icon: CONST.NEXT_STEP.ICONS.HOURGLASS, - message: [ - { - text: 'Waiting for ', - }, - { - text: `you`, - type: 'strong', - }, - { - text: ' to ', - }, - { - text: 'fix the issue(s)', - }, - ], - }; - - return optimisticNextStep; -} - -function getReportNextStep(currentNextStep: ReportNextStep | undefined, moneyRequestReport: OnyxEntry, transactions: Array>, policy: OnyxEntry) { - const nextApproverAccountID = getNextApproverAccountID(moneyRequestReport); - - if (isOpenExpenseReport(moneyRequestReport) && transactions.length > 0 && transactions.every((transaction) => isPendingCardOrIncompleteTransaction(transaction))) { - return buildOptimisticFixIssueNextStep(); - } - - if (isProcessingReport(moneyRequestReport) && transactions.length > 0 && transactions.every((transaction) => isPendingCardOrScanningTransaction(transaction))) { - return buildOptimisticFixIssueNextStep(); - } - - const isSubmitterSameAsNextApprover = isReportOwner(moneyRequestReport) && nextApproverAccountID === moneyRequestReport?.ownerAccountID; - - // When prevent self-approval is enabled & the current user is submitter AND they're submitting to themselves, we need to show the optimistic next step - // We should always show this optimistic message for policies with preventSelfApproval - // to avoid any flicker during transitions between online/offline states - if (isSubmitterSameAsNextApprover && policy?.preventSelfApproval) { - return buildOptimisticNextStepForPreventSelfApprovalsEnabled(); - } - - return currentNextStep; -} - /** * Generates an optimistic nextStep based on a current report status and other properties. * @@ -549,4 +498,4 @@ function buildNextStep( return optimisticNextStep; } -export {parseMessage, buildNextStep, buildOptimisticNextStepForPreventSelfApprovalsEnabled, getReportNextStep}; +export {parseMessage, buildNextStep, buildOptimisticNextStepForPreventSelfApprovalsEnabled}; diff --git a/src/libs/ReportPrimaryActionUtils.ts b/src/libs/ReportPrimaryActionUtils.ts index fb1b4b366f89..37b6c34cb5ee 100644 --- a/src/libs/ReportPrimaryActionUtils.ts +++ b/src/libs/ReportPrimaryActionUtils.ts @@ -43,8 +43,7 @@ import { hasPendingRTERViolation as hasPendingRTERViolationTransactionUtils, isDuplicate, isOnHold as isOnHoldTransactionUtils, - isPendingCardOrIncompleteTransaction, - isPendingCardOrScanningTransaction, + isPending, isScanning, shouldShowBrokenConnectionViolationForMultipleTransactions, shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils, @@ -84,7 +83,7 @@ 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) => isPendingCardOrIncompleteTransaction(transaction))) { + if (reportTransactions.length > 0 && reportTransactions.every((transaction) => isPending(transaction))) { return false; } @@ -129,7 +128,7 @@ function isApproveAction(report: Report, reportTransactions: Transaction[], poli return false; } - if (reportTransactions.length > 0 && reportTransactions.every((transaction) => isPendingCardOrScanningTransaction(transaction))) { + if (reportTransactions.length > 0 && reportTransactions.every((transaction) => isPending(transaction))) { return false; } diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index f09ac210d72d..184febe9ca4c 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -233,10 +233,6 @@ function isPendingCardOrScanningTransaction(transaction: OnyxEntry) return (isExpensifyCardTransaction(transaction) && isPending(transaction)) || isPartialTransaction(transaction) || (isScanRequest(transaction) && isScanning(transaction)); } -function isPendingCardOrIncompleteTransaction(transaction: OnyxEntry): boolean { - return (isExpensifyCardTransaction(transaction) && isPending(transaction)) || (isAmountMissing(transaction) && isMerchantMissing(transaction)); -} - /** * Optimistically generate a transaction. * @@ -1980,7 +1976,6 @@ export { isDemoTransaction, shouldShowViolation, isUnreportedAndHasInvalidDistanceRateTransaction, - isPendingCardOrIncompleteTransaction, getTransactionViolationsOfTransaction, isExpenseSplit, }; diff --git a/tests/unit/ReportPrimaryActionUtilsTest.ts b/tests/unit/ReportPrimaryActionUtilsTest.ts index a2f0c231db6e..bfbbdc618c84 100644 --- a/tests/unit/ReportPrimaryActionUtilsTest.ts +++ b/tests/unit/ReportPrimaryActionUtilsTest.ts @@ -80,7 +80,7 @@ describe('getPrimaryAction', () => { ); }); - it('should not return SUBMIT option for admin with only pending/incomplete transactions', async () => { + it('should not return SUBMIT option for admin with only pending transactions', async () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, @@ -99,22 +99,9 @@ describe('getPrimaryAction', () => { amount: 10, merchant: 'Merchant', date: '2025-01-01', - bank: CONST.EXPENSIFY_CARD.BANK, } as unknown as Transaction; - const transaction1 = { - reportID: `${REPORT_ID}`, - amount: 0, - modifiedAmount: 0, - receipt: { - source: 'test', - state: CONST.IOU.RECEIPT_STATE.SCAN_FAILED, - }, - merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, - modifiedMerchant: undefined, - } as unknown as Transaction; - - expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction, transaction1], violations: {}, policy: policy as Policy, isChatReportArchived: false})).toBe(''); + expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction], violations: {}, policy: policy as Policy, isChatReportArchived: false})).toBe(''); }); it('should return Approve for report being processed', async () => { @@ -136,8 +123,6 @@ describe('getPrimaryAction', () => { comment: { hold: 'Hold', }, - amount: 10, - merchant: 'merchant', } as unknown as Transaction; expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction], violations: {}, policy: policy as Policy, isChatReportArchived: false})).toBe( @@ -172,7 +157,7 @@ describe('getPrimaryAction', () => { expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction], violations: {}, policy: policy as Policy, isChatReportArchived: false})).toBe(''); }); - it('should return empty for report being processed but transactions are pending/partial', async () => { + it('should return empty for report being processed but transactions are pending', async () => { const report = { reportID: REPORT_ID, type: CONST.REPORT.TYPE.EXPENSE, @@ -192,22 +177,9 @@ describe('getPrimaryAction', () => { amount: 10, merchant: 'Merchant', date: '2025-01-01', - bank: CONST.EXPENSIFY_CARD.BANK, } as unknown as Transaction; - const transaction1 = { - reportID: `${REPORT_ID}`, - amount: 0, - modifiedAmount: 0, - receipt: { - source: 'test', - state: CONST.IOU.RECEIPT_STATE.SCAN_FAILED, - }, - merchant: CONST.TRANSACTION.PARTIAL_TRANSACTION_MERCHANT, - modifiedMerchant: undefined, - } as unknown as Transaction; - - expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction, transaction1], violations: {}, policy: policy as Policy, isChatReportArchived: false})).toBe(''); + expect(getReportPrimaryAction({report, chatReport, reportTransactions: [transaction], violations: {}, policy: policy as Policy, isChatReportArchived: false})).toBe(''); }); it('should return PAY for submitted invoice report if paid as personal', async () => {