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; }; };