diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx
index e1a998f78cab6..4eb3be871a8d1 100644
--- a/src/components/MoneyReportHeader.tsx
+++ b/src/components/MoneyReportHeader.tsx
@@ -3,12 +3,15 @@ import {View} from 'react-native';
import type {OnyxEntry} from 'react-native-onyx';
import {withOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
+import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useWindowDimensions from '@hooks/useWindowDimensions';
import * as CurrencyUtils from '@libs/CurrencyUtils';
import * as HeaderUtils from '@libs/HeaderUtils';
import * as ReportActionsUtils from '@libs/ReportActionsUtils';
import * as ReportUtils from '@libs/ReportUtils';
+import * as TransactionUtils from '@libs/TransactionUtils';
+import variables from '@styles/variables';
import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
@@ -19,8 +22,10 @@ import {isEmptyObject} from '@src/types/utils/EmptyObject';
import Button from './Button';
import ConfirmModal from './ConfirmModal';
import HeaderWithBackButton from './HeaderWithBackButton';
+import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
import MoneyReportHeaderStatusBar from './MoneyReportHeaderStatusBar';
+import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
import ProcessMoneyReportHoldMenu from './ProcessMoneyReportHoldMenu';
import SettlementButton from './SettlementButton';
@@ -71,6 +76,7 @@ function MoneyReportHeader({
onBackButtonPress,
}: MoneyReportHeaderProps) {
const styles = useThemeStyles();
+ const theme = useTheme();
const [isDeleteRequestModalVisible, setIsDeleteRequestModalVisible] = useState(false);
const {translate} = useLocalize();
const {windowWidth} = useWindowDimensions();
@@ -98,6 +104,9 @@ function MoneyReportHeader({
const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport);
const [isConfirmModalVisible, setIsConfirmModalVisible] = useState(false);
+ const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((transaction) => transaction.transactionID);
+ const allHavePendingRTERViolation = TransactionUtils.allHavePendingRTERViolation(transactionIDs);
+
const cancelPayment = useCallback(() => {
if (!chatReport) {
return;
@@ -112,12 +121,12 @@ function MoneyReportHeader({
const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport);
- const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton);
+ const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(moneyRequestReport) && (shouldShowPayButton || shouldShowApproveButton) && !allHavePendingRTERViolation;
- const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0;
+ const shouldShowSubmitButton = isDraft && reimbursableSpend !== 0 && !allHavePendingRTERViolation;
const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport);
const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE;
- const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length;
+ const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !allHavePendingRTERViolation;
const shouldShowAnyButton = shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep;
const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport);
const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency);
@@ -203,7 +212,7 @@ function MoneyReportHeader({
shouldShowBackButton={shouldUseNarrowLayout}
onBackButtonPress={onBackButtonPress}
// Shows border if no buttons or next steps are showing below the header
- shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout)}
+ shouldShowBorderBottom={!(shouldShowAnyButton && shouldUseNarrowLayout) && !(shouldShowNextStep && !shouldUseNarrowLayout) && !allHavePendingRTERViolation}
shouldShowThreeDotsButton
threeDotsMenuItems={threeDotsMenuItems}
threeDotsAnchorPosition={styles.threeDotsPopoverOffsetNoCloseButton(windowWidth)}
@@ -241,6 +250,20 @@ function MoneyReportHeader({
)}
+ {allHavePendingRTERViolation && (
+
+ }
+ description={translate('iou.pendingMatchWithCreditCardDescription')}
+ shouldShowBorderBottom
+ />
+ )}
{shouldShowSettlementButton && shouldUseNarrowLayout && (
diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index 6680ac88dfe90..98d023f28e75c 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -1,7 +1,8 @@
+import type {ReactNode} from 'react';
import React, {useCallback, useEffect, useState} from 'react';
import {View} from 'react-native';
import {withOnyx} from 'react-native-onyx';
-import type {OnyxEntry} from 'react-native-onyx';
+import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
@@ -16,12 +17,14 @@ import * as IOU from '@userActions/IOU';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
-import type {Policy, Report, ReportAction, ReportActions, Session, Transaction} from '@src/types/onyx';
+import type {Policy, Report, ReportAction, ReportActions, Session, Transaction, TransactionViolations} from '@src/types/onyx';
import type {OriginalMessageIOU} from '@src/types/onyx/OriginalMessage';
+import type IconAsset from '@src/types/utils/IconAsset';
import ConfirmModal from './ConfirmModal';
import HeaderWithBackButton from './HeaderWithBackButton';
import Icon from './Icon';
import * as Expensicons from './Icon/Expensicons';
+import type {MoneyRequestHeaderStatusBarProps} from './MoneyRequestHeaderStatusBar';
import MoneyRequestHeaderStatusBar from './MoneyRequestHeaderStatusBar';
import ProcessMoneyRequestHoldMenu from './ProcessMoneyRequestHoldMenu';
@@ -35,6 +38,9 @@ type MoneyRequestHeaderOnyxProps = {
/** All the data for the transaction */
transaction: OnyxEntry;
+ /** The violations of the transaction */
+ transactionViolations: OnyxCollection;
+
/** All report actions */
// eslint-disable-next-line react/no-unused-prop-types
parentReportActions: OnyxEntry;
@@ -65,6 +71,7 @@ function MoneyRequestHeader({
parentReport,
report,
parentReportAction,
+ transactionViolations,
transaction,
shownHoldUseExplanation = false,
policy,
@@ -101,7 +108,6 @@ function MoneyRequestHeader({
}, [parentReport?.reportID, parentReportAction, setIsDeleteModalVisible]);
const isScanning = TransactionUtils.hasReceipt(transaction) && TransactionUtils.isReceiptBeingScanned(transaction);
- const isPending = TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction);
const isDeletedParentAction = ReportActionsUtils.isDeletedAction(parentReportAction);
const canHoldOrUnholdRequest = !isSettled && !isApproved && !isDeletedParentAction;
@@ -120,6 +126,33 @@ function MoneyRequestHeader({
}
};
+ const getStatusIcon: (src: IconAsset) => ReactNode = (src) => (
+
+ );
+
+ const getStatusBarProps: () => MoneyRequestHeaderStatusBarProps | undefined = () => {
+ if (isOnHold) {
+ return {title: translate('iou.hold'), description: translate('iou.expenseOnHold'), danger: true, shouldShowBorderBottom: true};
+ }
+
+ if (TransactionUtils.isExpensifyCardTransaction(transaction) && TransactionUtils.isPending(transaction)) {
+ return {title: getStatusIcon(Expensicons.CreditCardHourglass), description: translate('iou.transactionPendingDescription'), shouldShowBorderBottom: true};
+ }
+ if (isScanning) {
+ return {title: getStatusIcon(Expensicons.ReceiptScan), description: translate('iou.receiptScanInProgressDescription'), shouldShowBorderBottom: true};
+ }
+ if (TransactionUtils.hasPendingRTERViolation(TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) {
+ return {title: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription'), shouldShowBorderBottom: true};
+ }
+ };
+
+ const statusBarProps = getStatusBarProps();
+
useEffect(() => {
if (canDeleteRequest) {
return;
@@ -184,7 +217,7 @@ function MoneyRequestHeader({
<>
- {isPending && (
-
- }
- description={translate('iou.transactionPendingDescription')}
- shouldShowBorderBottom={!isScanning}
- />
- )}
- {isScanning && (
+ {statusBarProps && (
- }
- description={translate('iou.receiptScanInProgressDescription')}
- shouldShowBorderBottom
- />
- )}
- {isOnHold && (
-
)}
@@ -259,7 +264,7 @@ function MoneyRequestHeader({
MoneyRequestHeader.displayName = 'MoneyRequestHeader';
-const MoneyRequestHeaderWithTransaction = withOnyx>({
+const MoneyRequestHeaderWithTransaction = withOnyx>({
transaction: {
key: ({report, parentReportActions}) => {
const parentReportAction = (report.parentReportActionID && parentReportActions ? parentReportActions[report.parentReportActionID] : {}) as ReportAction & OriginalMessageIOU;
@@ -270,9 +275,15 @@ const MoneyRequestHeaderWithTransaction = withOnyx, Omit>({
+export default withOnyx<
+ Omit,
+ Omit
+>({
session: {
key: ONYXKEYS.SESSION,
},
diff --git a/src/components/MoneyRequestHeaderStatusBar.tsx b/src/components/MoneyRequestHeaderStatusBar.tsx
index b7bba02236569..4ee3079d5f1fe 100644
--- a/src/components/MoneyRequestHeaderStatusBar.tsx
+++ b/src/components/MoneyRequestHeaderStatusBar.tsx
@@ -57,3 +57,5 @@ function MoneyRequestHeaderStatusBar({title, description, shouldShowBorderBottom
MoneyRequestHeaderStatusBar.displayName = 'MoneyRequestHeaderStatusBar';
export default MoneyRequestHeaderStatusBar;
+
+export type {MoneyRequestHeaderStatusBarProps};
diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
index 45893de809df8..4ff318bc3c475 100644
--- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
+++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx
@@ -36,7 +36,7 @@ import CONST from '@src/CONST';
import type {IOUMessage} from '@src/types/onyx/OriginalMessage';
import type {EmptyObject} from '@src/types/utils/EmptyObject';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
-import type {MoneyRequestPreviewProps} from './types';
+import type {MoneyRequestPreviewProps, PendingMessageProps} from './types';
function MoneyRequestPreviewContent({
iouReport,
@@ -84,7 +84,6 @@ function MoneyRequestPreviewContent({
const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH});
const hasReceipt = TransactionUtils.hasReceipt(transaction);
const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(transaction);
- const isPending = TransactionUtils.isPending(transaction);
const isOnHold = TransactionUtils.isOnHold(transaction);
const isSettlementOrApprovalPartial = Boolean(iouReport?.pendingFields?.partial);
const isPartialHold = isSettlementOrApprovalPartial && isOnHold;
@@ -184,6 +183,21 @@ function MoneyRequestPreviewContent({
return message;
};
+ const getPendingMessageProps: () => PendingMessageProps = () => {
+ if (isScanning) {
+ return {shouldShow: true, messageIcon: ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')};
+ }
+ if (TransactionUtils.isPending(transaction)) {
+ return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')};
+ }
+ if (TransactionUtils.hasPendingUI(transaction, TransactionUtils.getTransactionViolations(transaction?.transactionID ?? '', transactionViolations))) {
+ return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')};
+ }
+ return {shouldShow: false};
+ };
+
+ const pendingMessageProps = getPendingMessageProps();
+
const getDisplayAmountText = (): string => {
if (isScanning) {
return translate('iou.receiptScanning');
@@ -312,26 +326,15 @@ function MoneyRequestPreviewContent({
)}
- {isScanning && (
-
-
- {translate('iou.receiptScanInProgress')}
-
- )}
- {isPending && (
+ {pendingMessageProps.shouldShow && (
- {translate('iou.transactionPending')}
+ {pendingMessageProps.messageDescription}
)}
diff --git a/src/components/ReportActionItem/MoneyRequestPreview/types.ts b/src/components/ReportActionItem/MoneyRequestPreview/types.ts
index 0e3eb37ce6e3f..9dcea80fdc054 100644
--- a/src/components/ReportActionItem/MoneyRequestPreview/types.ts
+++ b/src/components/ReportActionItem/MoneyRequestPreview/types.ts
@@ -2,6 +2,7 @@ import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import type * as OnyxTypes from '@src/types/onyx';
+import type IconAsset from '@src/types/utils/IconAsset';
type MoneyRequestPreviewOnyxProps = {
/** All of the personal details for everyone */
@@ -71,4 +72,19 @@ type MoneyRequestPreviewProps = MoneyRequestPreviewOnyxProps & {
isWhisper?: boolean;
};
-export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps};
+type NoPendingProps = {shouldShow: false};
+
+type PendingProps = {
+ /** Whether to show the pending message or not */
+ shouldShow: true;
+
+ /** The icon to be displayed if a request is pending */
+ messageIcon: IconAsset;
+
+ /** The description to be displayed if a request is pending */
+ messageDescription: string;
+};
+
+type PendingMessageProps = PendingProps | NoPendingProps;
+
+export type {MoneyRequestPreviewProps, MoneyRequestPreviewOnyxProps, PendingMessageProps};
diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx
index 9c5821329e1c6..dae827cdb5c0d 100644
--- a/src/components/ReportActionItem/ReportPreview.tsx
+++ b/src/components/ReportActionItem/ReportPreview.tsx
@@ -32,6 +32,7 @@ import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, Report, ReportAction, Transaction, TransactionViolations, UserWallet} from '@src/types/onyx';
import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage';
+import type {PendingMessageProps} from './MoneyRequestPreview/types';
import ReportActionItemImages from './ReportActionItemImages';
type ReportPreviewOnyxProps = {
@@ -140,6 +141,8 @@ function ReportPreview({
const hasErrors = hasMissingSmartscanFields || (canUseViolations && ReportUtils.hasViolations(iouReportID, transactionViolations)) || ReportUtils.hasActionsWithErrors(iouReportID);
const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3);
const lastThreeReceipts = lastThreeTransactionsWithReceipts.map((transaction) => ReceiptUtils.getThumbnailAndImageURIs(transaction));
+ const showRTERViolationMessage =
+ numberOfRequests === 1 && TransactionUtils.hasPendingUI(allTransactions[0], TransactionUtils.getTransactionViolations(allTransactions[0].transactionID, transactionViolations));
let formattedMerchant = numberOfRequests === 1 ? TransactionUtils.getMerchant(allTransactions[0]) : null;
const formattedDescription = numberOfRequests === 1 ? TransactionUtils.getDescription(allTransactions[0]) : null;
@@ -148,7 +151,7 @@ function ReportPreview({
formattedMerchant = null;
}
- const shouldShowSubmitButton = isOpenExpenseReport && reimbursableSpend !== 0;
+ const shouldShowSubmitButton = isOpenExpenseReport && reimbursableSpend !== 0 && !showRTERViolationMessage;
const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.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
@@ -210,7 +213,7 @@ function ReportPreview({
const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(iouReport);
- const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(iouReport) && (shouldShowPayButton || shouldShowApproveButton);
+ const shouldShowSettlementButton = !ReportUtils.isInvoiceReport(iouReport) && (shouldShowPayButton || shouldShowApproveButton) && !showRTERViolationMessage;
const shouldPromptUserToAddBankAccount = ReportUtils.hasMissingPaymentMethod(userWallet, iouReportID);
const shouldShowRBR = !iouSettled && hasErrors;
@@ -229,6 +232,21 @@ function ReportPreview({
const shouldShowScanningSubtitle = numberOfScanningReceipts === 1 && numberOfRequests === 1;
const shouldShowPendingSubtitle = numberOfPendingRequests === 1 && numberOfRequests === 1;
+ const getPendingMessageProps: () => PendingMessageProps = () => {
+ if (shouldShowScanningSubtitle) {
+ return {shouldShow: true, messageIcon: Expensicons.ReceiptScan, messageDescription: translate('iou.receiptScanInProgress')};
+ }
+ if (shouldShowPendingSubtitle) {
+ return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')};
+ }
+ if (showRTERViolationMessage) {
+ return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')};
+ }
+ return {shouldShow: false};
+ };
+
+ const pendingMessageProps = getPendingMessageProps();
+
const {supportText} = useMemo(() => {
if (formattedMerchant) {
return {supportText: formattedMerchant};
@@ -314,26 +332,15 @@ function ReportPreview({
)}
- {shouldShowScanningSubtitle && (
-
-
- {translate('iou.receiptScanInProgress')}
-
- )}
- {shouldShowPendingSubtitle && (
-
+ {pendingMessageProps.shouldShow && (
+
- {translate('iou.transactionPending')}
+ {pendingMessageProps.messageDescription}
)}
diff --git a/src/languages/en.ts b/src/languages/en.ts
index d21c4883ee21a..93db79329a67e 100755
--- a/src/languages/en.ts
+++ b/src/languages/en.ts
@@ -645,6 +645,9 @@ export default {
canceled: 'Canceled',
posted: 'Posted',
deleteReceipt: 'Delete receipt',
+ pendingMatchWithCreditCard: 'Receipt pending match with credit card.',
+ pendingMatchWithCreditCardDescription: 'Receipt pending match with credit card. Mark as cash to ignore and request payment.',
+ routePending: 'Route pending...',
receiptScanning: 'Receipt scanning...',
receiptScanInProgress: 'Receipt scan in progress.',
receiptScanInProgressDescription: 'Receipt scan in progress. Check back later or enter the details now.',
diff --git a/src/languages/es.ts b/src/languages/es.ts
index 707526056e047..54abab03fa931 100644
--- a/src/languages/es.ts
+++ b/src/languages/es.ts
@@ -638,6 +638,9 @@ export default {
canceled: 'Canceló',
posted: 'Contabilizado',
deleteReceipt: 'Eliminar recibo',
+ pendingMatchWithCreditCard: 'Recibo pendiente de adjuntar con la tarjeta de crédito.',
+ pendingMatchWithCreditCardDescription: 'Recibo pendiente de adjuntar con tarjeta de crédito. Marca como efectivo para ignorar y solicitar pago.',
+ routePending: 'Ruta pendiente...',
receiptIssuesFound: (count: number) => `${count === 1 ? 'Problema encontrado' : 'Problemas encontrados'}`,
fieldPending: 'Pendiente...',
receiptScanning: 'Escaneando recibo...',
diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts
index ae30d648a6aa4..b1a8aea521b19 100644
--- a/src/libs/TransactionUtils.ts
+++ b/src/libs/TransactionUtils.ts
@@ -4,7 +4,7 @@ import Onyx from 'react-native-onyx';
import type {ValueOf} from 'type-fest';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
-import type {Policy, RecentWaypoint, Report, TaxRate, TaxRates, Transaction, TransactionViolation} from '@src/types/onyx';
+import type {Policy, RecentWaypoint, Report, TaxRate, TaxRates, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx';
import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction';
import {isEmptyObject} from '@src/types/utils/EmptyObject';
import type {IOURequestType} from './actions/IOU';
@@ -15,7 +15,6 @@ import * as NumberUtils from './NumberUtils';
import {getCleanedTagName} from './PolicyUtils';
let allTransactions: OnyxCollection = {};
-
Onyx.connect({
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
@@ -27,6 +26,13 @@ Onyx.connect({
},
});
+let allTransactionViolations: OnyxCollection = {};
+Onyx.connect({
+ key: ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS,
+ waitForCollectionCallback: true,
+ callback: (value) => (allTransactionViolations = value),
+});
+
let allReports: OnyxCollection;
Onyx.connect({
key: ONYXKEYS.COLLECTION.REPORT,
@@ -506,6 +512,40 @@ function hasMissingSmartscanFields(transaction: OnyxEntry): boolean
return Boolean(transaction && !isDistanceRequest(transaction) && !isReceiptBeingScanned(transaction) && areRequiredFieldsEmpty(transaction));
}
+/**
+ * Get all transaction violations of the transaction with given tranactionID.
+ */
+function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection | null): TransactionViolations | null {
+ return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null;
+}
+
+/**
+ * Check if there is pending rter violation in transactionViolations.
+ */
+function hasPendingRTERViolation(transactionViolations?: TransactionViolations | null): boolean {
+ return Boolean(
+ transactionViolations?.some((transactionViolation: TransactionViolation) => transactionViolation.name === CONST.VIOLATIONS.RTER && transactionViolation.data?.pendingPattern),
+ );
+}
+
+/**
+ * Check if there is pending rter violation in all transactionViolations with given transactionIDs.
+ */
+function allHavePendingRTERViolation(transactionIds: string[]): boolean {
+ const transactionsWithRTERViolations = transactionIds.map((transactionId) => {
+ const transactionViolations = getTransactionViolations(transactionId, allTransactionViolations);
+ return hasPendingRTERViolation(transactionViolations);
+ });
+ return transactionsWithRTERViolations.length > 0 && transactionsWithRTERViolations.every((value) => value === true);
+}
+
+/**
+ * Check if the transaction is pending or has a pending rter violation.
+ */
+function hasPendingUI(transaction: OnyxEntry, transactionViolations?: TransactionViolations | null): boolean {
+ return isReceiptBeingScanned(transaction) || isPending(transaction) || (!!transaction && hasPendingRTERViolation(transactionViolations));
+}
+
/**
* Check if the transaction has a defined route
*/
@@ -608,7 +648,7 @@ function isOnHoldByTransactionID(transactionID: string): boolean {
/**
* Checks if any violations for the provided transaction are of type 'violation'
*/
-function hasViolation(transactionID: string, transactionViolations: OnyxCollection): boolean {
+function hasViolation(transactionID: string, transactionViolations: OnyxCollection): boolean {
return Boolean(
transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION),
);
@@ -621,10 +661,6 @@ function hasNoticeTypeViolation(transactionID: string, transactionViolations: On
return Boolean(transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some((violation: TransactionViolation) => violation.type === 'notice'));
}
-function getTransactionViolations(transactionID: string, transactionViolations: OnyxCollection): TransactionViolation[] | null {
- return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null;
-}
-
/**
* this is the formulae to calculate tax
*/
@@ -763,6 +799,9 @@ export {
isCreatedMissing,
areRequiredFieldsEmpty,
hasMissingSmartscanFields,
+ hasPendingRTERViolation,
+ allHavePendingRTERViolation,
+ hasPendingUI,
getWaypointIndex,
waypointHasValidAddress,
getRecentTransactions,
diff --git a/src/types/onyx/TransactionViolation.ts b/src/types/onyx/TransactionViolation.ts
index 28de4582bd5e5..ab2037ff336ab 100644
--- a/src/types/onyx/TransactionViolation.ts
+++ b/src/types/onyx/TransactionViolation.ts
@@ -29,6 +29,7 @@ type TransactionViolation = {
tagListIndex?: number;
tagListName?: string;
errorIndexes?: number[];
+ pendingPattern?: boolean;
};
};