Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Expense details - Violation is not displayed on description field when opening IOU details. #56405

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
5 changes: 4 additions & 1 deletion src/components/BrokenConnectionDescription.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import {useOnyx} from 'react-native-onyx';
import type {OnyxEntry} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
Expand All @@ -7,6 +8,7 @@ import {isCurrentUserSubmitter, isProcessingReport, isReportApproved, isReportMa
import {getTransactionViolations} from '@libs/TransactionUtils';
import Navigation from '@navigation/Navigation';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, Report} from '@src/types/onyx';
import TextLink from './TextLink';
Expand All @@ -24,7 +26,8 @@ type BrokenConnectionDescriptionProps = {
function BrokenConnectionDescription({transactionID, policy, report}: BrokenConnectionDescriptionProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const transactionViolations = getTransactionViolations(transactionID);
const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`);
const transactionViolations = getTransactionViolations(transactionID, allTransactionViolations);

const brokenConnection530Error = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530);
const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION);
Expand Down
3 changes: 2 additions & 1 deletion src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID
}`,
);
const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need to subscribe to the whole collection here:

diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx
index 6c4f9f92b36..fa5b4d3ffe4 100644
--- a/src/components/MoneyRequestHeader.tsx
+++ b/src/components/MoneyRequestHeader.tsx
@@ -10,19 +10,18 @@ import useTheme from '@hooks/useTheme';
 import useThemeStyles from '@hooks/useThemeStyles';
 import useTransactionViolations from '@hooks/useTransactionViolations';
 import Navigation from '@libs/Navigation/Navigation';
-import {isPolicyAdmin} from '@libs/PolicyUtils';
+import {isInstantSubmitEnabled, isPolicyAdmin} from '@libs/PolicyUtils';
 import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils';
-import {isCurrentUserSubmitter} from '@libs/ReportUtils';
+import {isCurrentUserSubmitter, isOpenExpenseReport, isProcessingReport} from '@libs/ReportUtils';
 import {
-    allHavePendingRTERViolation,
-    hasPendingRTERViolation,
+    hasPendingRTERViolation as hasPendingRTERViolationTransactionUtils,
     hasReceipt,
+    isBrokenConnectionViolation,
     isDuplicate as isDuplicateTransactionUtils,
     isExpensifyCardTransaction,
     isOnHold as isOnHoldTransactionUtils,
     isPending,
     isReceiptBeingScanned,
-    shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils,
 } from '@libs/TransactionUtils';
 import variables from '@styles/variables';
 import {markAsCash as markAsCashAction} from '@userActions/Transaction';
@@ -68,7 +67,6 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
         }`,
     );
     const transactionViolations = useTransactionViolations(transaction?.transactionID);
-    const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
 
     const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true});
     const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA);
@@ -82,12 +80,13 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
 
     const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP;
     const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth;
-    const transactionIDList = transaction ? [transaction.transactionID] : [];
-    const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList, allTransactionViolations);
+    const hasPendingRTERViolation = hasPendingRTERViolationTransactionUtils(transactionViolations);
 
-    const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, parentReport, policy, allTransactionViolations);
+    const hasBrokenConnectionViolation = !!transactionViolations.find((violation) => isBrokenConnectionViolation(violation));
+    const shouldShowBrokenConnectionViolation =
+        hasBrokenConnectionViolation && (!isPolicyAdmin(policy) || isOpenExpenseReport(report) || (isProcessingReport(report) && isInstantSubmitEnabled(policy)));
 
-    const shouldShowMarkAsCashButton = hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID)));
+    const shouldShowMarkAsCashButton = hasPendingRTERViolation || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID)));
 
     const markAsCash = useCallback(() => {
         markAsCashAction(transaction?.transactionID, reportID);
@@ -124,7 +123,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
                 ),
             };
         }
-        if (hasPendingRTERViolation(transactionViolations)) {
+        if (hasPendingRTERViolation) {
             return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')};
         }
         if (isScanning) {
diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts
index 73f456b152f..925411e6329 100644
--- a/src/libs/TransactionUtils/index.ts
+++ b/src/libs/TransactionUtils/index.ts
@@ -792,10 +792,13 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations |
  */
 function hasBrokenConnectionViolation(transactionID: string | undefined, transactionViolations: OnyxCollection<TransactionViolations> | undefined): boolean {
     const violations = getTransactionViolations(transactionID, transactionViolations);
-    return !!violations?.find(
-        (violation) =>
-            violation.name === CONST.VIOLATIONS.RTER &&
-            (violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION || violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530),
+    return !!violations?.find((violation) => isBrokenConnectionViolation(violation));
+}
+
+function isBrokenConnectionViolation(violation: TransactionViolation) {
+    return (
+        violation.name === CONST.VIOLATIONS.RTER &&
+        (violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION || violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530)
     );
 }
 
@@ -1469,6 +1472,7 @@ export {
     hasReservationList,
     hasViolation,
     hasBrokenConnectionViolation,
+    isBrokenConnectionViolation,
     shouldShowBrokenConnectionViolation,
     hasNoticeTypeViolation,
     hasWarningTypeViolation,

const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true});
const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA);
const isLoadingHoldUseExplained = isLoadingOnyxValue(dismissedHoldUseExplanationResult);
Expand Down Expand Up @@ -121,7 +122,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
),
};
}
if (hasPendingRTERViolation(getTransactionViolations(transaction?.transactionID))) {
if (hasPendingRTERViolation(getTransactionViolations(transaction?.transactionID, allTransactionViolations))) {
return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')};
}
if (isScanning) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ function MoneyRequestPreviewContent({
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS);
const [allViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const transactionViolations = getTransactionViolations(transaction?.transactionID);
const transactionViolations = getTransactionViolations(transaction?.transactionID, allViolations);

const sessionAccountID = session?.accountID;
const managerID = iouReport?.managerID ?? CONST.DEFAULT_NUMBER_ID;
Expand Down
3 changes: 2 additions & 1 deletion src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,8 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals

const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID ?? CONST.DEFAULT_NUMBER_ID}`);
const [transactionBackup] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${linkedTransactionID ?? CONST.DEFAULT_NUMBER_ID}`);
const transactionViolations = getTransactionViolations(linkedTransactionID);
const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`);
const transactionViolations = getTransactionViolations(linkedTransactionID, allTransactionViolations);

