diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx index ed5ecf41078a..078cbed25631 100644 --- a/src/components/BrokenConnectionDescription.tsx +++ b/src/components/BrokenConnectionDescription.tsx @@ -1,12 +1,13 @@ 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 {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'; @@ -14,6 +15,7 @@ import TextLink from './TextLink'; type BrokenConnectionDescriptionProps = { /** Transaction id of the corresponding report */ transactionID: string | undefined; + /** Current report */ report: OnyxEntry; @@ -24,7 +26,7 @@ type BrokenConnectionDescriptionProps = { function BrokenConnectionDescription({transactionID, policy, report}: BrokenConnectionDescriptionProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); - const transactionViolations = getTransactionViolations(transactionID); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID ?? CONST.DEFAULT_NUMBER_ID}`); 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); @@ -44,12 +46,7 @@ function BrokenConnectionDescription({transactionID, policy, report}: BrokenConn {`${translate('violations.adminBrokenConnectionError')}`} { - if (!policy?.id) { - return; - } - Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy?.id)); - }} + onPress={() => Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy?.id))} >{`${translate('workspace.common.companyCards')}`} . diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 0d0e862f36f5..268ab770059e 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -8,7 +8,6 @@ import useLocalize from '@hooks/useLocalize'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {markAsCash as markAsCashUtil} from '@libs/actions/Transaction'; import Navigation from '@libs/Navigation/Navigation'; import {isPolicyAdmin} from '@libs/PolicyUtils'; import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils'; @@ -26,6 +25,7 @@ import { shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils, } from '@libs/TransactionUtils'; import variables from '@styles/variables'; +import {markAsCash as markAsCashAction} from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; @@ -61,12 +61,13 @@ 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.DEFAULT_NUMBER_ID}`); + const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`); const [transaction] = useOnyx( `${ONYXKEYS.COLLECTION.TRANSACTION}${ isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID }`, ); + const [transactionViolations] = 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); @@ -87,7 +88,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const shouldShowMarkAsCashButton = hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID))); const markAsCash = useCallback(() => { - markAsCashUtil(transaction?.transactionID, reportID); + markAsCashAction(transaction?.transactionID, reportID); }, [reportID, transaction?.transactionID]); const isScanning = hasReceipt(transaction) && isReceiptBeingScanned(transaction); @@ -121,7 +122,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre ), }; } - if (hasPendingRTERViolation(getTransactionViolations(transaction?.transactionID))) { + if (hasPendingRTERViolation(getTransactionViolations(transaction?.transactionID, 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 c70bda657e67..e20fe09058e1 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx +++ b/src/components/ReportActionItem/MoneyRequestPreview/MoneyRequestPreviewContent.tsx @@ -112,8 +112,7 @@ 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 transactionViolations = getTransactionViolations(transaction?.transactionID); + const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS); const sessionAccountID = session?.accountID; const managerID = iouReport?.managerID ?? CONST.DEFAULT_NUMBER_ID; @@ -146,9 +145,9 @@ function MoneyRequestPreviewContent({ const isOnHold = isOnHoldTransactionUtils(transaction); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; const isPartialHold = isSettlementOrApprovalPartial && isOnHold; - const hasViolations = hasViolationTransactionUtils(transaction?.transactionID, allViolations, true); - const hasNoticeTypeViolations = hasNoticeTypeViolationTransactionUtils(transaction?.transactionID, allViolations, true) && isPaidGroupPolicy(iouReport); - const hasWarningTypeViolations = hasWarningTypeViolationTransactionUtils(transaction?.transactionID, allViolations, true); + const hasViolations = hasViolationTransactionUtils(transaction?.transactionID, transactionViolations, true); + const hasNoticeTypeViolations = hasNoticeTypeViolationTransactionUtils(transaction?.transactionID, transactionViolations, true) && isPaidGroupPolicy(iouReport); + const hasWarningTypeViolations = hasWarningTypeViolationTransactionUtils(transaction?.transactionID, transactionViolations, true); const hasFieldErrors = hasMissingSmartscanFields(transaction); const isDistanceRequest = isDistanceRequestTransactionUtils(transaction); const isPerDiemRequest = isPerDiemRequestTransactionUtils(transaction); @@ -164,8 +163,11 @@ function MoneyRequestPreviewContent({ // Get transaction violations for given transaction id from onyx, find duplicated transactions violations and get duplicates const allDuplicates = useMemo( - () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], - [transactionViolations], + () => + transactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction?.transactionID}`]?.find( + (violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, + )?.data?.duplicates ?? [], + [transaction?.transactionID, transactionViolations], ); // Remove settled transactions from duplicates @@ -238,13 +240,14 @@ function MoneyRequestPreviewContent({ } if (shouldShowRBR && transaction) { + const violations = getTransactionViolations(transaction.transactionID, transactionViolations); if (shouldShowHoldMessage) { return `${message} ${CONST.DOT_SEPARATOR} ${translate('violations.hold')}`; } - const firstViolation = transactionViolations?.at(0); + const firstViolation = violations?.at(0); if (firstViolation) { const violationMessage = ViolationsUtils.getViolationTranslation(firstViolation, translate); - const violationsCount = transactionViolations?.filter((v) => v.type === CONST.VIOLATION_TYPES.VIOLATION).length ?? 0; + const violationsCount = violations?.filter((v) => v.type === CONST.VIOLATION_TYPES.VIOLATION).length ?? 0; const isTooLong = violationsCount > 1 || violationMessage.length > 15; const hasViolationsAndFieldErrors = violationsCount > 0 && hasFieldErrors; @@ -284,7 +287,7 @@ function MoneyRequestPreviewContent({ if (shouldShowBrokenConnectionViolation(transaction ? [transaction.transactionID] : [], iouReport, policy)) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('violations.brokenConnection530Error')}; } - if (hasPendingUI(transaction, transactionViolations)) { + if (hasPendingUI(transaction, getTransactionViolations(transaction?.transactionID, transactionViolations))) { return {shouldShow: true, messageIcon: Expensicons.Hourglass, messageDescription: translate('iou.pendingMatchWithCreditCard')}; } return {shouldShow: false}; diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 54f3a8b6907f..10c72dbd8841 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -49,7 +49,6 @@ import { getDistanceInMeters, getTagForDisplay, getTaxName, - getTransactionViolations, hasMissingSmartscanFields, hasReceipt as hasReceiptTransactionUtils, hasReservationList, @@ -134,7 +133,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 transactionViolations = getTransactionViolations(linkedTransactionID); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${linkedTransactionID ?? CONST.DEFAULT_NUMBER_ID}`); const { created: transactionDate, @@ -699,7 +698,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals {shouldShowTax && ( ({...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, transactionViolations)); 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, transactionViolations); const shouldDisableSubmitButton = shouldShowSubmitButton && !isAllowedToSubmitDraftExpenseReport(iouReport); diff --git a/src/libs/ReportActionsUtils.ts b/src/libs/ReportActionsUtils.ts index b959479d8e63..e6f9810ab336 100644 --- a/src/libs/ReportActionsUtils.ts +++ b/src/libs/ReportActionsUtils.ts @@ -1614,9 +1614,9 @@ function wasActionTakenByCurrentUser(reportAction: OnyxInputOrEntry { - if (!reportID || !transactionID) { - return undefined; +function getIOUActionForReportID(reportID: string | undefined, transactionID: string): OnyxEntry { + if (!reportID) { + return; } const report = allReports?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`]; const reportActions = getAllReportActions(report?.reportID); diff --git a/src/libs/SearchUIUtils.ts b/src/libs/SearchUIUtils.ts index 51f8e9428bdb..f47a0d56001d 100644 --- a/src/libs/SearchUIUtils.ts +++ b/src/libs/SearchUIUtils.ts @@ -340,7 +340,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 474329bd6951..a8ffc240aa6f 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -766,11 +766,8 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry): /** * Get all transaction violations of the transaction with given tranactionID. */ -function getTransactionViolations(transactionID: string | undefined): TransactionViolations | null { - if (!transactionID) { - return null; - } - return allTransactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation) => !isViolationDismissed(transactionID, violation)) ?? null; +function getTransactionViolations(transactionID: string | undefined, transactionViolations: OnyxCollection | null): TransactionViolations | null { + return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID] ?? null; } /** @@ -789,8 +786,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 +798,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 +815,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); @@ -906,20 +908,14 @@ 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) { - return false; - } - const duplicateViolation = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.find( + const hasDuplicatedViolation = !!allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.some( (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, ); - - const hasDuplicatedViolation = !!duplicateViolation; if (!checkDismissed) { - return !!duplicateViolation; + return hasDuplicatedViolation; } - - const didDismissedViolation = isViolationDismissed(transactionID, duplicateViolation); - + const didDismissedViolation = + allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.comment?.dismissedViolations?.duplicatedTransaction?.[currentUserEmail] === `${currentUserAccountID}`; return hasDuplicatedViolation && !didDismissedViolation; } @@ -945,32 +941,16 @@ function isOnHoldByTransactionID(transactionID: string | undefined | null): bool return isOnHold(allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]); } -/** - * Checks if a violation is dismissed for the given transaction - */ -function isViolationDismissed(transactionID: string | undefined, violation: TransactionViolation | undefined): boolean { - if (!transactionID || !violation) { - return false; - } - return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.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) { - return false; - } const transaction = getTransaction(transactionID); if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { return false; } return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => - violation.type === CONST.VIOLATION_TYPES.VIOLATION && - (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transactionID, violation), + (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.VIOLATION && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), ); } @@ -978,18 +958,12 @@ 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) { - return false; - } const transaction = getTransaction(transactionID); if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { return false; } return !!transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.some( - (violation: TransactionViolation) => - violation.type === CONST.VIOLATION_TYPES.NOTICE && - (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transactionID, violation), + (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.NOTICE && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), ); } @@ -997,9 +971,6 @@ function hasNoticeTypeViolation(transactionID: string | undefined, transactionVi * Checks if any violations for the provided transaction are of type 'warning' */ function hasWarningTypeViolation(transactionID: string | undefined, transactionViolations: OnyxCollection, showInReview?: boolean): boolean { - if (!transactionID) { - return false; - } const transaction = getTransaction(transactionID); if (isExpensifyCardTransaction(transaction) && isPending(transaction)) { return false; @@ -1007,10 +978,7 @@ function hasWarningTypeViolation(transactionID: string | undefined, transactionV const violations = transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]; const warningTypeViolations = violations?.filter( - (violation: TransactionViolation) => - violation.type === CONST.VIOLATION_TYPES.WARNING && - (showInReview === undefined || showInReview === (violation.showInReview ?? false)) && - !isViolationDismissed(transactionID, violation), + (violation: TransactionViolation) => violation.type === CONST.VIOLATION_TYPES.WARNING && (showInReview === undefined || showInReview === (violation.showInReview ?? false)), ) ?? []; const hasOnlyDupeDetectionViolation = warningTypeViolations?.every((violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION); @@ -1483,7 +1451,6 @@ export { getFormattedPostedDate, getCategoryTaxCodeAndAmount, isPerDiemRequest, - isViolationDismissed, }; export type {TransactionChanges}; diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 731f8751f6d3..143f5f14bcfe 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -3854,7 +3854,7 @@ function updateMoneyRequestAttendees( policy: OnyxEntry, policyTagList: OnyxEntry, policyCategories: OnyxEntry, - violations?: OnyxEntry, + violations: OnyxEntry, ) { const transactionChanges: TransactionChanges = { attendees, @@ -8011,14 +8011,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..e13fd01fdcd7 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'; @@ -6,7 +7,7 @@ import Text from '@components/Text'; 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,8 +17,7 @@ type DebugTransactionViolationsProps = { }; function DebugTransactionViolations({transactionID}: DebugTransactionViolationsProps) { - const transactionViolations = getTransactionViolations(transactionID); - + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); const styles = useThemeStyles(); const {translate} = useLocalize(); diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationCreatePage.tsx index 452925da8b86..b5f3d0d603d5 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'; @@ -9,11 +10,10 @@ import TextInput from '@components/TextInput'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import DebugUtils from '@libs/DebugUtils'; -import {canUseTouchScreen} from '@libs/DeviceCapabilities'; +import * as DeviceCapabilities 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'; @@ -62,7 +62,7 @@ function DebugTransactionViolationCreatePage({ }: DebugTransactionViolationCreatePageProps) { const {translate} = useLocalize(); const styles = useThemeStyles(); - const transactionViolations = getTransactionViolations(transactionID); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); const [draftTransactionViolation, setDraftTransactionViolation] = useState(() => getInitialTransactionViolation()); const [error, setError] = useState(); @@ -95,7 +95,7 @@ function DebugTransactionViolationCreatePage({ {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx index 1b26b0c5f72a..9db84c341d59 100644 --- a/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx +++ b/src/pages/Debug/TransactionViolation/DebugTransactionViolationPage.tsx @@ -1,18 +1,18 @@ 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 Debug from '@libs/actions/Debug'; import DebugUtils from '@libs/DebugUtils'; -import {canUseTouchScreen} from '@libs/DeviceCapabilities'; +import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import type {DebugTabNavigatorRoutes} from '@libs/Navigation/DebugTabNavigator'; 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'; @@ -29,7 +29,7 @@ function DebugTransactionViolationPage({ }, }: DebugTransactionViolationPageProps) { const {translate} = useLocalize(); - const transactionViolations = getTransactionViolations(transactionID); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); const transactionViolation = useMemo(() => transactionViolations?.[Number(index)], [index, transactionViolations]); const styles = useThemeStyles(); @@ -84,7 +84,7 @@ function DebugTransactionViolationPage({ {({safeAreaPaddingBottomStyle}) => ( diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx index 883b7e2d8031..cb27ecfcbb3c 100644 --- a/src/pages/TransactionDuplicate/Review.tsx +++ b/src/pages/TransactionDuplicate/Review.tsx @@ -10,13 +10,13 @@ import Text from '@components/Text'; import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -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 {isReportApproved, isSettled} from '@libs/ReportUtils'; -import {getTransaction, getTransactionViolations} from '@libs/TransactionUtils'; +import * as ReportActionsUtils from '@libs/ReportActionsUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; +import * as Transaction from '@userActions/Transaction'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -28,24 +28,23 @@ function TransactionDuplicateReview() { const route = useRoute>(); const currentPersonalDetails = useCurrentUserPersonalDetails(); const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); - const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); - const transactionID = getLinkedTransactionID(reportAction, report?.reportID) ?? undefined; - const transactionViolations = getTransactionViolations(transactionID); - + const reportAction = ReportActionsUtils.getReportAction(report?.parentReportID ?? '-1', report?.parentReportActionID ?? '-1'); + const transactionID = ReportActionsUtils.getLinkedTransactionID(reportAction, report?.reportID ?? '-1') ?? '-1'; + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); const duplicateTransactionIDs = useMemo( () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], [transactionViolations], ); - const transactionIDs = transactionID ? [transactionID, ...duplicateTransactionIDs] : [...duplicateTransactionIDs]; + const transactionIDs = [transactionID, ...duplicateTransactionIDs]; - const transactions = transactionIDs.map((item) => getTransaction(item)).sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()); + const transactions = transactionIDs.map((item) => TransactionUtils.getTransaction(item)).sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()); const keepAll = () => { - dismissDuplicateTransactionViolation(transactionIDs, currentPersonalDetails); + Transaction.dismissDuplicateTransactionViolation(transactionIDs, currentPersonalDetails); Navigation.goBack(); }; - const hasSettledOrApprovedTransaction = transactions.some((transaction) => isSettled(transaction?.reportID) || isReportApproved(transaction?.reportID)); + const hasSettledOrApprovedTransaction = transactions.some((transaction) => ReportUtils.isSettled(transaction?.reportID) || ReportUtils.isReportApproved(transaction?.reportID)); return ( diff --git a/src/pages/iou/request/step/IOURequestStepAttendees.tsx b/src/pages/iou/request/step/IOURequestStepAttendees.tsx index 2e64e55fb8a1..409ef1cfe02d 100644 --- a/src/pages/iou/request/step/IOURequestStepAttendees.tsx +++ b/src/pages/iou/request/step/IOURequestStepAttendees.tsx @@ -4,10 +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 {setMoneyRequestAttendees, updateMoneyRequestAttendees} from '@libs/actions/IOU'; import Navigation from '@libs/Navigation/Navigation'; -import {getAttendees, getTransactionViolations} from '@libs/TransactionUtils'; +import * as TransactionUtils 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'; @@ -39,20 +39,20 @@ function IOURequestStepAttendees({ policyCategories, }: IOURequestStepAttendeesProps) { const isEditing = action === CONST.IOU.ACTION.EDIT; - const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID || CONST.DEFAULT_NUMBER_ID}`); - const [attendees, setAttendees] = useState(() => getAttendees(transaction)); + const [transaction] = useOnyx(`${isEditing ? ONYXKEYS.COLLECTION.TRANSACTION : ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID || -1}`); + const [attendees, setAttendees] = useState(() => TransactionUtils.getAttendees(transaction)); const previousAttendees = usePrevious(attendees); const {translate} = useLocalize(); - const violations = getTransactionViolations(transactionID); + const [violations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`); const saveAttendees = useCallback(() => { if (attendees.length <= 0) { return; } if (!lodashIsEqual(previousAttendees, attendees)) { - setMoneyRequestAttendees(transactionID, attendees, !isEditing); + IOU.setMoneyRequestAttendees(transactionID, attendees, !isEditing); if (isEditing) { - updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, violations ?? undefined); + IOU.updateMoneyRequestAttendees(transactionID, reportID, attendees, policy, policyTags, policyCategories, violations); } } diff --git a/src/types/onyx/Transaction.ts b/src/types/onyx/Transaction.ts index a573bce74e27..968b9a4dea4b 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?: Partial>>; + dismissedViolations?: Record>; }; /** Model of transaction custom unit */ diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index b161ea8fe40c..659240cc0c30 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -1,12 +1,9 @@ import {beforeEach} from '@jest/globals'; import Onyx from 'react-native-onyx'; -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'; -import type {TransactionViolationsCollectionDataSet} from '@src/types/onyx/TransactionViolation'; const categoryOutOfPolicyViolation = { name: CONST.VIOLATIONS.CATEGORY_OUT_OF_POLICY, @@ -33,16 +30,6 @@ 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[]; @@ -362,91 +349,3 @@ describe('getViolationsOnyxData', () => { }); }); }); - -const getFakeTransaction = (transactionID: string, comment?: Transaction['comment']) => ({ - transactionID, - attendees: [{email: 'text@expensify.com'}], - reportID: '1234', - amount: 100, - comment: comment ?? {}, - created: '2023-07-24 13:46:20', - merchant: 'United Airlines', - currency: 'USD', -}); - -const CARLOS_EMAIL = 'cmartins@expensifail.com'; -const CARLOS_ACCOUNT_ID = 1; - -describe('getViolations', () => { - beforeAll(() => { - Onyx.init({ - keys: ONYXKEYS, - initialKeyStates: { - [ONYXKEYS.SESSION]: { - email: CARLOS_EMAIL, - accountID: CARLOS_ACCOUNT_ID, - }, - }, - }); - }); - - afterEach(() => Onyx.clear()); - - it('should check if violation is dismissed or not', async () => { - const transaction = getFakeTransaction('123', { - dismissedViolations: {smartscanFailed: {[CARLOS_EMAIL]: CARLOS_ACCOUNT_ID.toString()}}, - }); - - const transactionCollectionDataSet: TransactionCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: transaction, - }; - - await Onyx.multiSet({...transactionCollectionDataSet}); - - const isSmartScanDismissed = isViolationDismissed(transaction.transactionID, smartScanFailedViolation); - const isDuplicateViolationDismissed = isViolationDismissed(transaction.transactionID, duplicatedTransactionViolation); - - expect(isSmartScanDismissed).toBeTruthy(); - expect(isDuplicateViolationDismissed).toBeFalsy(); - }); - - it('should return filtered out dismissed violations', async () => { - const transaction = getFakeTransaction('123', { - dismissedViolations: {smartscanFailed: {[CARLOS_EMAIL]: CARLOS_ACCOUNT_ID.toString()}}, - }); - - const transactionCollectionDataSet: TransactionCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: transaction, - }; - - const transactionViolationsCollection: TransactionViolationsCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`]: [duplicatedTransactionViolation, smartScanFailedViolation, tagOutOfPolicyViolation], - }; - - await Onyx.multiSet({...transactionCollectionDataSet, ...transactionViolationsCollection}); - - // Should filter out the smartScanFailedViolation - const filteredViolations = getTransactionViolations(transaction.transactionID); - expect(filteredViolations).toEqual([duplicatedTransactionViolation, tagOutOfPolicyViolation]); - }); - - it('checks if transaction has warning type violation after filtering dismissed violations', async () => { - const transaction = getFakeTransaction('123', { - dismissedViolations: {smartscanFailed: {[CARLOS_EMAIL]: CARLOS_ACCOUNT_ID.toString()}}, - }); - - const transactionCollectionDataSet: TransactionCollectionDataSet = { - [`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`]: transaction, - }; - - const transactionViolationsCollection = { - [`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`]: [duplicatedTransactionViolation, smartScanFailedViolation, tagOutOfPolicyViolation], - }; - - await Onyx.multiSet({...transactionCollectionDataSet}); - - // Should filter out the smartScanFailedViolation and return true, duplicatedTransactionViolation is a warning type violation but it's not considered in hasWarningTypeViolation - const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction.transactionID, transactionViolationsCollection); - expect(hasWarningTypeViolationRes).toBeFalsy(); - }); -});