From 1e084f2b40f4cf631ad23866c0618ec3cda1a005 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 5 Feb 2025 19:33:59 +0530 Subject: [PATCH 01/10] fix: Expense details - Violation is not displayed on description field when opening IOU details. Signed-off-by: krishna2323 --- .../BrokenConnectionDescription.tsx | 5 +++- src/components/MoneyRequestHeader.tsx | 3 ++- .../MoneyRequestPreviewContent.tsx | 2 +- .../ReportActionItem/MoneyRequestView.tsx | 3 ++- .../ReportActionItem/ReportPreview.tsx | 12 +++++----- src/libs/SearchUIUtils.ts | 2 +- src/libs/TransactionUtils/index.ts | 23 +++++++++++-------- src/libs/actions/IOU.ts | 5 ++-- .../DebugTransactionViolations.tsx | 5 +++- .../DebugTransactionViolationCreatePage.tsx | 4 +++- .../DebugTransactionViolationPage.tsx | 4 +++- src/pages/TransactionDuplicate/Review.tsx | 3 ++- .../request/step/IOURequestStepAttendees.tsx | 3 ++- 13 files changed, 47 insertions(+), 27 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index bc628458c46c..a61a7a3f976a 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -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'; @@ -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'; @@ -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); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 0d0e862f36f5..684335d3d272 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -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); const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true}); const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA); const isLoadingHoldUseExplained = isLoadingOnyxValue(dismissedHoldUseExplanationResult); @@ -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) { diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 7eaee699018d..876e40d36e72 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -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; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 52cfa82366b4..a4fc6016bc6f 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -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, diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index b05add5bc162..9717ed711de8 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -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); 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}`, @@ -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; @@ -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); diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 3bf31689dce9..9ff584e3a882 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -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; } diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index ad5f53602bdc..63f91557e47a 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -766,11 +766,11 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry): /** * 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 | 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; } /** @@ -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): boolean { + const violations = getTransactionViolations(transactionID, allViolations ?? allTransactionViolations); return !!violations?.find( (violation) => violation.name === CONST.VIOLATIONS.RTER && @@ -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 | SearchReport, policy: OnyxEntry | SearchPolicy): boolean { - const transactionsWithBrokenConnectionViolation = transactionIDList?.map((transactionID) => hasBrokenConnectionViolation(transactionID)) ?? []; +function shouldShowBrokenConnectionViolation( + transactionIDList: string[] | undefined, + report: OnyxEntry | SearchReport, + policy: OnyxEntry | SearchPolicy, + allViolations?: OnyxCollection, +): boolean { + const transactionsWithBrokenConnectionViolation = transactionIDList?.map((transactionID) => hasBrokenConnectionViolation(transactionID, allViolations)) ?? []; return ( transactionsWithBrokenConnectionViolation.length > 0 && transactionsWithBrokenConnectionViolation?.some((value) => value === true) && @@ -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): 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); diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index d2b4d018bc23..d843edf6d170 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -8014,14 +8014,15 @@ function canSubmitReport( report: OnyxEntry | SearchReport, policy: OnyxEntry | SearchPolicy, transactions: OnyxTypes.Transaction[] | SearchTransaction[], + allViolations?: OnyxCollection, ) { 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 && diff --git a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx index 5190d6c76e50..bc0600f5c76d 100644 --- a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx +++ b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx @@ -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'; @@ -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'; @@ -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(); diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx index 452925da8b86..2530639b0ef3 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx @@ -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'; @@ -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(() => getInitialTransactionViolation()); const [error, setError] = useState(); diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx index 1b26b0c5f72a..6c8246502bf7 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx @@ -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'; @@ -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(); diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx index 3c77c173415f..824b011fffca 100644 --- a/src/pages/TransactionDuplicate/Review.tsx +++ b/src/pages/TransactionDuplicate/Review.tsx @@ -28,9 +28,10 @@ function TransactionDuplicateReview() { const route = useRoute>(); 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 ?? [], diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index 2e64e55fb8a1..9e4f700227d5 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -43,7 +43,8 @@ function IOURequestStepAttendees({ const [attendees, setAttendees] = useState(() => 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) { From fafe6d4ed23561d25dad931f5d4d731eb0a44736 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 5 Feb 2025 20:06:48 +0530 Subject: [PATCH 02/10] TS issues. Signed-off-by: krishna2323 --- tests/unit/ViolationUtilsTest.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index b161ea8fe40c..335cc644ec61 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -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, @@ -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]); }); From 1143243a0f06c047f2329ddd98544e9f9d52f4f0 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 6 Feb 2025 02:46:44 +0530 Subject: [PATCH 03/10] create useTransactionViolations hook. Signed-off-by: krishna2323 --- .../BrokenConnectionDescription.tsx | 7 +-- src/components/MoneyRequestHeader.tsx | 9 ++-- .../MoneyRequestPreviewContent.tsx | 7 +-- .../ReportActionItem/MoneyRequestView.tsx | 5 +- .../ReportActionItem/ReportPreview.tsx | 9 ++-- src/hooks/useTransactionViolations.ts | 19 ++++++++ src/libs/TransactionUtils/index.ts | 46 ++++++++++--------- .../DebugTransactionViolations.tsx | 7 +-- .../DebugTransactionViolationCreatePage.tsx | 7 ++- .../DebugTransactionViolationPage.tsx | 7 ++- src/pages/TransactionDuplicate/Review.tsx | 6 +-- .../request/step/IOURequestStepAttendees.tsx | 10 ++-- tests/unit/ViolationUtilsTest.ts | 4 +- 13 files changed, 79 insertions(+), 64 deletions(-) create mode 100644 src/hooks/useTransactionViolations.ts diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index a61a7a3f976a..b5b2e9ef4a4f 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -1,14 +1,12 @@ 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'; +import useTransactionViolations from '@hooks/useTransactionViolations'; import {isInstantSubmitEnabled, isPolicyAdmin as isPolicyAdminPolicyUtils} from '@libs/PolicyUtils'; import {isCurrentUserSubmitter, isProcessingReport, isReportApproved, isReportManuallyReimbursed} from '@libs/ReportUtils'; -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'; @@ -26,8 +24,7 @@ type BrokenConnectionDescriptionProps = { function BrokenConnectionDescription({transactionID, policy, report}: BrokenConnectionDescriptionProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`); - const transactionViolations = getTransactionViolations(transactionID, allTransactionViolations); + const transactionViolations = useTransactionViolations(transactionID); 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); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 684335d3d272..223803220ac4 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -8,6 +8,7 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useTransactionViolations from '@hooks/useTransactionViolations'; import {markAsCash as markAsCashUtil} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; import {isPolicyAdmin} from '@libs/PolicyUtils'; @@ -15,7 +16,6 @@ import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils import {isCurrentUserSubmitter} from '@libs/ReportUtils'; import { allHavePendingRTERViolation, - getTransactionViolations, hasPendingRTERViolation, hasReceipt, isDuplicate as isDuplicateTransactionUtils, @@ -67,6 +67,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID }`, ); + 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); @@ -81,9 +82,9 @@ 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); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList, allTransactionViolations); - const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, parentReport, policy); + const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, parentReport, policy, allTransactionViolations); const shouldShowMarkAsCashButton = hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID))); @@ -122,7 +123,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre ), }; } - if (hasPendingRTERViolation(getTransactionViolations(transaction?.transactionID, allTransactionViolations))) { + if (hasPendingRTERViolation(transactionViolations)) { return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')}; } if (isScanning) { diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 876e40d36e72..eff664acd7e2 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -23,6 +23,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useTransactionViolations from '@hooks/useTransactionViolations'; import useWindowDimensions from '@hooks/useWindowDimensions'; import ControlSelection from '@libs/ControlSelection'; import {convertToDisplayString} from '@libs/CurrencyUtils'; @@ -48,7 +49,6 @@ import type {TransactionDetails} from '@libs/ReportUtils'; import StringUtils from '@libs/StringUtils'; import { compareDuplicateTransactionFields, - getTransactionViolations, hasMissingSmartscanFields, hasNoticeTypeViolation as hasNoticeTypeViolationTransactionUtils, hasPendingUI, @@ -113,7 +113,8 @@ 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, allViolations); + const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const transactionViolations = useTransactionViolations(transaction?.transactionID); const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? CONST.DEFAULT_NUMBER_ID; @@ -281,7 +282,7 @@ function MoneyRequestPreviewContent({ if (isPending(transaction)) { return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; } - if (shouldShowBrokenConnectionViolation(transaction ? [transaction.transactionID] : [], iouReport, policy)) { + if (shouldShowBrokenConnectionViolation(transaction ? [transaction.transactionID] : [], iouReport, policy, allTransactionViolations)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } if (hasPendingUI(transaction, transactionViolations)) { diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index a4fc6016bc6f..430fb2b6e30a 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -16,6 +16,7 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails' import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; +import useTransactionViolations from '@hooks/useTransactionViolations'; import useViolations from '@hooks/useViolations'; import type {ViolationField} from '@hooks/useViolations'; import {convertToDisplayString} from '@libs/CurrencyUtils'; @@ -49,7 +50,6 @@ import { getDistanceInMeters, getTagForDisplay, getTaxName, - getTransactionViolations, hasMissingSmartscanFields, hasReceipt as hasReceiptTransactionUtils, hasReservationList, @@ -134,8 +134,7 @@ 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 [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`); - const transactionViolations = getTransactionViolations(linkedTransactionID, allTransactionViolations); + const transactionViolations = useTransactionViolations(transaction?.transactionID); const { created: transactionDate, diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 9717ed711de8..14b5452573b4 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -21,6 +21,7 @@ import useNetwork from '@hooks/useNetwork'; import usePolicy from '@hooks/usePolicy'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useTransactionViolations from '@hooks/useTransactionViolations'; import ControlSelection from '@libs/ControlSelection'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; @@ -71,7 +72,6 @@ import StringUtils from '@libs/StringUtils'; import { getDescription, getMerchant, - getTransactionViolations, hasPendingUI, isCardTransaction, isPartialMerchant, @@ -147,6 +147,7 @@ function ReportPreview({ const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, { selector: (_transactions) => reportTransactionsSelector(_transactions, iouReportID), }); + const lastTransaction = transactions?.at(0); const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET); const [invoiceReceiverPolicy] = useOnyx( @@ -235,11 +236,11 @@ function ReportPreview({ (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, allTransactionViolations)); - const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, iouReport, policy); + const lastTransactionViolations = useTransactionViolations(lastTransaction?.transactionID); + const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(lastTransaction, lastTransactionViolations); + const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, iouReport, policy, allTransactionViolations); let formattedMerchant = numberOfRequests === 1 ? getMerchant(lastTransaction) : null; const formattedDescription = numberOfRequests === 1 ? getDescription(lastTransaction) : null; diff --git a/src/hooks/useTransactionViolations.ts b/src/hooks/useTransactionViolations.ts new file mode 100644 index 000000000000..463d8af27304 --- /dev/null +++ b/src/hooks/useTransactionViolations.ts @@ -0,0 +1,19 @@ +import {useOnyx} from 'react-native-onyx'; +import {getTransaction, isViolationDismissed} from '@libs/TransactionUtils'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {TransactionViolations} from '@src/types/onyx'; + +function useTransactionViolations(transactionID?: string): TransactionViolations | undefined { + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, { + selector: (violations) => { + const transaction = getTransaction(transactionID); + if (!transaction) { + return []; + } + return (violations ?? []).filter((violation) => !isViolationDismissed(transaction, violation)); + }, + }); + return transactionViolations; +} + +export default useTransactionViolations; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 63f91557e47a..908cc469be3b 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -766,11 +766,12 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry): /** * Get all transaction violations of the transaction with given tranactionID. */ -function getTransactionViolations(transactionID: string | undefined, transactionViolations: OnyxCollection | undefined): TransactionViolations | null { +function getTransactionViolations(transactionID: string | undefined, transactionViolations: OnyxCollection | undefined): TransactionViolations | undefined { + const transaction = getTransaction(transactionID); if (!transactionID || !transactionViolations) { - return null; + return undefined; } - return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation) => !isViolationDismissed(transactionID, violation)) ?? null; + return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation) => !isViolationDismissed(transaction, violation)); } /** @@ -789,8 +790,8 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | /** * Check if there is broken connection violation. */ -function hasBrokenConnectionViolation(transactionID?: string, allViolations?: OnyxCollection): boolean { - const violations = getTransactionViolations(transactionID, allViolations ?? allTransactionViolations); +function hasBrokenConnectionViolation(transactionID: string | undefined, transactionViolations: OnyxCollection | undefined): boolean { + const violations = getTransactionViolations(transactionID, transactionViolations); return !!violations?.find( (violation) => violation.name === CONST.VIOLATIONS.RTER && @@ -805,9 +806,9 @@ function shouldShowBrokenConnectionViolation( transactionIDList: string[] | undefined, report: OnyxEntry | SearchReport, policy: OnyxEntry | SearchPolicy, - allViolations?: OnyxCollection, + transactionViolations: OnyxCollection | undefined, ): boolean { - const transactionsWithBrokenConnectionViolation = transactionIDList?.map((transactionID) => hasBrokenConnectionViolation(transactionID, allViolations)) ?? []; + const transactionsWithBrokenConnectionViolation = transactionIDList?.map((transactionID) => hasBrokenConnectionViolation(transactionID, transactionViolations)) ?? []; return ( transactionsWithBrokenConnectionViolation.length > 0 && transactionsWithBrokenConnectionViolation?.some((value) => value === true) && @@ -818,10 +819,10 @@ function shouldShowBrokenConnectionViolation( /** * Check if there is pending rter violation in all transactionViolations with given transactionIDs. */ -function allHavePendingRTERViolation(transactionIds: string[], allViolations?: OnyxCollection): boolean { +function allHavePendingRTERViolation(transactionIds: string[], transactionViolations: OnyxCollection | undefined): boolean { const transactionsWithRTERViolations = transactionIds.map((transactionId) => { - const transactionViolations = getTransactionViolations(transactionId, allViolations ?? allTransactionViolations); - return hasPendingRTERViolation(transactionViolations); + const filteredTransactionViolations = getTransactionViolations(transactionId, transactionViolations); + return hasPendingRTERViolation(filteredTransactionViolations); }); return transactionsWithRTERViolations.length > 0 && transactionsWithRTERViolations.every((value) => value === true); } @@ -911,7 +912,8 @@ function getRecentTransactions(transactions: Record, size = 2): * @param checkDismissed - whether to check if the violation has already been dismissed as well */ function isDuplicate(transactionID: string | undefined, checkDismissed = false): boolean { - if (!transactionID) { + const transaction = getTransaction(transactionID); + if (!transaction) { return false; } const duplicateViolation = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.find( @@ -923,7 +925,7 @@ function isDuplicate(transactionID: string | undefined, checkDismissed = false): return !!duplicateViolation; } - const didDismissedViolation = isViolationDismissed(transactionID, duplicateViolation); + const didDismissedViolation = isViolationDismissed(transaction, duplicateViolation); return hasDuplicatedViolation && !didDismissedViolation; } @@ -953,21 +955,21 @@ function isOnHoldByTransactionID(transactionID: string | undefined | null): bool /** * Checks if a violation is dismissed for the given transaction */ -function isViolationDismissed(transactionID: string | undefined, violation: TransactionViolation | undefined): boolean { - if (!transactionID || !violation) { +function isViolationDismissed(transaction: OnyxEntry, violation: TransactionViolation | undefined): boolean { + if (!transaction || !violation) { return false; } - return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail] === `${currentUserAccountID}`; + return transaction?.comment?.dismissedViolations?.[violation.name]?.[currentUserEmail] === `${currentUserAccountID}`; } /** * Checks if any violations for the provided transaction are of type 'violation' */ function hasViolation(transactionID: string | undefined, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { - if (!transactionID) { + const transaction = getTransaction(transactionID); + if (!transaction) { return false; } - const transaction = getTransaction(transactionID); if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { return false; } @@ -975,7 +977,7 @@ function hasViolation(transactionID: string | undefined, transactionViolations: (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transactionID, violation), + !isViolationDismissed(transaction, violation), ); } @@ -983,10 +985,10 @@ function hasViolation(transactionID: string | undefined, transactionViolations: * Checks if any violations for the provided transaction are of type 'notice' */ function hasNoticeTypeViolation(transactionID: string | undefined, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { - if (!transactionID) { + const transaction = getTransaction(transactionID); + if (!transaction) { return false; } - const transaction = getTransaction(transactionID); if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { return false; } @@ -994,7 +996,7 @@ function hasNoticeTypeViolation(transactionID: string | undefined, transactionVi (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transactionID, violation), + !isViolationDismissed(transaction, violation), ); } @@ -1015,7 +1017,7 @@ function hasWarningTypeViolation(transactionID: string | undefined, transactionV (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transactionID, violation), + !isViolationDismissed(transaction, violation), ) ?? []; const hasOnlyDupeDetectionViolation = warningTypeViolations?.every((violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION); diff --git a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx index bc0600f5c76d..ca5f605470c7 100644 --- a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx +++ b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx @@ -1,14 +1,12 @@ 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'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import useTransactionViolations from '@hooks/useTransactionViolations'; 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'; @@ -18,8 +16,7 @@ type DebugTransactionViolationsProps = { }; function DebugTransactionViolations({transactionID}: DebugTransactionViolationsProps) { - const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`); - const transactionViolations = getTransactionViolations(transactionID, allTransactionViolations); + const transactionViolations = useTransactionViolations(transactionID); const styles = useThemeStyles(); const {translate} = useLocalize(); diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx index 2530639b0ef3..6096a069f558 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx @@ -1,6 +1,5 @@ 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'; @@ -9,12 +8,12 @@ import Text from '@components/Text'; import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import useTransactionViolations from '@hooks/useTransactionViolations'; import DebugUtils from '@libs/DebugUtils'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; -import {getTransactionViolations} from '@libs/TransactionUtils'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; import Debug from '@userActions/Debug'; import CONST from '@src/CONST'; @@ -63,8 +62,8 @@ function DebugTransactionViolationCreatePage({ }: DebugTransactionViolationCreatePageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`); - const transactionViolations = getTransactionViolations(transactionID, allTransactionViolations); + const transactionViolations = useTransactionViolations(transactionID); + const [draftTransactionViolation, setDraftTransactionViolation] = useState(() => getInitialTransactionViolation()); const [error, setError] = useState(); diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx index 6c8246502bf7..f062195e807d 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx @@ -1,10 +1,10 @@ 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'; import useThemeStyles from '@hooks/useThemeStyles'; +import useTransactionViolations from '@hooks/useTransactionViolations'; import Debug from '@libs/actions/Debug'; import DebugUtils from '@libs/DebugUtils'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; @@ -13,7 +13,6 @@ import DebugTabNavigator from '@libs/Navigation/DebugTabNavigator'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; -import {getTransactionViolations} from '@libs/TransactionUtils'; import DebugDetails from '@pages/Debug/DebugDetails'; import DebugJSON from '@pages/Debug/DebugJSON'; import NotFoundPage from '@pages/ErrorPage/NotFoundPage'; @@ -30,9 +29,9 @@ function DebugTransactionViolationPage({ }, }: DebugTransactionViolationPageProps) { const {translate} = useLocalize(); - const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`); - const transactionViolations = getTransactionViolations(transactionID, allTransactionViolations); + const transactionViolations = useTransactionViolations(transactionID); const transactionViolation = useMemo(() => transactionViolations?.[Number(index)], [index, transactionViolations]); + const styles = useThemeStyles(); const saveChanges = useCallback( diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx index 824b011fffca..001c3a788e2d 100644 --- a/src/pages/TransactionDuplicate/Review.tsx +++ b/src/pages/TransactionDuplicate/Review.tsx @@ -10,13 +10,14 @@ import Text from '@components/Text'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; +import useTransactionViolations from '@hooks/useTransactionViolations'; import {dismissDuplicateTransactionViolation} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import {getLinkedTransactionID, getReportAction} from '@libs/ReportActionsUtils'; import {isReportIDApproved, isSettled} from '@libs/ReportUtils'; -import {getTransaction, getTransactionViolations} from '@libs/TransactionUtils'; +import {getTransaction} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -28,10 +29,9 @@ function TransactionDuplicateReview() { const route = useRoute>(); 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, allTransactionViolations); + const transactionViolations = useTransactionViolations(transactionID); const duplicateTransactionIDs = useMemo( () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index 9e4f700227d5..6e9f674d7a79 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -4,9 +4,10 @@ import {useOnyx} from 'react-native-onyx'; import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; +import useTransactionViolations from '@hooks/useTransactionViolations'; import {setMoneyRequestAttendees, updateMoneyRequestAttendees} from '@libs/actions/IOU'; import Navigation from '@libs/Navigation/Navigation'; -import {getAttendees, getTransactionViolations} from '@libs/TransactionUtils'; +import {getAttendees} from '@libs/TransactionUtils'; import MoneyRequestAttendeeSelector from '@pages/iou/request/MoneyRequestAttendeeSelector'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -43,8 +44,7 @@ function IOURequestStepAttendees({ const [attendees, setAttendees] = useState(() => getAttendees(transaction)); const previousAttendees = usePrevious(attendees); const {translate} = useLocalize(); - const [allTransactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}`); - const violations = getTransactionViolations(transactionID, allTransactionViolations); + const transactionViolations = useTransactionViolations(transactionID); const saveAttendees = useCallback(() => { if (attendees.length <= 0) { @@ -53,12 +53,12 @@ function IOURequestStepAttendees({ if (!lodashIsEqual(previousAttendees, attendees)) { setMoneyRequestAttendees(transactionID, attendees, !isEditing); if (isEditing) { - updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, violations ?? undefined); + updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, transactionViolations ?? undefined); } } Navigation.goBack(backTo); - }, [attendees, backTo, isEditing, policy, policyCategories, policyTags, previousAttendees, reportID, transactionID, violations]); + }, [attendees, backTo, isEditing, policy, policyCategories, policyTags, previousAttendees, reportID, transactionID, transactionViolations]); const navigateBack = () => { Navigation.goBack(backTo); diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index 335cc644ec61..596c6a64fbc6 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -402,8 +402,8 @@ describe('getViolations', () => { await Onyx.multiSet({...transactionCollectionDataSet}); - const isSmartScanDismissed = isViolationDismissed(transaction.transactionID, smartScanFailedViolation); - const isDuplicateViolationDismissed = isViolationDismissed(transaction.transactionID, duplicatedTransactionViolation); + const isSmartScanDismissed = isViolationDismissed(transaction, smartScanFailedViolation); + const isDuplicateViolationDismissed = isViolationDismissed(transaction, duplicatedTransactionViolation); expect(isSmartScanDismissed).toBeTruthy(); expect(isDuplicateViolationDismissed).toBeFalsy(); From fc381b2359a6abbe403320eeac22d0229b752eb8 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 6 Feb 2025 02:50:44 +0530 Subject: [PATCH 04/10] fix TS fails. Signed-off-by: krishna2323 --- src/components/MoneyReportHeader.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 1d6c3fe4cfba..45585a03b14f 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -133,6 +133,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const isOnHold = isOnHoldTransactionUtils(transaction); const isDeletedParentAction = !!requestParentReportAction && isDeletedAction(requestParentReportAction); const isDuplicate = isDuplicateTransactionUtils(transaction?.transactionID); + const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); // Only the requestor can delete the request, admins can only edit it. const isActionOwner = @@ -150,8 +151,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea return !!transactions && transactions.length > 0 && transactions.every((t) => isExpensifyCardTransaction(t) && isPending(t)); }, [transactions]); const transactionIDs = transactions?.map((t) => t.transactionID) ?? []; - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs); - const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transactionIDs, moneyRequestReport, policy); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs, allTransactionViolations); + const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transactionIDs, moneyRequestReport, policy, allTransactionViolations); const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(moneyRequestReport?.reportID); const isPayAtEndExpense = isPayAtEndExpenseTransactionUtils(transaction); const isArchivedReport = isArchivedReportWithID(moneyRequestReport?.reportID); From acb2a64b9efd9930ea94c152d541f33f454620c1 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 6 Feb 2025 03:00:34 +0530 Subject: [PATCH 05/10] fix Jest Unit Tests. Signed-off-by: krishna2323 --- src/libs/ReportUtils.ts | 2 +- src/libs/TransactionUtils/index.ts | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index dbf2bd7d3a35..616ea4086ad4 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6920,7 +6920,7 @@ function hasViolations( reportTransactions?: SearchTransaction[], ): boolean { const transactions = reportTransactions ?? getReportTransactions(reportID); - return transactions.some((transaction) => hasViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); + return transactions.some((transaction) => hasViolation(transaction, transactionViolations, shouldShowInReview)); } /** diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 908cc469be3b..28cf1e205e05 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -965,15 +965,14 @@ function isViolationDismissed(transaction: OnyxEntry, violation: Tr /** * Checks if any violations for the provided transaction are of type 'violation' */ -function hasViolation(transactionID: string | undefined, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { - const transaction = getTransaction(transactionID); +function hasViolation(transaction: Transaction | undefined, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { if (!transaction) { return false; } if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { return false; } - return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( + return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transaction.transactionID]?.some( (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && From 69136350251e71a4d8eb808acb89555e8a47fd08 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 6 Feb 2025 03:04:29 +0530 Subject: [PATCH 06/10] minor fix. Signed-off-by: krishna2323 --- .../MoneyRequestPreview/MoneyRequestPreviewContent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index eff664acd7e2..5077d6cf3705 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -147,7 +147,7 @@ function MoneyRequestPreviewContent({ const isOnHold = isOnHoldTransactionUtils(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = hasViolationTransactionUtils(transaction?.transactionID, allViolations, true); + const hasViolations = hasViolationTransactionUtils(transaction, allViolations, true); const hasNoticeTypeViolations = hasNoticeTypeViolationTransactionUtils(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); const hasWarningTypeViolations = hasWarningTypeViolationTransactionUtils(transaction?.transactionID, allViolations, true); const hasFieldErrors = hasMissingSmartscanFields(transaction); From 083bf1e8b64c3d0924e8d0e84e31b7f4ab2bc58d Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 6 Feb 2025 04:06:15 +0530 Subject: [PATCH 07/10] update as per requested changes. Signed-off-by: krishna2323 --- src/hooks/useTransactionViolations.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/src/hooks/useTransactionViolations.ts b/src/hooks/useTransactionViolations.ts index 463d8af27304..6d856261201b 100644 --- a/src/hooks/useTransactionViolations.ts +++ b/src/hooks/useTransactionViolations.ts @@ -1,19 +1,13 @@ +import {useMemo} from 'react'; import {useOnyx} from 'react-native-onyx'; -import {getTransaction, isViolationDismissed} from '@libs/TransactionUtils'; +import {isViolationDismissed} from '@libs/TransactionUtils'; import ONYXKEYS from '@src/ONYXKEYS'; import type {TransactionViolations} from '@src/types/onyx'; -function useTransactionViolations(transactionID?: string): TransactionViolations | undefined { - const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, { - selector: (violations) => { - const transaction = getTransaction(transactionID); - if (!transaction) { - return []; - } - return (violations ?? []).filter((violation) => !isViolationDismissed(transaction, violation)); - }, - }); - return transactionViolations; +function useTransactionViolations(transactionID?: string): TransactionViolations { + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); + const [transactionViolations = []] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); + return useMemo(() => transactionViolations.filter((violation) => !isViolationDismissed(transaction, violation)), [transaction, transactionViolations]); } export default useTransactionViolations; From 17fcb63143817933899d74f97deb2bd2435b8ad7 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Thu, 6 Feb 2025 22:21:11 +0530 Subject: [PATCH 08/10] optimize onyx transaction violations access. Signed-off-by: krishna2323 --- src/components/MoneyReportHeader.tsx | 9 ++++++--- .../MoneyRequestPreviewContent.tsx | 14 ++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 45585a03b14f..f157668ee444 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -133,7 +133,6 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const isOnHold = isOnHoldTransactionUtils(transaction); const isDeletedParentAction = !!requestParentReportAction && isDeletedAction(requestParentReportAction); const isDuplicate = isDuplicateTransactionUtils(transaction?.transactionID); - const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); // Only the requestor can delete the request, admins can only edit it. const isActionOwner = @@ -151,8 +150,12 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea return !!transactions && transactions.length > 0 && transactions.every((t) => isExpensifyCardTransaction(t) && isPending(t)); }, [transactions]); const transactionIDs = transactions?.map((t) => t.transactionID) ?? []; - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs, allTransactionViolations); - const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transactionIDs, moneyRequestReport, policy, allTransactionViolations); + const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, { + selector: (allTransactions) => + Object.fromEntries(Object.entries(allTransactions ?? {}).filter(([key]) => transactionIDs.includes(key.replace(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, '')))), + }); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs, violations); + const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transactionIDs, moneyRequestReport, policy, violations); const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(moneyRequestReport?.reportID); const isPayAtEndExpense = isPayAtEndExpenseTransactionUtils(transaction); const isArchivedReport = isArchivedReportWithID(moneyRequestReport?.reportID); diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 5077d6cf3705..c0a32c9f5990 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -112,8 +112,10 @@ function MoneyRequestPreviewContent({ const transactionID = isMoneyRequestAction ? getOriginalMessage(action)?.IOUTransactionID : undefined; const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`); const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS); - const [allViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); - const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); + const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, { + selector: (allTransactions) => + Object.fromEntries(Object.entries(allTransactions ?? {}).filter(([key]) => transaction?.transactionID === key.replace(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, ''))), + }); const transactionViolations = useTransactionViolations(transaction?.transactionID); const sessionAccountID = session?.accountID; @@ -147,9 +149,9 @@ function MoneyRequestPreviewContent({ const isOnHold = isOnHoldTransactionUtils(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = hasViolationTransactionUtils(transaction, allViolations, true); - const hasNoticeTypeViolations = hasNoticeTypeViolationTransactionUtils(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); - const hasWarningTypeViolations = hasWarningTypeViolationTransactionUtils(transaction?.transactionID, allViolations, true); + const hasViolations = hasViolationTransactionUtils(transaction, violations, true); + const hasNoticeTypeViolations = hasNoticeTypeViolationTransactionUtils(transaction?.transactionID, violations, true) && isPaidGroupPolicy(iouReport); + const hasWarningTypeViolations = hasWarningTypeViolationTransactionUtils(transaction?.transactionID, violations, true); const hasFieldErrors = hasMissingSmartscanFields(transaction); const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const isPerDiemRequest = isPerDiemRequestTransactionUtils(transaction); @@ -282,7 +284,7 @@ function MoneyRequestPreviewContent({ if (isPending(transaction)) { return {shouldShow: true, messageIcon: Expensicons.CreditCardHourglass, messageDescription: translate('iou.transactionPending')}; } - if (shouldShowBrokenConnectionViolation(transaction ? [transaction.transactionID] : [], iouReport, policy, allTransactionViolations)) { + if (shouldShowBrokenConnectionViolation(transaction ? [transaction.transactionID] : [], iouReport, policy, violations)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } if (hasPendingUI(transaction, transactionViolations)) { From 6000897106dc4c3c9510fd14131c428485822ae2 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 12 Feb 2025 15:31:17 +0530 Subject: [PATCH 09/10] add changes from original PR. Signed-off-by: krishna2323 --- src/components/BrokenConnectionDescription.tsx | 2 -- src/components/MoneyReportHeader.tsx | 2 +- src/components/MoneyRequestHeader.tsx | 2 +- .../MoneyRequestPreviewContent.tsx | 15 +++++---------- src/libs/ReportActionsUtils.ts | 6 +++--- src/libs/TransactionUtils/index.ts | 2 ++ src/libs/actions/IOU.ts | 4 ++-- .../Transaction/DebugTransactionViolations.tsx | 1 - .../DebugTransactionViolationCreatePage.tsx | 1 - .../DebugTransactionViolationPage.tsx | 1 - src/pages/TransactionDuplicate/Review.tsx | 8 ++++---- .../iou/request/step/IOURequestStepAttendees.tsx | 6 +++--- src/types/onyx/Transaction.ts | 2 +- tests/unit/ViolationUtilsTest.ts | 13 +++++++++++-- 14 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index abc8bbbb95f4..0bc3dba642cf 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -1,6 +1,5 @@ import React from 'react'; import type {OnyxEntry} from 'react-native-onyx'; -import {useOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useTransactionViolations from '@hooks/useTransactionViolations'; @@ -8,7 +7,6 @@ import {isInstantSubmitEnabled, isPolicyAdmin as isPolicyAdminPolicyUtils} from import {isCurrentUserSubmitter, isProcessingReport, isReportApproved, isReportManuallyReimbursed} from '@libs/ReportUtils'; 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'; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index f157668ee444..d032227a5dd2 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -181,7 +181,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; const filteredTransactions = transactions?.filter((t) => t) ?? []; - const shouldShowSubmitButton = canSubmitReport(moneyRequestReport, policy, filteredTransactions); + const shouldShowSubmitButton = canSubmitReport(moneyRequestReport, policy, filteredTransactions, violations); const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && connectedIntegration && isAdmin && canBeExported(moneyRequestReport); diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 1e521e5530b7..6c4f9f92b366 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -61,7 +61,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre // eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout(); const route = useRoute(); - const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`); + const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID ?? CONST.DEFAULT_NUMBER_ID}`); const [transaction] = useOnyx( `${ONYXKEYS.COLLECTION.TRANSACTION}${ isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID diff --git a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx index 2bec2ecf8332..c57378405b63 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -118,7 +118,6 @@ function MoneyRequestPreviewContent({ }); const transactionViolations = useTransactionViolations(transaction?.transactionID); - const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? CONST.DEFAULT_NUMBER_ID; const ownerAccountID = iouReport?.ownerAccountID ?? CONST.DEFAULT_NUMBER_ID; @@ -169,11 +168,8 @@ function MoneyRequestPreviewContent({ // Get transaction violations for given transaction id from onyx, find duplicated transactions violations and get duplicates const allDuplicates = useMemo( - () => - transactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction?.transactionID}`]?.find( - (violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, - )?.data?.duplicates ?? [], - [transaction?.transactionID, transactionViolations], + () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], + [transactionViolations], ); // Remove settled transactions from duplicates @@ -246,14 +242,13 @@ function MoneyRequestPreviewContent({ } if (shouldShowRBR && transaction) { - const violations = getTransactionViolations(transaction.transactionID, transactionViolations); if (shouldShowHoldMessage) { return `${message} ${CONST.DOT_SEPARATOR} ${translate('violations.hold')}`; } - const firstViolation = violations?.at(0); + const firstViolation = transactionViolations?.at(0); if (firstViolation) { const violationMessage = ViolationsUtils.getViolationTranslation(firstViolation, translate); - const violationsCount = violations?.filter((v) => v.type === CONST.VIOLATION_TYPES.VIOLATION).length ?? 0; + const violationsCount = transactionViolations?.filter((v) => v.type === CONST.VIOLATION_TYPES.VIOLATION).length ?? 0; const isTooLong = violationsCount > 1 || violationMessage.length > 15; const hasViolationsAndFieldErrors = violationsCount > 0 && hasFieldErrors; @@ -293,7 +288,7 @@ function MoneyRequestPreviewContent({ if (shouldShowBrokenConnectionViolation(transaction ? [transaction.transactionID] : [], iouReport, policy, violations)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (hasPendingUI(transaction, getTransactionViolations(transaction?.transactionID, transactionViolations))) { + if (hasPendingUI(transaction, transactionViolations)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index 607de0fee0e7..8cfc81070871 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1610,9 +1610,9 @@ function wasActionTakenByCurrentUser(reportAction: OnyxInputOrEntry { - if (!reportID) { - return; +function getIOUActionForReportID(reportID: string | undefined, transactionID: string | undefined): OnyxEntry { + if (!reportID || !transactionID) { + return undefined; } const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const reportActions = getAllReportActions(report?.reportID); diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 68dd31754691..73f456b152f6 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -919,6 +919,7 @@ function isDuplicate(transactionID: string | undefined, checkDismissed = false): const duplicateViolation = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.find( (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, ); + const hasDuplicatedViolation = !!duplicateViolation; if (!checkDismissed) { return hasDuplicatedViolation; } @@ -1488,6 +1489,7 @@ export { getFormattedPostedDate, getCategoryTaxCodeAndAmount, isPerDiemRequest, + isViolationDismissed, }; export type {TransactionChanges}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 9f61f040bb57..18102c3dbc02 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3932,7 +3932,7 @@ function updateMoneyRequestAttendees( policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, - violations: OnyxEntry, + violations: OnyxEntry | undefined, ) { const transactionChanges: TransactionChanges = { attendees, @@ -8130,7 +8130,7 @@ function canSubmitReport( report: OnyxEntry | SearchReport, policy: OnyxEntry | SearchPolicy, transactions: OnyxTypes.Transaction[] | SearchTransaction[], - allViolations?: OnyxCollection, + allViolations: OnyxCollection | undefined, ) { const currentUserAccountID = getCurrentUserAccountID(); const isOpenExpenseReport = isOpenExpenseReportReportUtils(report); diff --git a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx index d72c176ff1e9..e3d3f518f88a 100644 --- a/src/pages/Debug/Transaction/DebugTransactionViolations.tsx +++ b/src/pages/Debug/Transaction/DebugTransactionViolations.tsx @@ -1,5 +1,4 @@ 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'; diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx index 8369f8352166..a3fb7f71fd54 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx @@ -1,6 +1,5 @@ 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'; diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx index 69380bff53be..89825f8e91ba 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx @@ -1,6 +1,5 @@ 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'; diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx index 54f8830c17ca..66f240d6fbca 100644 --- a/src/pages/TransactionDuplicate/Review.tsx +++ b/src/pages/TransactionDuplicate/Review.tsx @@ -36,16 +36,16 @@ function TransactionDuplicateReview() { () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], [transactionViolations], ); - const transactionIDs = [transactionID, ...duplicateTransactionIDs]; + const transactionIDs = transactionID ? [transactionID, ...duplicateTransactionIDs] : duplicateTransactionIDs; - const transactions = transactionIDs.map((item) => TransactionUtils.getTransaction(item)).sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()); + const transactions = transactionIDs.map((item) => getTransaction(item)).sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()); const keepAll = () => { - Transaction.dismissDuplicateTransactionViolation(transactionIDs, currentPersonalDetails); + dismissDuplicateTransactionViolation(transactionIDs, currentPersonalDetails); Navigation.goBack(); }; - const hasSettledOrApprovedTransaction = transactions.some((transaction) => ReportUtils.isSettled(transaction?.reportID) || ReportUtils.isReportIDApproved(transaction?.reportID)); + const hasSettledOrApprovedTransaction = transactions.some((transaction) => isSettled(transaction?.reportID) || isReportIDApproved(transaction?.reportID)); return ( diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index 7bca05836bfd..e15b77ca4f9c 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -5,7 +5,7 @@ import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useTransactionViolations from '@hooks/useTransactionViolations'; -import {setMoneyRequestAttendees, updateMoneyRequestAttendees} from '@libs/actions/IOU'; +import {updateMoneyRequestAttendees} from '@libs/actions/IOU'; import Navigation from '@libs/Navigation/Navigation'; import {getAttendees} from '@libs/TransactionUtils'; import MoneyRequestAttendeeSelector from '@pages/iou/request/MoneyRequestAttendeeSelector'; @@ -41,8 +41,8 @@ function IOURequestStepAttendees({ policyCategories, }: IOURequestStepAttendeesProps) { const isEditing = action === CONST.IOU.ACTION.EDIT; - const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID || -1}`); - const [attendees, setAttendees] = useState(() => TransactionUtils.getAttendees(transaction)); + const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID || CONST.DEFAULT_NUMBER_ID}`); + const [attendees, setAttendees] = useState(() => getAttendees(transaction)); const previousAttendees = usePrevious(attendees); const {translate} = useLocalize(); const transactionViolations = useTransactionViolations(transactionID); diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index afb9538ff3ec..6190fd55a829 100644 --- a/src/types/onyx/Transaction.ts +++ b/src/types/onyx/Transaction.ts @@ -82,7 +82,7 @@ type Comment = { splits?: Split[]; /** Violations that were dismissed */ - dismissedViolations?: Record>; + dismissedViolations?: Partial>>; }; /** Model of transaction custom unit */ diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index 96696b23cdc8..8670657a85ef 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -1,13 +1,13 @@ import {beforeEach} from '@jest/globals'; import Onyx from 'react-native-onyx'; import {convertAmountToDisplayString} from '@libs/CurrencyUtils'; +import {getTransactionViolations, hasWarningTypeViolation, isViolationDismissed} from '@libs/TransactionUtils'; import ViolationsUtils from '@libs/Violations/ViolationsUtils'; 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'; - const categoryOutOfPolicyViolation = { name: CONST.VIOLATIONS.CATEGORY_OUT_OF_POLICY, type: CONST.VIOLATION_TYPES.VIOLATION, @@ -58,6 +58,16 @@ const tagOutOfPolicyViolation = { type: CONST.VIOLATION_TYPES.VIOLATION, }; +const smartScanFailedViolation = { + name: CONST.VIOLATIONS.SMARTSCAN_FAILED, + type: CONST.VIOLATION_TYPES.WARNING, +}; + +const duplicatedTransactionViolation = { + name: CONST.VIOLATIONS.DUPLICATED_TRANSACTION, + type: CONST.VIOLATION_TYPES.WARNING, +}; + describe('getViolationsOnyxData', () => { let transaction: Transaction; let transactionViolations: TransactionViolation[]; @@ -500,4 +510,3 @@ describe('getViolations', () => { expect(hasWarningTypeViolationRes).toBeFalsy(); }); }); - From b99c0d90f8d6b0d19bf773f6fb532d5e00a21997 Mon Sep 17 00:00:00 2001 From: krishna2323 Date: Wed, 12 Feb 2025 15:38:41 +0530 Subject: [PATCH 10/10] fix TS check. Signed-off-by: krishna2323 --- .../DebugTransactionViolationCreatePage.tsx | 4 ++-- .../TransactionViolation/DebugTransactionViolationPage.tsx | 4 ++-- src/pages/iou/request/step/IOURequestStepAttendees.tsx | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx index a3fb7f71fd54..93c6d26597dc 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx @@ -10,7 +10,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import useTransactionViolations from '@hooks/useTransactionViolations'; import DebugUtils from '@libs/DebugUtils'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackScreenProps} from '@libs/Navigation/PlatformStackNavigation/types'; import type {DebugParamList} from '@libs/Navigation/types'; @@ -95,7 +95,7 @@ function DebugTransactionViolationCreatePage({ {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx index 89825f8e91ba..f062195e807d 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx @@ -7,7 +7,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useTransactionViolations from '@hooks/useTransactionViolations'; import Debug from '@libs/actions/Debug'; import DebugUtils from '@libs/DebugUtils'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; +import {canUseTouchScreen} from '@libs/DeviceCapabilities'; import type {DebugTabNavigatorRoutes} from '@libs/Navigation/DebugTabNavigator'; import DebugTabNavigator from '@libs/Navigation/DebugTabNavigator'; import Navigation from '@libs/Navigation/Navigation'; @@ -85,7 +85,7 @@ function DebugTransactionViolationPage({ {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index e15b77ca4f9c..6e9f674d7a79 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -5,11 +5,10 @@ import type {OnyxEntry} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import usePrevious from '@hooks/usePrevious'; import useTransactionViolations from '@hooks/useTransactionViolations'; -import {updateMoneyRequestAttendees} from '@libs/actions/IOU'; +import {setMoneyRequestAttendees, updateMoneyRequestAttendees} from '@libs/actions/IOU'; import Navigation from '@libs/Navigation/Navigation'; import {getAttendees} from '@libs/TransactionUtils'; import MoneyRequestAttendeeSelector from '@pages/iou/request/MoneyRequestAttendeeSelector'; -import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -52,7 +51,7 @@ function IOURequestStepAttendees({ return; } if (!lodashIsEqual(previousAttendees, attendees)) { - IOU.setMoneyRequestAttendees(transactionID, attendees, !isEditing); + setMoneyRequestAttendees(transactionID, attendees, !isEditing); if (isEditing) { updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, transactionViolations ?? undefined); }