const {
created: transactionDate,
Expand Down
12 changes: 6 additions & 6 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ function ReportPreview({
const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {
selector: (_transactions) => reportTransactionsSelector(_transactions, iouReportID),
});
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
roryabraham marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can add selector to get violations only for transactions on the current report:

diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx
index 7635d2946bf..bc0bce19d00 100644
--- a/src/components/ReportActionItem/ReportPreview.tsx
+++ b/src/components/ReportActionItem/ReportPreview.tsx
@@ -148,7 +148,11 @@ function ReportPreview({
         selector: (_transactions) => reportTransactionsSelector(_transactions, iouReportID),
     });
     const lastTransaction = transactions?.at(0);
-    const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
+    const transactionIDList = transactions?.map((reportTransaction) => reportTransaction.transactionID) ?? [];
+    const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {
+        selector: (allTransactionViolations) =>
+            Object.fromEntries(Object.entries(allTransactionViolations ?? {}).filter(([key]) => transactionIDList.includes(key.replace(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, '')))),
+    });
     const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET);
     const [invoiceReceiverPolicy] = useOnyx(
         `${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : CONST.DEFAULT_NUMBER_ID}`,
@@ -230,17 +234,16 @@ function ReportPreview({
     const hasErrors =
         (hasMissingSmartscanFields && !iouSettled) ||
         // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-        hasViolations(iouReportID, allTransactionViolations, true) ||
-        hasNoticeTypeViolations(iouReportID, allTransactionViolations, true) ||
-        hasWarningTypeViolations(iouReportID, allTransactionViolations, true) ||
+        hasViolations(iouReportID, violations, true) ||
+        hasNoticeTypeViolations(iouReportID, violations, true) ||
+        hasWarningTypeViolations(iouReportID, violations, true) ||
         (isReportOwner(iouReport) && hasReportViolations(iouReportID)) ||
         hasActionsWithErrors(iouReportID);
     const lastThreeTransactions = transactions?.slice(-3) ?? [];
     const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...getThumbnailAndImageURIs(transaction), transaction}));
-    const transactionIDList = transactions?.map((reportTransaction) => reportTransaction.transactionID) ?? [];
     const lastTransactionViolations = useTransactionViolations(lastTransaction?.transactionID);
     const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(lastTransaction, lastTransactionViolations);
-    const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, iouReport, policy, allTransactionViolations);
+    const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, iouReport, policy, violations);
     let formattedMerchant = numberOfRequests === 1 ? getMerchant(lastTransaction) : null;
     const formattedDescription = numberOfRequests === 1 ? getDescription(lastTransaction) : null;
 
@@ -251,7 +254,7 @@ function ReportPreview({
     const isArchived = isArchivedReportWithID(iouReport?.reportID);
     const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
     const filteredTransactions = transactions?.filter((transaction) => transaction) ?? [];
-    const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions, allTransactionViolations);
+    const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions, violations);
     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

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You could also DRY this selector into a hook. Something like:

import {useOnyx} from 'react-native-onyx';
import {reportTransactionsSelector} from '@libs/ReportUtils';
import ONYXKEYS from '@src/ONYXKEYS';

function useReportWithTransactionsAndViolations(reportID?: string) {
    const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID ?? '-1'}`);
    const [transactions = []] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {
        selector: (_transactions) => reportTransactionsSelector(_transactions, reportID),
    });
    const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {
        selector: (allViolations) =>
            Object.fromEntries(
                Object.entries(allViolations ?? {}).filter(([key]) =>
                    transactions.some((transaction) => transaction.transactionID === key.replace(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, '')),
                ),
            ),
    });
    return [report, transactions, violations];
}

export default useReportWithTransactionsAndViolations;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All done, also created the useReportWithTransactionsAndViolations hook.

const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET);
const [invoiceReceiverPolicy] = useOnyx(
`${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : CONST.DEFAULT_NUMBER_ID}`,
Expand Down Expand Up @@ -229,16 +229,16 @@ function ReportPreview({
const hasErrors =
(hasMissingSmartscanFields && !iouSettled) ||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
hasViolations(iouReportID, transactionViolations, true) ||
hasNoticeTypeViolations(iouReportID, transactionViolations, true) ||
hasWarningTypeViolations(iouReportID, transactionViolations, true) ||
hasViolations(iouReportID, allTransactionViolations, true) ||
hasNoticeTypeViolations(iouReportID, allTransactionViolations, true) ||
hasWarningTypeViolations(iouReportID, allTransactionViolations, true) ||
(isReportOwner(iouReport) && hasReportViolations(iouReportID)) ||
hasActionsWithErrors(iouReportID);
const lastThreeTransactions = transactions?.slice(-3) ?? [];
const lastTransaction = transactions?.at(0);
const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...getThumbnailAndImageURIs(transaction), transaction}));
const transactionIDList = transactions?.map((reportTransaction) => reportTransaction.transactionID) ?? [];
const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(lastTransaction, getTransactionViolations(lastTransaction?.transactionID));
const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(lastTransaction, getTransactionViolations(lastTransaction?.transactionID, allTransactionViolations));
const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, iouReport, policy);
let formattedMerchant = numberOfRequests === 1 ? getMerchant(lastTransaction) : null;
const formattedDescription = numberOfRequests === 1 ? getDescription(lastTransaction) : null;
Expand All @@ -250,7 +250,7 @@ function ReportPreview({
const isArchived = isArchivedReportWithID(iouReport?.reportID);
const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
const filteredTransactions = transactions?.filter((transaction) => transaction) ?? [];
const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions);
const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions, allTransactionViolations);

const shouldDisableSubmitButton = shouldShowSubmitButton && !isAllowedToSubmitDraftExpenseReport(iouReport);

Expand Down
2 changes: 1 addition & 1 deletion src/libs/SearchUIUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -355,7 +355,7 @@ function getAction(data: OnyxTypes.SearchResults['data'], key: string): SearchTr
}

// 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) && isAllowedToApproveExpenseReport) {
if (canSubmitReport(report, policy, allReportTransactions, allViolations) && isAllowedToApproveExpenseReport) {
return CONST.SEARCH.ACTION_TYPES.SUBMIT;
}

Expand Down
23 changes: 14 additions & 9 deletions src/libs/TransactionUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -766,11 +766,11 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry<Transaction>):
/**
* Get all transaction violations of the transaction with given tranactionID.
*/
function getTransactionViolations(transactionID: string | undefined): TransactionViolations | null {
if (!transactionID) {
function getTransactionViolations(transactionID: string | undefined, transactionViolations: OnyxCollection<TransactionViolations> | undefined): TransactionViolations | null {
if (!transactionID || !transactionViolations) {
return null;
}
return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation) => !isViolationDismissed(transactionID, violation)) ?? null;
return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation) => !isViolationDismissed(transactionID, violation)) ?? null;
}

/**
Expand All @@ -789,8 +789,8 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations |
/**
* Check if there is broken connection violation.
*/
function hasBrokenConnectionViolation(transactionID?: string): boolean {
const violations = getTransactionViolations(transactionID);
function hasBrokenConnectionViolation(transactionID?: string, allViolations?: OnyxCollection<TransactionViolations>): boolean {
const violations = getTransactionViolations(transactionID, allViolations ?? allTransactionViolations);
return !!violations?.find(
(violation) =>
violation.name === CONST.VIOLATIONS.RTER &&
Expand All @@ -801,8 +801,13 @@ function hasBrokenConnectionViolation(transactionID?: string): boolean {
/**
* Check if user should see broken connection violation warning.
*/
function shouldShowBrokenConnectionViolation(transactionIDList: string[] | undefined, report: OnyxEntry<Report> | SearchReport, policy: OnyxEntry<Policy> | SearchPolicy): boolean {
const transactionsWithBrokenConnectionViolation = transactionIDList?.map((transactionID) => hasBrokenConnectionViolation(transactionID)) ?? [];
function shouldShowBrokenConnectionViolation(
transactionIDList: string[] | undefined,
report: OnyxEntry<Report> | SearchReport,
policy: OnyxEntry<Policy> | SearchPolicy,
allViolations?: OnyxCollection<TransactionViolations>,
): boolean {
const transactionsWithBrokenConnectionViolation = transactionIDList?.map((transactionID) => hasBrokenConnectionViolation(transactionID, allViolations)) ?? [];
return (
transactionsWithBrokenConnectionViolation.length > 0 &&
transactionsWithBrokenConnectionViolation?.some((value) => value === true) &&
Expand All @@ -813,9 +818,9 @@ function shouldShowBrokenConnectionViolation(transactionIDList: string[] | undef
/**
* Check if there is pending rter violation in all transactionViolations with given transactionIDs.
*/
function allHavePendingRTERViolation(transactionIds: string[]): boolean {
function allHavePendingRTERViolation(transactionIds: string[], allViolations?: OnyxCollection<TransactionViolations>): boolean {
const transactionsWithRTERViolations = transactionIds.map((transactionId) => {
const transactionViolations = getTransactionViolations(transactionId);
const transactionViolations = getTransactionViolations(transactionId, allViolations ?? allTransactionViolations);
return hasPendingRTERViolation(transactionViolations);
});
return transactionsWithRTERViolations.length > 0 && transactionsWithRTERViolations.every((value) => value === true);
Expand Down
5 changes: 3 additions & 2 deletions src/libs/actions/IOU.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8014,14 +8014,15 @@ function canSubmitReport(
report: OnyxEntry<OnyxTypes.Report> | SearchReport,
policy: OnyxEntry<OnyxTypes.Policy> | SearchPolicy,
transactions: OnyxTypes.Transaction[] | SearchTransaction[],
allViolations?: OnyxCollection<OnyxTypes.TransactionViolations>,
) {
const currentUserAccountID = getCurrentUserAccountID();
const isOpenExpenseReport = isOpenExpenseReportReportUtils(report);
const isArchived = isArchivedReportWithID(report?.reportID);
const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
const transactionIDList = transactions.map((transaction) => transaction.transactionID);
const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList);
const hasBrokenConnectionViolation = shouldShowBrokenConnectionViolation(transactionIDList, report, policy);
const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList, allViolations);
const hasBrokenConnectionViolation = shouldShowBrokenConnectionViolation(transactionIDList, report, policy, allViolations);

const hasOnlyPendingCardOrScanFailTransactions =
transactions.length > 0 &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import React from 'react';
import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import PressableWithFeedback from '@components/Pressable/PressableWithFeedback';
import ScrollView from '@components/ScrollView';
Expand All @@ -7,6 +8,7 @@ import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import Navigation from '@libs/Navigation/Navigation';
import {getTransactionViolations} from '@libs/TransactionUtils';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {TransactionViolation} from '@src/types/onyx';

Expand All @@ -16,7 +18,8 @@ type DebugTransactionViolationsProps = {
};

function DebugTransactionViolations({transactionID}: DebugTransactionViolationsProps) {
const transactionViolations = getTransactionViolations(transactionID);
const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`);
const transactionViolations = getTransactionViolations(transactionID, allTransactionViolations);

const styles = useThemeStyles();
const {translate} = useLocalize();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {useCallback, useState} from 'react';
import {View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import Button from '@components/Button';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
Expand Down Expand Up @@ -62,7 +63,8 @@ function DebugTransactionViolationCreatePage({
}: DebugTransactionViolationCreatePageProps) {
const {translate} = useLocalize();
const styles = useThemeStyles();
const transactionViolations = getTransactionViolations(transactionID);
const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`);
const transactionViolations = getTransactionViolations(transactionID, allTransactionViolations);
const [draftTransactionViolation, setDraftTransactionViolation] = useState<string>(() => getInitialTransactionViolation());
const [error, setError] = useState<string>();

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, {useCallback, useMemo} from 'react';
import {InteractionManager, View} from 'react-native';
import {useOnyx} from 'react-native-onyx';
import HeaderWithBackButton from '@components/HeaderWithBackButton';
import ScreenWrapper from '@components/ScreenWrapper';
import useLocalize from '@hooks/useLocalize';
Expand Down Expand Up @@ -29,7 +30,8 @@ function DebugTransactionViolationPage({
},
}: DebugTransactionViolationPageProps) {
const {translate} = useLocalize();
const transactionViolations = getTransactionViolations(transactionID);
const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`);
const transactionViolations = getTransactionViolations(transactionID, allTransactionViolations);
const transactionViolation = useMemo(() => transactionViolations?.[Number(index)], [index, transactionViolations]);
const styles = useThemeStyles();

Expand Down
3 changes: 2 additions & 1 deletion src/pages/TransactionDuplicate/Review.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ function TransactionDuplicateReview() {
const route = useRoute<PlatformStackRouteProp<TransactionDuplicateNavigatorParamList, typeof SCREENS.TRANSACTION_DUPLICATE.REVIEW>>();
const currentPersonalDetails = useCurrentUserPersonalDetails();
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`);
const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`);
const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID);
const transactionID = getLinkedTransactionID(reportAction, report?.reportID) ?? undefined;
const transactionViolations = getTransactionViolations(transactionID);
const transactionViolations = getTransactionViolations(transactionID, allTransactionViolations);

const duplicateTransactionIDs = useMemo(
() => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [],
Expand Down
3 changes: 2 additions & 1 deletion src/pages/iou/request/step/IOURequestStepAttendees.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,8 @@ function IOURequestStepAttendees({
const [attendees, setAttendees] = useState<Attendee[]>(() => getAttendees(transaction));
const previousAttendees = usePrevious(attendees);
const {translate} = useLocalize();
const violations = getTransactionViolations(transactionID);
const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`);
const violations = getTransactionViolations(transactionID, allTransactionViolations);

const saveAttendees = useCallback(() => {
if (attendees.length <= 0) {
Expand Down
7 changes: 3 additions & 4 deletions tests/unit/ViolationUtilsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy, PolicyCategories, PolicyTagLists, Transaction, TransactionViolation} from '@src/types/onyx';
import type {TransactionCollectionDataSet} from '@src/types/onyx/Transaction';
import type {TransactionViolationsCollectionDataSet} from '@src/types/onyx/TransactionViolation';

const categoryOutOfPolicyViolation = {
name: CONST.VIOLATIONS.CATEGORY_OUT_OF_POLICY,
Expand Down Expand Up @@ -419,14 +418,14 @@ describe('getViolations', () => {
[`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: transaction,
};

const transactionViolationsCollection: TransactionViolationsCollectionDataSet = {
const transactionViolationsCollection = {
[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`]: [duplicatedTransactionViolation, smartScanFailedViolation, tagOutOfPolicyViolation],
};

await Onyx.multiSet({...transactionCollectionDataSet, ...transactionViolationsCollection});
await Onyx.multiSet({...transactionCollectionDataSet});

// Should filter out the smartScanFailedViolation
const filteredViolations = getTransactionViolations(transaction.transactionID);
const filteredViolations = getTransactionViolations(transaction.transactionID, transactionViolationsCollection);
expect(filteredViolations).toEqual([duplicatedTransactionViolation, tagOutOfPolicyViolation]);
});

Expand Down