diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 602cefbbb246..67bcf2d206d6 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -231,7 +231,7 @@ function MoneyReportHeader({ }, [reportPDFFilename, translate]); // Check if there is pending rter violation in all transactionViolations with given transactionIDs. - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs, violations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, violations); // Check if user should see broken connection violation warning. const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationForMultipleTransactions(transactionIDs, moneyRequestReport, policy, violations); const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(moneyRequestReport?.reportID); @@ -401,7 +401,7 @@ function MoneyReportHeader({ }; const getFirstDuplicateThreadID = (reportTransactions: OnyxTypes.Transaction[], allReportActions: OnyxTypes.ReportAction[]) => { - const duplicateTransaction = reportTransactions.find((reportTransaction) => isDuplicate(reportTransaction.transactionID)); + const duplicateTransaction = reportTransactions.find((reportTransaction) => isDuplicate(reportTransaction)); if (!duplicateTransaction) { return null; } diff --git a/src/components/MoneyRequestHeader.tsx b/src/components/MoneyRequestHeader.tsx index 9b5a8485268d..aca313f055fd 100644 --- a/src/components/MoneyRequestHeader.tsx +++ b/src/components/MoneyRequestHeader.tsx @@ -89,7 +89,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre const theme = useTheme(); const {translate} = useLocalize(); const isOnHold = isOnHoldTransactionUtils(transaction); - const isDuplicate = isDuplicateTransactionUtils(transaction?.transactionID); + const isDuplicate = isDuplicateTransactionUtils(transaction); const reportID = report?.reportID; const {removeTransaction} = useSearchContext(); diff --git a/src/components/ReportActionItem/MoneyRequestView.tsx b/src/components/ReportActionItem/MoneyRequestView.tsx index 2346cda1150d..dff5824387dc 100644 --- a/src/components/ReportActionItem/MoneyRequestView.tsx +++ b/src/components/ReportActionItem/MoneyRequestView.tsx @@ -540,7 +540,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals clearAllRelatedReportActionErrors(report.reportID, parentReportAction); return; } - revert(transaction?.transactionID ?? linkedTransactionID, getLastModifiedExpense(report?.reportID)); + revert(transaction, getLastModifiedExpense(report?.reportID)); clearError(transaction.transactionID); clearAllRelatedReportActionErrors(report.reportID, parentReportAction); }, [transaction, chatReport, parentReportAction, linkedTransactionID, report?.reportID]); diff --git a/src/components/ReportActionItem/TransactionPreview/index.tsx b/src/components/ReportActionItem/TransactionPreview/index.tsx index 48d4baad8c27..aaf7df797ef6 100644 --- a/src/components/ReportActionItem/TransactionPreview/index.tsx +++ b/src/components/ReportActionItem/TransactionPreview/index.tsx @@ -9,11 +9,11 @@ import useTransactionViolations from '@hooks/useTransactionViolations'; import ControlSelection from '@libs/ControlSelection'; import {convertToDisplayString} from '@libs/CurrencyUtils'; import {canUseTouchScreen} from '@libs/DeviceCapabilities'; +import Navigation from '@libs/Navigation/Navigation'; import {getIOUActionForReportID, getOriginalMessage, isMoneyRequestAction as isMoneyRequestActionReportActionsUtils} from '@libs/ReportActionsUtils'; import {getTransactionDetails} from '@libs/ReportUtils'; import {getReviewNavigationRoute} from '@libs/TransactionPreviewUtils'; import {getOriginalTransactionWithSplitInfo, isCardTransaction, removeSettledAndApprovedTransactions} from '@libs/TransactionUtils'; -import Navigation from '@navigation/Navigation'; import type {PlatformStackRouteProp} from '@navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@navigation/types'; import {clearWalletTermsError} from '@userActions/PaymentMethods'; @@ -43,7 +43,6 @@ function TransactionPreview(props: TransactionPreviewProps) { const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`, {canBeMissing: true}); const route = useRoute>(); - const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params?.threadReportID}`, {canBeMissing: true}); const isMoneyRequestAction = isMoneyRequestActionReportActionsUtils(action); const transactionID = transactionIDFromProps ?? (isMoneyRequestAction ? getOriginalMessage(action)?.IOUTransactionID : null); const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true}); @@ -54,10 +53,14 @@ function TransactionPreview(props: TransactionPreviewProps) { const [personalDetails] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {canBeMissing: true}); // Get transaction violations for given transaction id from onyx, find duplicated transactions violations and get duplicates - const allDuplicates = useMemo(() => violations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], [violations]); - const duplicates = useMemo(() => removeSettledAndApprovedTransactions(allDuplicates), [allDuplicates]); + const allDuplicateIDs = useMemo(() => violations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], [violations]); + const [allDuplicates] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}`, { + selector: (allTransactions) => allDuplicateIDs.map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]), + canBeMissing: true, + }); + const duplicates = useMemo(() => removeSettledAndApprovedTransactions(allDuplicates ?? []), [allDuplicates]); const sessionAccountID = session?.accountID; - const areThereDuplicates = allDuplicates.length > 0 && duplicates.length > 0 && allDuplicates.length === duplicates.length; + const areThereDuplicates = allDuplicateIDs.length > 0 && duplicates.length > 0 && allDuplicateIDs.length === duplicates.length; const transactionDetails = useMemo(() => getTransactionDetails(transaction), [transaction]); const {amount: requestAmount, currency: requestCurrency} = transactionDetails ?? {}; @@ -75,8 +78,8 @@ function TransactionPreview(props: TransactionPreviewProps) { }, [chatReportID]); const navigateToReviewFields = useCallback(() => { - Navigation.navigate(getReviewNavigationRoute(route, transactionThreadReport, transaction, duplicates)); - }, [duplicates, transactionThreadReport, route, transaction]); + Navigation.navigate(getReviewNavigationRoute(route, transaction, duplicates)); + }, [route, transaction, duplicates]); let transactionPreview = transaction; diff --git a/src/hooks/useSelectedTransactionsActions.ts b/src/hooks/useSelectedTransactionsActions.ts index f73dcb4a97d0..360978f7a439 100644 --- a/src/hooks/useSelectedTransactionsActions.ts +++ b/src/hooks/useSelectedTransactionsActions.ts @@ -195,9 +195,9 @@ function useSelectedTransactionsActions({ }); } - const canAllSelectedTransactionsBeRemoved = selectedTransactionIDs.every((transactionID) => { - const canRemoveTransaction = canDeleteCardTransactionByLiabilityType(transactionID); - const action = getIOUActionForTransactionID(reportActions, transactionID); + const canAllSelectedTransactionsBeRemoved = Object.values(selectedTransactions).every((transaction) => { + const canRemoveTransaction = canDeleteCardTransactionByLiabilityType(transaction); + const action = getIOUActionForTransactionID(reportActions, transaction.transactionID); const isActionDeleted = isDeletedAction(action); const isIOUActionOwner = typeof action?.actorAccountID === 'number' && typeof session?.accountID === 'number' && action.actorAccountID === session?.accountID; diff --git a/src/libs/ReportPreviewActionUtils.ts b/src/libs/ReportPreviewActionUtils.ts index 67f46be40cc5..d175594bb030 100644 --- a/src/libs/ReportPreviewActionUtils.ts +++ b/src/libs/ReportPreviewActionUtils.ts @@ -222,7 +222,7 @@ function canReview(report: Report, violations: OnyxCollection transaction.transactionID) ?? []; - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs, violations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, violations); const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationForMultipleTransactions(transactionIDs, report, policy, violations); if (hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isAdmin || isSubmitter) && !isReportApproved({report}) && !isReportManuallyReimbursed(report))) { diff --git a/src/libs/ReportPrimaryActionUtils.ts b/src/libs/ReportPrimaryActionUtils.ts index ff5474aa80cc..786e6df47628 100644 --- a/src/libs/ReportPrimaryActionUtils.ts +++ b/src/libs/ReportPrimaryActionUtils.ts @@ -250,7 +250,7 @@ function isRemoveHoldAction(report: Report, chatReport: OnyxEntry, repor } function isReviewDuplicatesAction(report: Report, reportTransactions: Transaction[], policy?: Policy) { - const hasDuplicates = reportTransactions.some((transaction) => isDuplicate(transaction.transactionID)); + const hasDuplicates = reportTransactions.some((transaction) => isDuplicate(transaction)); if (!hasDuplicates) { return false; @@ -279,7 +279,7 @@ function isMarkAsCashAction(report: Report, reportTransactions: Transaction[], v } const transactionIDs = reportTransactions.map((t) => t.transactionID); - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs, violations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(reportTransactions, violations); if (hasAllPendingRTERViolations) { return true; diff --git a/src/libs/ReportSecondaryActionUtils.ts b/src/libs/ReportSecondaryActionUtils.ts index 44e70af76a3d..136eb83d2b8c 100644 --- a/src/libs/ReportSecondaryActionUtils.ts +++ b/src/libs/ReportSecondaryActionUtils.ts @@ -184,7 +184,7 @@ function isApproveAction(report: Report, reportTransactions: Transaction[], viol return false; } const isExpenseReport = isExpenseReportUtils(report); - const reportHasDuplicatedTransactions = reportTransactions.some((transaction) => isDuplicate(transaction.transactionID)); + const reportHasDuplicatedTransactions = reportTransactions.some((transaction) => isDuplicate(transaction)); if (isExpenseReport && isProcessingReport && reportHasDuplicatedTransactions) { return true; @@ -192,7 +192,7 @@ function isApproveAction(report: Report, reportTransactions: Transaction[], viol const transactionIDs = reportTransactions.map((t) => t.transactionID); - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs, violations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(reportTransactions, violations); if (hasAllPendingRTERViolations) { return true; diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index c90103355399..6498d356b4de 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -2485,8 +2485,7 @@ function canDeleteTransaction(moneyRequestReport: OnyxEntry, isReportArc /** * Checks whether the card transaction support deleting based on liability type */ -function canDeleteCardTransactionByLiabilityType(iouTransactionID?: string): boolean { - const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${iouTransactionID}`]; +function canDeleteCardTransactionByLiabilityType(transaction: OnyxEntry): boolean { const isCardTransaction = isCardTransactionTransactionUtils(transaction); if (!isCardTransaction) { return true; @@ -2498,14 +2497,13 @@ function canDeleteCardTransactionByLiabilityType(iouTransactionID?: string): boo * Can only delete if the author is this user and the action is an ADD_COMMENT action or an IOU action in an unsettled report, or if the user is a * policy admin */ -function canDeleteReportAction(reportAction: OnyxInputOrEntry, reportID: string | undefined): boolean { +function canDeleteReportAction(reportAction: OnyxInputOrEntry, reportID: string | undefined, iouTransaction?: OnyxEntry): boolean { const report = getReportOrDraftReport(reportID); const isActionOwner = reportAction?.actorAccountID === currentUserAccountID; const policy = allPolicies?.[`${ONYXKEYS.COLLECTION.POLICY}${report?.policyID}`] ?? null; if (isMoneyRequestAction(reportAction)) { - const iouTransactionID = getOriginalMessage(reportAction)?.IOUTransactionID; - const isCardTransactionCanBeDeleted = canDeleteCardTransactionByLiabilityType(iouTransactionID); + const isCardTransactionCanBeDeleted = canDeleteCardTransactionByLiabilityType(iouTransaction); // For now, users cannot delete split actions const isSplitAction = getOriginalMessage(reportAction)?.type === CONST.IOU.REPORT_ACTION_TYPE.SPLIT; @@ -7945,7 +7943,7 @@ function hasViolations( */ function hasWarningTypeViolations(reportID: string | undefined, transactionViolations: OnyxCollection, shouldShowInReview?: boolean): boolean { const transactions = getReportTransactions(reportID); - return transactions.some((transaction) => hasWarningTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); + return transactions.some((transaction) => hasWarningTypeViolation(transaction, transactionViolations, shouldShowInReview)); } /** @@ -7973,7 +7971,7 @@ function hasReceiptErrors(reportID: string | undefined): boolean { */ function hasNoticeTypeViolations(reportID: string | undefined, transactionViolations: OnyxCollection, shouldShowInReview?: boolean): boolean { const transactions = getReportTransactions(reportID); - return transactions.some((transaction) => hasNoticeTypeViolation(transaction.transactionID, transactionViolations, shouldShowInReview)); + return transactions.some((transaction) => hasNoticeTypeViolation(transaction, transactionViolations, shouldShowInReview)); } function hasReportViolations(reportID: string | undefined) { diff --git a/src/libs/TransactionPreviewUtils.ts b/src/libs/TransactionPreviewUtils.ts index 1f7888a2fd5a..f106d8864c26 100644 --- a/src/libs/TransactionPreviewUtils.ts +++ b/src/libs/TransactionPreviewUtils.ts @@ -10,7 +10,7 @@ import {convertToDisplayString} from './CurrencyUtils'; import DateUtils from './DateUtils'; import type {PlatformStackRouteProp} from './Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from './Navigation/types'; -import {getOriginalMessage, getReportAction, isMessageDeleted, isMoneyRequestAction} from './ReportActionsUtils'; +import {getOriginalMessage, isMessageDeleted, isMoneyRequestAction} from './ReportActionsUtils'; import {hasActionsWithErrors, hasReportViolations, isPaidGroupPolicy, isPaidGroupPolicyExpenseReport, isReportApproved, isReportOwner, isSettled} from './ReportUtils'; import type {TransactionDetails} from './ReportUtils'; import StringUtils from './StringUtils'; @@ -64,20 +64,21 @@ function getIOUPayerAndReceiver(managerID: number, ownerAccountID: number, perso const getReviewNavigationRoute = ( route: PlatformStackRouteProp, - report: OnyxEntry, transaction: OnyxEntry, - duplicates: string[], + duplicates: Array>, ) => { const backTo = route.params.backTo; - const parentReportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); - const reviewingTransactionID = isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID : undefined; - // Clear the draft before selecting a different expense to prevent merging fields from the previous expense // (e.g., category, tag, tax) that may be not enabled/available in the new expense's policy. abandonReviewDuplicateTransactions(); - const comparisonResult = compareDuplicateTransactionFields(reviewingTransactionID, transaction?.reportID, transaction?.transactionID ?? reviewingTransactionID); - setReviewDuplicatesKey({...comparisonResult.keep, duplicates, transactionID: transaction?.transactionID, reportID: transaction?.reportID}); + const comparisonResult = compareDuplicateTransactionFields(transaction, duplicates, transaction?.reportID, transaction?.transactionID); + setReviewDuplicatesKey({ + ...comparisonResult.keep, + duplicates: duplicates.map((duplicate) => duplicate?.transactionID).filter(Boolean) as string[], + transactionID: transaction?.transactionID, + reportID: transaction?.reportID, + }); if (comparisonResult.change.merchant) { return ROUTES.TRANSACTION_DUPLICATE_REVIEW_MERCHANT_PAGE.getRoute(route.params?.threadReportID, backTo); @@ -170,7 +171,7 @@ function getTransactionPreviewTextAndTranslationPaths({ const showCashOrCard: TranslationPathOrText = {translationPath: isTransactionMadeWithCard ? 'iou.card' : 'iou.cash'}; const isTransactionScanning = isScanning(transaction); const hasFieldErrors = hasMissingSmartscanFields(transaction); - const hasViolationsOfTypeNotice = hasNoticeTypeViolation(transaction?.transactionID, violations, true) && isPaidGroupPolicy(iouReport); + const hasViolationsOfTypeNotice = hasNoticeTypeViolation(transaction, violations, true) && isPaidGroupPolicy(iouReport); const hasActionWithErrors = hasActionsWithErrors(iouReport?.reportID); const {amount: requestAmount, currency: requestCurrency} = transactionDetails; @@ -299,7 +300,7 @@ function createTransactionPreviewConditionals({ const isApproved = isReportApproved({report: iouReport}); const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial; - const hasViolationsOfTypeNotice = hasNoticeTypeViolation(transaction?.transactionID, violations, true) && iouReport && isPaidGroupPolicy(iouReport); + const hasViolationsOfTypeNotice = hasNoticeTypeViolation(transaction, violations, true) && iouReport && isPaidGroupPolicy(iouReport); const hasFieldErrors = hasMissingSmartscanFields(transaction); const isFetchingWaypoints = isFetchingWaypointsFromServer(transaction); @@ -317,7 +318,7 @@ function createTransactionPreviewConditionals({ const shouldShowCategory = !!categoryForDisplay && isReportAPolicyExpenseChat; // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - const hasAnyViolations = hasViolationsOfTypeNotice || hasWarningTypeViolation(transaction?.transactionID, violations, true) || hasViolation(transaction, violations, true); + const hasAnyViolations = hasViolationsOfTypeNotice || hasWarningTypeViolation(transaction, violations, true) || hasViolation(transaction, violations, true); const hasErrorOrOnHold = hasFieldErrors || (!isFullySettled && !isFullyApproved && isTransactionOnHold); const hasReportViolationsOrActionErrors = (isReportOwner(iouReport) && hasReportViolations(iouReport?.reportID)) || hasActionsWithErrors(iouReport?.reportID); const shouldShowRBR = hasAnyViolations || hasErrorOrOnHold || hasReportViolationsOrActionErrors; diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index d43de211207c..7041e85ff501 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -873,12 +873,12 @@ function hasMissingSmartscanFields(transaction: OnyxInputOrEntry): /** * Get all transaction violations of the transaction with given transactionID. */ -function getTransactionViolations(transactionID: string | undefined, transactionViolations: OnyxCollection | undefined): TransactionViolations | undefined { - const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; - if (!transactionID || !transactionViolations) { +function getTransactionViolations(transaction: OnyxEntry, transactionViolations: OnyxCollection): TransactionViolations | undefined { + if (!transaction || !transactionViolations) { return undefined; } - return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]?.filter((violation) => !isViolationDismissed(transaction, violation)); + + return transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transaction.transactionID]?.filter((violation) => !isViolationDismissed(transaction, violation)); } /** @@ -897,8 +897,8 @@ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | /** * Check if there is broken connection violation. */ -function hasBrokenConnectionViolation(transactionID: string | undefined, transactionViolations: OnyxCollection | undefined): boolean { - const violations = getTransactionViolations(transactionID, transactionViolations); +function hasBrokenConnectionViolation(transaction: Transaction | SearchTransaction, transactionViolations: OnyxCollection | undefined): boolean { + const violations = getTransactionViolations(transaction, transactionViolations); return !!violations?.find((violation) => isBrokenConnectionViolation(violation)); } @@ -953,9 +953,13 @@ function shouldShowBrokenConnectionViolationForMultipleTransactions( /** * Check if there is pending rter violation in all transactionViolations with given transactionIDs. */ -function allHavePendingRTERViolation(transactionIds: string[], transactionViolations: OnyxCollection | undefined): boolean { - const transactionsWithRTERViolations = transactionIds.map((transactionId) => { - const filteredTransactionViolations = getTransactionViolations(transactionId, transactionViolations); +function allHavePendingRTERViolation(transactions: OnyxEntry, transactionViolations: OnyxCollection | undefined): boolean { + if (!transactions) { + return false; + } + + const transactionsWithRTERViolations = transactions.map((transaction) => { + const filteredTransactionViolations = getTransactionViolations(transaction, transactionViolations); return hasPendingRTERViolation(filteredTransactionViolations); }); return transactionsWithRTERViolations.length > 0 && transactionsWithRTERViolations.every((value) => value === true); @@ -973,11 +977,11 @@ function checkIfShouldShowMarkAsCashButton(hasRTERPendingViolation: boolean, sho /** * Check if there is any transaction without RTER violation within the given transactionIDs. */ -function hasAnyTransactionWithoutRTERViolation(transactionIds: string[], transactionViolations: OnyxCollection | undefined): boolean { +function hasAnyTransactionWithoutRTERViolation(transactions: Transaction[] | SearchTransaction[], transactionViolations: OnyxCollection | undefined): boolean { return ( - transactionIds.length > 0 && - transactionIds.some((transactionId) => { - return !hasBrokenConnectionViolation(transactionId, transactionViolations); + transactions.length > 0 && + transactions.some((transaction) => { + return !hasBrokenConnectionViolation(transaction, transactionViolations); }) ); } @@ -1066,12 +1070,11 @@ function getRecentTransactions(transactions: Record, size = 2): * @param transactionID - the transaction to check * @param checkDismissed - whether to check if the violation has already been dismissed as well */ -function isDuplicate(transactionID: string | undefined, checkDismissed = false): boolean { - const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; +function isDuplicate(transaction: OnyxEntry, checkDismissed = false): boolean { if (!transaction) { return false; } - const duplicateViolation = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`]?.find( + const duplicateViolation = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction.transactionID}`]?.find( (violation: TransactionViolation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION, ); const hasDuplicatedViolation = !!duplicateViolation; @@ -1150,18 +1153,21 @@ function hasDuplicateTransactions(iouReportID?: string, allReportTransactions?: const transactionsByIouReportID = getReportTransactions(iouReportID); const reportTransactions = allReportTransactions ?? transactionsByIouReportID; - return reportTransactions.length > 0 && reportTransactions.some((transaction) => isDuplicate(transaction?.transactionID, true)); + return reportTransactions.length > 0 && reportTransactions.some((transaction) => isDuplicate(transaction, true)); } /** * Checks if any violations for the provided transaction are of type 'notice' */ -function hasNoticeTypeViolation(transactionID: string | undefined, transactionViolations: TransactionViolation[] | OnyxCollection, showInReview?: boolean): boolean { - const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; +function hasNoticeTypeViolation( + transaction: OnyxEntry, + transactionViolations: TransactionViolation[] | OnyxCollection, + showInReview?: boolean, +): boolean { if (!doesTransactionSupportViolations(transaction)) { return false; } - const violations = Array.isArray(transactionViolations) ? transactionViolations : transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]; + const violations = Array.isArray(transactionViolations) ? transactionViolations : transactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction?.transactionID}`]; return !!violations?.some( (violation: TransactionViolation) => @@ -1174,12 +1180,15 @@ function hasNoticeTypeViolation(transactionID: string | undefined, transactionVi /** * Checks if any violations for the provided transaction are of type 'warning' */ -function hasWarningTypeViolation(transactionID: string | undefined, transactionViolations: TransactionViolation[] | OnyxCollection, showInReview?: boolean): boolean { - const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; +function hasWarningTypeViolation( + transaction: OnyxEntry, + transactionViolations: TransactionViolation[] | OnyxCollection, + showInReview?: boolean, +): boolean { if (!doesTransactionSupportViolations(transaction)) { return false; } - const violations = Array.isArray(transactionViolations) ? transactionViolations : transactionViolations?.[ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS + transactionID]; + const violations = Array.isArray(transactionViolations) ? transactionViolations : transactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction?.transactionID}`]; const warningTypeViolations = violations?.filter( (violation: TransactionViolation) => @@ -1298,15 +1307,6 @@ function getTaxName(policy: OnyxEntry, transaction: OnyxEntry taxRate.code === (transaction?.taxCode ?? defaultTaxCode))?.modifiedName; } -/** - * @deprecated Get the data straight from Onyx - */ -// TODO: Will be handled in this PR https://github.com/Expensify/App/pull/62434 -// eslint-disable-next-line deprecation/deprecation -function getTransaction(transactionID: string | number | undefined): OnyxEntry { - return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]; -} - function getTransactionOrDraftTransaction(transactionID: string): OnyxEntry { return allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? allTransactionDrafts?.[`${ONYXKEYS.COLLECTION.TRANSACTION_DRAFT}${transactionID}`]; } @@ -1322,12 +1322,8 @@ type FieldsToChange = { reimbursable?: Array; }; -function removeSettledAndApprovedTransactions(transactionIDs: string[]): string[] { - return transactionIDs.filter( - (transactionID) => - !isSettled(allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.reportID) && - !isReportIDApproved(allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`]?.reportID), - ); +function removeSettledAndApprovedTransactions(transactions: Array>): Transaction[] { + return transactions.filter((transaction) => !!transaction && !isSettled(transaction?.reportID) && !isReportIDApproved(transaction?.reportID)) as Transaction[]; } /** @@ -1349,10 +1345,12 @@ function removeSettledAndApprovedTransactions(transactionIDs: string[]): string[ */ function compareDuplicateTransactionFields( - reviewingTransactionID?: string | undefined, + reviewingTransaction?: OnyxEntry, + duplicates?: Array>, reportID?: string | undefined, selectedTransactionID?: string, ): {keep: Partial; change: FieldsToChange} { + const reviewingTransactionID = reviewingTransaction?.transactionID; if (!reviewingTransactionID || !reportID) { return {change: {}, keep: {}}; } @@ -1364,9 +1362,7 @@ function compareDuplicateTransactionFields( if (!reviewingTransactionID || !reportID) { return {keep, change}; } - const transactionViolations = allTransactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${reviewingTransactionID}`]; - const duplicates = transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? []; - const transactions = removeSettledAndApprovedTransactions([reviewingTransactionID, ...duplicates]).map((item) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${item}`]); + const transactions = removeSettledAndApprovedTransactions([reviewingTransaction, ...(duplicates ?? [])]); const fieldsToCompare: FieldsToCompare = { merchant: ['modifiedMerchant', 'merchant'], @@ -1528,7 +1524,11 @@ function buildNewTransactionAfterReviewingDuplicates(reviewDuplicateTransaction: }; } -function buildMergeDuplicatesParams(reviewDuplicates: OnyxEntry, originalTransaction: Partial): MergeDuplicatesParams { +function buildMergeDuplicatesParams( + reviewDuplicates: OnyxEntry, + duplicatedTransactions: Array>, + originalTransaction: Partial, +): MergeDuplicatesParams { return { amount: -getAmount(originalTransaction as OnyxEntry, true), reportID: originalTransaction?.reportID, @@ -1536,7 +1536,7 @@ function buildMergeDuplicatesParams(reviewDuplicates: OnyxEntry), created: getFormattedCreated(originalTransaction as OnyxEntry), transactionID: reviewDuplicates?.transactionID, - transactionIDList: removeSettledAndApprovedTransactions(reviewDuplicates?.duplicates ?? []), + transactionIDList: removeSettledAndApprovedTransactions(duplicatedTransactions ?? []).map((transaction) => transaction.transactionID), billable: reviewDuplicates?.billable ?? false, reimbursable: reviewDuplicates?.reimbursable ?? false, category: reviewDuplicates?.category ?? '', @@ -1582,7 +1582,7 @@ function getAllSortedTransactions(iouReportID?: string): Array) => { @@ -1714,9 +1714,6 @@ export { hasWarningTypeViolation, isCustomUnitRateIDForP2P, getRateID, - // TODO: Will be handled in this PR https://github.com/Expensify/App/pull/62434 - // eslint-disable-next-line deprecation/deprecation - getTransaction, compareDuplicateTransactionFields, getTransactionID, buildNewTransactionAfterReviewingDuplicates, diff --git a/src/libs/actions/IOU.ts b/src/libs/actions/IOU.ts index 1842a2755c1e..c90facc690c4 100644 --- a/src/libs/actions/IOU.ts +++ b/src/libs/actions/IOU.ts @@ -7797,9 +7797,13 @@ function deleteMoneyRequest(transactionID: string | undefined, reportAction: Ony ]; if (transactionViolations) { - removeSettledAndApprovedTransactions( - transactionViolations.filter((violation) => violation?.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION).flatMap((violation) => violation?.data?.duplicates ?? []), - ).forEach((duplicateID) => { + const duplicates = transactionViolations + .filter((violation) => violation?.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION) + .flatMap((violation) => violation?.data?.duplicates ?? []) + .map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]); + + removeSettledAndApprovedTransactions(duplicates).forEach((duplicate) => { + const duplicateID = duplicate?.transactionID; const duplicateTransactionsViolations = allTransactionViolations[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${duplicateID}`]; if (!duplicateTransactionsViolations) { return; @@ -9209,10 +9213,9 @@ function canSubmitReport( const currentUserAccountID = getCurrentUserAccountID(); const isOpenExpenseReport = isOpenExpenseReportReportUtils(report); const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; - const transactionIDList = transactions.map((transaction) => transaction.transactionID); - const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList, allViolations); + const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, allViolations); const isManualSubmitEnabled = getCorrectedAutoReportingFrequency(policy) === CONST.POLICY.AUTO_REPORTING_FREQUENCIES.MANUAL; - const hasTransactionWithoutRTERViolation = hasAnyTransactionWithoutRTERViolation(transactionIDList, allViolations); + const hasTransactionWithoutRTERViolation = hasAnyTransactionWithoutRTERViolation(transactions, allViolations); const hasOnlyPendingCardOrScanFailTransactions = transactions.length > 0 && transactions.every((t) => isPendingCardOrScanningTransaction(t)); return ( @@ -9414,7 +9417,7 @@ function approveMoneyRequest(expenseReport: OnyxEntry, full?: // Remove duplicates violations if we approve the report if (hasDuplicates) { - const transactions = getReportTransactions(expenseReport.reportID).filter((transaction) => isDuplicate(transaction.transactionID, true)); + const transactions = getReportTransactions(expenseReport.reportID).filter((transaction) => isDuplicate(transaction, true)); if (!full) { transactions.filter((transaction) => !isOnHold(transaction)); } diff --git a/src/libs/actions/Transaction.ts b/src/libs/actions/Transaction.ts index 4e6d9abd963d..a931e9b948d0 100644 --- a/src/libs/actions/Transaction.ts +++ b/src/libs/actions/Transaction.ts @@ -502,15 +502,15 @@ function getLastModifiedExpense(reportID?: string): OriginalMessageModifiedExpen return getOriginalMessage(modifiedExpenseActions.at(-1)); } -function revert(transactionID?: string, originalMessage?: OriginalMessageModifiedExpense | undefined) { - const transaction = allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`] ?? ({} as Transaction); - - if (transaction && originalMessage?.oldAmount && originalMessage.oldCurrency && 'amount' in originalMessage && 'currency' in originalMessage) { - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, { - modifiedAmount: transaction?.amount && transaction?.amount < 0 ? -Math.abs(originalMessage.oldAmount) : originalMessage.oldAmount, - modifiedCurrency: originalMessage.oldCurrency, - }); +function revert(transaction?: OnyxEntry, originalMessage?: OriginalMessageModifiedExpense | undefined) { + if (!transaction || !originalMessage?.oldAmount || !originalMessage.oldCurrency || !('amount' in originalMessage) || !('currency' in originalMessage)) { + return; } + + Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transaction.transactionID}`, { + modifiedAmount: transaction?.amount && transaction?.amount < 0 ? -Math.abs(originalMessage.oldAmount) : originalMessage.oldAmount, + modifiedCurrency: originalMessage.oldCurrency, + }); } function markAsCash(transactionID: string | undefined, transactionThreadReportID: string | undefined) { diff --git a/src/pages/ReportDetailsPage.tsx b/src/pages/ReportDetailsPage.tsx index 33e168ebb6ff..65c7ded59aa5 100644 --- a/src/pages/ReportDetailsPage.tsx +++ b/src/pages/ReportDetailsPage.tsx @@ -275,8 +275,9 @@ function ReportDetailsPage({policy, report, route, reportMetadata}: ReportDetail isTaskModifiable && isTaskActionable; const canDeleteRequest = isActionOwner && (canDeleteTransaction(moneyRequestReport, isMoneyRequestReportArchived) || isSelfDMTrackExpenseReport) && !isDeletedParentAction; - const iouTransactionID = isMoneyRequestAction(requestParentReportAction) ? getOriginalMessage(requestParentReportAction)?.IOUTransactionID : ''; - const isCardTransactionCanBeDeleted = canDeleteCardTransactionByLiabilityType(iouTransactionID); + const iouTransactionID = isMoneyRequestAction(requestParentReportAction) ? getOriginalMessage(requestParentReportAction)?.IOUTransactionID : undefined; + const [iouTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${iouTransactionID}`, {canBeMissing: true}); + const isCardTransactionCanBeDeleted = canDeleteCardTransactionByLiabilityType(iouTransaction); const shouldShowDeleteButton = shouldShowTaskDeleteButton || (canDeleteRequest && isCardTransactionCanBeDeleted); useEffect(() => { if (canDeleteRequest) { diff --git a/src/pages/TransactionDuplicate/Confirmation.tsx b/src/pages/TransactionDuplicate/Confirmation.tsx index 4f03a6228b27..58c6ac265725 100644 --- a/src/pages/TransactionDuplicate/Confirmation.tsx +++ b/src/pages/TransactionDuplicate/Confirmation.tsx @@ -21,6 +21,7 @@ import Navigation from '@libs/Navigation/Navigation'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import variables from '@styles/variables'; +import CONST from '@src/CONST'; import * as IOU from '@src/libs/actions/IOU'; import * as ReportActionsUtils from '@src/libs/ReportActionsUtils'; import * as ReportUtils from '@src/libs/ReportUtils'; @@ -38,18 +39,37 @@ function Confirmation() { const route = useRoute>(); const currentUserPersonalDetails = useCurrentUserPersonalDetails(); const [reviewDuplicates, reviewDuplicatesResult] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES, {canBeMissing: true}); - const transaction = useMemo(() => TransactionUtils.buildNewTransactionAfterReviewingDuplicates(reviewDuplicates), [reviewDuplicates]); + const newTransaction = useMemo(() => TransactionUtils.buildNewTransactionAfterReviewingDuplicates(reviewDuplicates), [reviewDuplicates]); const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true}); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, { + canBeMissing: false, + }); + const allDuplicateIDs = useMemo( + () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], + [transactionViolations], + ); + const [allDuplicates] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}`, { + selector: (allTransactions) => allDuplicateIDs.map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]), + canBeMissing: true, + }); + + const compareResult = TransactionUtils.compareDuplicateTransactionFields(transaction, allDuplicates, reviewDuplicates?.reportID); const {goBack} = useReviewDuplicatesNavigation(Object.keys(compareResult.change ?? {}), 'confirmation', route.params.threadReportID, route.params.backTo); const [report, reportResult] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, {canBeMissing: true}); - const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transaction?.reportID}`, {canBeMissing: true}); - const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${transaction?.reportID}`, {canBeMissing: true}); + const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${newTransaction?.reportID}`, {canBeMissing: true}); + const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${newTransaction?.reportID}`, {canBeMissing: true}); const reportAction = Object.values(reportActions ?? {}).find( (action) => ReportActionsUtils.isMoneyRequestAction(action) && ReportActionsUtils.getOriginalMessage(action)?.IOUTransactionID === reviewDuplicates?.transactionID, ); - - const transactionsMergeParams = useMemo(() => TransactionUtils.buildMergeDuplicatesParams(reviewDuplicates, transaction), [reviewDuplicates, transaction]); + const [duplicates] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}`, { + selector: (allTransactions) => reviewDuplicates?.duplicates.map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]), + canBeMissing: true, + }); + const transactionsMergeParams = useMemo( + () => TransactionUtils.buildMergeDuplicatesParams(reviewDuplicates, duplicates ?? [], newTransaction), + [duplicates, reviewDuplicates, newTransaction], + ); const isReportOwner = iouReport?.ownerAccountID === currentUserPersonalDetails?.accountID; const mergeDuplicates = useCallback(() => { @@ -87,9 +107,9 @@ function Confirmation() { isEmptyObject(report) || !ReportUtils.isValidReport(report) || ReportUtils.isReportNotFound(report) || - (reviewDuplicatesResult.status === 'loaded' && (!transaction?.transactionID || !doesTransactionBelongToReport)); + (reviewDuplicatesResult.status === 'loaded' && (!newTransaction?.transactionID || !doesTransactionBelongToReport)); - if (isLoadingOnyxValue(reviewDuplicatesResult, reportResult) || !transaction?.transactionID) { + if (isLoadingOnyxValue(reviewDuplicatesResult, reportResult) || !newTransaction?.transactionID) { return ; } @@ -122,7 +142,7 @@ function Confirmation() { shouldShowAnimatedBackground={false} readonly isFromReviewDuplicates - updatedTransaction={transaction as OnyxEntry} + updatedTransaction={newTransaction as OnyxEntry} /> diff --git a/src/pages/TransactionDuplicate/Review.tsx b/src/pages/TransactionDuplicate/Review.tsx index 66f240d6fbca..31f4e87512f2 100644 --- a/src/pages/TransactionDuplicate/Review.tsx +++ b/src/pages/TransactionDuplicate/Review.tsx @@ -17,7 +17,6 @@ import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigat import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; import {getLinkedTransactionID, getReportAction} from '@libs/ReportActionsUtils'; import {isReportIDApproved, isSettled} from '@libs/ReportUtils'; -import {getTransaction} from '@libs/TransactionUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; @@ -28,7 +27,7 @@ function TransactionDuplicateReview() { const {translate} = useLocalize(); const route = useRoute>(); const currentPersonalDetails = useCurrentUserPersonalDetails(); - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${route.params.threadReportID}`, {canBeMissing: true}); const reportAction = getReportAction(report?.parentReportID, report?.parentReportActionID); const transactionID = getLinkedTransactionID(reportAction, report?.reportID) ?? undefined; const transactionViolations = useTransactionViolations(transactionID); @@ -38,14 +37,20 @@ function TransactionDuplicateReview() { ); const transactionIDs = transactionID ? [transactionID, ...duplicateTransactionIDs] : duplicateTransactionIDs; - const transactions = transactionIDs.map((item) => getTransaction(item)).sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()); + const [transactions] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}`, { + selector: (allTransactions) => + transactionIDs + .map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]) + .sort((a, b) => new Date(a?.created ?? '').getTime() - new Date(b?.created ?? '').getTime()), + canBeMissing: true, + }); const keepAll = () => { dismissDuplicateTransactionViolation(transactionIDs, currentPersonalDetails); Navigation.goBack(); }; - const hasSettledOrApprovedTransaction = transactions.some((transaction) => isSettled(transaction?.reportID) || isReportIDApproved(transaction?.reportID)); + const hasSettledOrApprovedTransaction = transactions?.some((transaction) => isSettled(transaction?.reportID) || isReportIDApproved(transaction?.reportID)); return ( @@ -61,7 +66,7 @@ function TransactionDuplicateReview() { /> {!!hasSettledOrApprovedTransaction && {translate('iou.someDuplicatesArePaid')}} - + ); diff --git a/src/pages/TransactionDuplicate/ReviewBillable.tsx b/src/pages/TransactionDuplicate/ReviewBillable.tsx index 574ae40c49c9..88aefbdb107b 100644 --- a/src/pages/TransactionDuplicate/ReviewBillable.tsx +++ b/src/pages/TransactionDuplicate/ReviewBillable.tsx @@ -8,7 +8,8 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {compareDuplicateTransactionFields, getTransactionID} from '@libs/TransactionUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; @@ -17,14 +18,26 @@ import ReviewFields from './ReviewFields'; function ReviewBillable() { const route = useRoute>(); const {translate} = useLocalize(); - const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); + const transactionID = getTransactionID(route.params.threadReportID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES, {canBeMissing: true}); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true}); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, { + canBeMissing: true, + }); + const allDuplicateIDs = useMemo( + () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], + [transactionViolations], + ); + const [allDuplicates] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}`, { + selector: (allTransactions) => allDuplicateIDs.map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]), + canBeMissing: true, + }); + const compareResult = compareDuplicateTransactionFields(transaction, allDuplicates, reviewDuplicates?.reportID); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), 'billable', - route.params.threadReportID ?? '', + route.params.threadReportID, route.params.backTo, ); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewCategory.tsx b/src/pages/TransactionDuplicate/ReviewCategory.tsx index 599647749456..3e7a6c887938 100644 --- a/src/pages/TransactionDuplicate/ReviewCategory.tsx +++ b/src/pages/TransactionDuplicate/ReviewCategory.tsx @@ -8,7 +8,8 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {compareDuplicateTransactionFields, getTransactionID} from '@libs/TransactionUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; @@ -17,14 +18,27 @@ import ReviewFields from './ReviewFields'; function ReviewCategory() { const route = useRoute>(); const {translate} = useLocalize(); - const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); + const transactionID = getTransactionID(route.params.threadReportID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES, {canBeMissing: true}); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true}); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, { + canBeMissing: false, + }); + const allDuplicateIDs = useMemo( + () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], + [transactionViolations], + ); + const [allDuplicates] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}`, { + selector: (allTransactions) => allDuplicateIDs.map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]), + canBeMissing: true, + }); + + const compareResult = compareDuplicateTransactionFields(transaction, allDuplicates, reviewDuplicates?.reportID); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), 'category', - route.params.threadReportID ?? '', + route.params.threadReportID, route.params.backTo, ); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewDescription.tsx b/src/pages/TransactionDuplicate/ReviewDescription.tsx index d261591b4c45..d2c3c9d590af 100644 --- a/src/pages/TransactionDuplicate/ReviewDescription.tsx +++ b/src/pages/TransactionDuplicate/ReviewDescription.tsx @@ -11,6 +11,7 @@ import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/type import Parser from '@libs/Parser'; import StringUtils from '@libs/StringUtils'; import {compareDuplicateTransactionFields, getTransactionID} from '@libs/TransactionUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; @@ -21,7 +22,20 @@ function ReviewDescription() { const {translate} = useLocalize(); const transactionID = getTransactionID(route.params.threadReportID); const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES, {canBeMissing: false}); - const compareResult = compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true}); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, { + canBeMissing: false, + }); + const allDuplicateIDs = useMemo( + () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], + [transactionViolations], + ); + const [allDuplicates] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}`, { + selector: (allTransactions) => allDuplicateIDs.map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]), + canBeMissing: true, + }); + + const compareResult = compareDuplicateTransactionFields(transaction, allDuplicates, reviewDuplicates?.reportID); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), diff --git a/src/pages/TransactionDuplicate/ReviewMerchant.tsx b/src/pages/TransactionDuplicate/ReviewMerchant.tsx index 9c4e6a7f4fc1..28b4165e18a7 100644 --- a/src/pages/TransactionDuplicate/ReviewMerchant.tsx +++ b/src/pages/TransactionDuplicate/ReviewMerchant.tsx @@ -8,7 +8,8 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {compareDuplicateTransactionFields, getTransactionID} from '@libs/TransactionUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; @@ -17,14 +18,27 @@ import ReviewFields from './ReviewFields'; function ReviewMerchant() { const route = useRoute>(); const {translate} = useLocalize(); - const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); + const transactionID = getTransactionID(route.params.threadReportID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES, {canBeMissing: true}); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true}); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, { + canBeMissing: false, + }); + const allDuplicateIDs = useMemo( + () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], + [transactionViolations], + ); + const [allDuplicates] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}`, { + selector: (allTransactions) => allDuplicateIDs.map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]), + canBeMissing: true, + }); + + const compareResult = compareDuplicateTransactionFields(transaction, allDuplicates, reviewDuplicates?.reportID); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), 'merchant', - route.params.threadReportID ?? '', + route.params.threadReportID, route.params.backTo, ); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx index 3fc20b2c9cb9..2552a8483424 100644 --- a/src/pages/TransactionDuplicate/ReviewReimbursable.tsx +++ b/src/pages/TransactionDuplicate/ReviewReimbursable.tsx @@ -8,7 +8,8 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {compareDuplicateTransactionFields, getTransactionID} from '@libs/TransactionUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; @@ -17,14 +18,27 @@ import ReviewFields from './ReviewFields'; function ReviewReimbursable() { const route = useRoute>(); const {translate} = useLocalize(); - const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); + const transactionID = getTransactionID(route.params.threadReportID); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES, {canBeMissing: true}); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true}); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, { + canBeMissing: false, + }); + const allDuplicateIDs = useMemo( + () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], + [transactionViolations], + ); + const [allDuplicates] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}`, { + selector: (allTransactions) => allDuplicateIDs.map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]), + canBeMissing: true, + }); + + const compareResult = compareDuplicateTransactionFields(transaction, allDuplicates, reviewDuplicates?.reportID); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), 'reimbursable', - route.params.threadReportID ?? '', + route.params.threadReportID, route.params.backTo, ); const options = useMemo( diff --git a/src/pages/TransactionDuplicate/ReviewTag.tsx b/src/pages/TransactionDuplicate/ReviewTag.tsx index 5ead3966a1f3..6617c2f7013a 100644 --- a/src/pages/TransactionDuplicate/ReviewTag.tsx +++ b/src/pages/TransactionDuplicate/ReviewTag.tsx @@ -8,8 +8,9 @@ import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation' import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {getCleanedTagName} from '@libs/PolicyUtils'; +import {compareDuplicateTransactionFields, getTransactionID} from '@libs/TransactionUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import type {FieldItemType} from './ReviewFields'; @@ -18,15 +19,29 @@ import ReviewFields from './ReviewFields'; function ReviewTag() { const route = useRoute>(); const {translate} = useLocalize(); - const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); + const transactionID = getTransactionID(route.params.threadReportID); - const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES, {canBeMissing: true}); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true}); + const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, { + selector: (allTransactionsViolations) => allTransactionsViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`], + canBeMissing: false, + }); + const allDuplicateIDs = useMemo( + () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], + [transactionViolations], + ); + const [allDuplicates] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}`, { + selector: (allTransactions) => allDuplicateIDs.map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]), + canBeMissing: true, + }); + + const compareResult = compareDuplicateTransactionFields(transaction, allDuplicates, reviewDuplicates?.reportID); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), 'tag', - route.params.threadReportID ?? '', + route.params.threadReportID, route.params.backTo, ); const options = useMemo( @@ -35,7 +50,7 @@ function ReviewTag() { !tag ? {text: translate('violations.none'), value: ''} : { - text: PolicyUtils.getCleanedTagName(tag), + text: getCleanedTagName(tag), value: tag, }, ), diff --git a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx index ea2561028db3..cbfab955efad 100644 --- a/src/pages/TransactionDuplicate/ReviewTaxCode.tsx +++ b/src/pages/TransactionDuplicate/ReviewTaxCode.tsx @@ -6,11 +6,12 @@ import ScreenWrapper from '@components/ScreenWrapper'; import useLocalize from '@hooks/useLocalize'; import useReviewDuplicatesNavigation from '@hooks/useReviewDuplicatesNavigation'; import {setReviewDuplicatesKey} from '@libs/actions/Transaction'; -import * as CurrencyUtils from '@libs/CurrencyUtils'; +import {convertToBackendAmount} from '@libs/CurrencyUtils'; import type {PlatformStackRouteProp} from '@libs/Navigation/PlatformStackNavigation/types'; import type {TransactionDuplicateNavigatorParamList} from '@libs/Navigation/types'; -import * as PolicyUtils from '@libs/PolicyUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; +import {getPolicy, getTaxByID} from '@libs/PolicyUtils'; +import {calculateTaxAmount, compareDuplicateTransactionFields, getAmount, getDefaultTaxCode, getTaxValue, getTransactionID} from '@libs/TransactionUtils'; +import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type SCREENS from '@src/SCREENS'; import ReviewDescription from './ReviewDescription'; @@ -20,28 +21,41 @@ import ReviewFields from './ReviewFields'; function ReviewTaxRate() { const route = useRoute>(); const {translate} = useLocalize(); - const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES); - const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reviewDuplicates?.reportID ?? route.params.threadReportID}`); + const [reviewDuplicates] = useOnyx(ONYXKEYS.REVIEW_DUPLICATES, {canBeMissing: true}); + const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reviewDuplicates?.reportID ?? route.params.threadReportID}`, {canBeMissing: true}); // This will be fixed as part of https://github.com/Expensify/Expensify/issues/507850 // eslint-disable-next-line deprecation/deprecation - const policy = PolicyUtils.getPolicy(report?.policyID ?? ''); - const transactionID = TransactionUtils.getTransactionID(route.params.threadReportID ?? ''); - const compareResult = TransactionUtils.compareDuplicateTransactionFields(transactionID, reviewDuplicates?.reportID ?? '-1'); + const policy = getPolicy(report?.policyID); + const transactionID = getTransactionID(route.params.threadReportID); + const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, {canBeMissing: true}); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`, { + canBeMissing: false, + }); + const allDuplicateIDs = useMemo( + () => transactionViolations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], + [transactionViolations], + ); + const [allDuplicates] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}`, { + selector: (allTransactions) => allDuplicateIDs.map((id) => allTransactions?.[`${ONYXKEYS.COLLECTION.TRANSACTION}${id}`]), + canBeMissing: true, + }); + + const compareResult = compareDuplicateTransactionFields(transaction, allDuplicates, reviewDuplicates?.reportID); const stepNames = Object.keys(compareResult.change ?? {}).map((key, index) => (index + 1).toString()); const {currentScreenIndex, goBack, navigateToNextScreen} = useReviewDuplicatesNavigation( Object.keys(compareResult.change ?? {}), 'taxCode', - route.params.threadReportID ?? '', + route.params.threadReportID, route.params.backTo, ); - const transaction = TransactionUtils.getTransaction(transactionID); + const options = useMemo( () => compareResult.change.taxCode?.map((taxID) => !taxID - ? {text: translate('violations.none'), value: TransactionUtils.getDefaultTaxCode(policy, transaction) ?? ''} + ? {text: translate('violations.none'), value: getDefaultTaxCode(policy, transaction) ?? ''} : { - text: PolicyUtils.getTaxByID(policy, taxID)?.name ?? '', + text: getTaxByID(policy, taxID)?.name ?? '', value: taxID, }, ), @@ -49,8 +63,8 @@ function ReviewTaxRate() { ); const getTaxAmount = useCallback( (taxID: string) => { - const taxPercentage = TransactionUtils.getTaxValue(policy, transaction, taxID); - return CurrencyUtils.convertToBackendAmount(TransactionUtils.calculateTaxAmount(taxPercentage ?? '', TransactionUtils.getAmount(transaction), transaction?.currency ?? '')); + const taxPercentage = getTaxValue(policy, transaction, taxID); + return convertToBackendAmount(calculateTaxAmount(taxPercentage ?? '', getAmount(transaction), transaction?.currency ?? '')); }, [policy, transaction], ); diff --git a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx index a233c81fe7f8..84c4dadd272f 100755 --- a/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx +++ b/src/pages/home/report/ContextMenu/BaseReportActionContextMenu.tsx @@ -21,7 +21,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout'; import useRestoreInputFocus from '@hooks/useRestoreInputFocus'; import useStyleUtils from '@hooks/useStyleUtils'; import {getExpensifyCardFromReportAction} from '@libs/CardMessageUtils'; -import {getLinkedTransactionID, getOneTransactionThreadReportID, getReportAction} from '@libs/ReportActionsUtils'; +import {getLinkedTransactionID, getOneTransactionThreadReportID, getOriginalMessage, getReportAction} from '@libs/ReportActionsUtils'; import { chatIncludesChronosWithID, getSourceIDFromReportAction, @@ -35,7 +35,7 @@ import shouldEnableContextMenuEnterShortcut from '@libs/shouldEnableContextMenuE import {isAnonymousUser, signOutAndRedirectToSignIn} from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {ReportAction} from '@src/types/onyx'; +import type {OriginalMessageIOU, ReportAction} from '@src/types/onyx'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {ContextMenuAction, ContextMenuActionPayload} from './ContextMenuActions'; import ContextMenuActions from './ContextMenuActions'; @@ -192,6 +192,8 @@ function BaseReportActionContextMenu({ const isChildReportArchived = useReportIsArchived(childReport?.reportID); const isParentReportArchived = useReportIsArchived(childReport?.parentReportID); const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${childReport?.parentReportID}`, {canBeMissing: true}); + const iouTransactionID = (getOriginalMessage(moneyRequestAction ?? reportAction) as OriginalMessageIOU)?.IOUTransactionID; + const [iouTransaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${iouTransactionID}`, {canBeMissing: true}); const isMoneyRequest = useMemo(() => ReportUtilsIsMoneyRequest(childReport), [childReport]); const isTrackExpenseReport = ReportUtilsIsTrackExpenseReport(childReport); @@ -224,6 +226,7 @@ function BaseReportActionContextMenu({ moneyRequestAction, areHoldRequirementsMet, account, + iouTransaction, }), ); diff --git a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx index b8aea7e9a2cd..f155d81497df 100644 --- a/src/pages/home/report/ContextMenu/ContextMenuActions.tsx +++ b/src/pages/home/report/ContextMenu/ContextMenuActions.tsx @@ -171,6 +171,7 @@ type ShouldShow = (args: { moneyRequestAction: ReportAction | undefined; areHoldRequirementsMet: boolean; account: OnyxEntry; + iouTransaction: OnyxEntry; }) => boolean; type ContextMenuActionPayload = { @@ -819,11 +820,15 @@ const ContextMenuActions: ContextMenuAction[] = [ isAnonymousAction: false, textTranslateKey: 'reportActionContextMenu.deleteAction', icon: Expensicons.Trashcan, - shouldShow: ({type, reportAction, isArchivedRoom, isChronosReport, reportID, moneyRequestAction}) => + shouldShow: ({type, reportAction, isArchivedRoom, isChronosReport, reportID, moneyRequestAction, iouTransaction}) => // Until deleting parent threads is supported in FE, we will prevent the user from deleting a thread parent !!reportID && type === CONST.CONTEXT_MENU_TYPES.REPORT_ACTION && - canDeleteReportAction(moneyRequestAction ?? reportAction, isMoneyRequestAction(moneyRequestAction) ? getOriginalMessage(moneyRequestAction)?.IOUReportID : reportID) && + canDeleteReportAction( + moneyRequestAction ?? reportAction, + isMoneyRequestAction(moneyRequestAction) ? getOriginalMessage(moneyRequestAction)?.IOUReportID : reportID, + iouTransaction, + ) && !isArchivedRoom && !isChronosReport && !isMessageDeleted(reportAction), diff --git a/tests/actions/EnforceActionExportRestrictions.ts b/tests/actions/EnforceActionExportRestrictions.ts index c015aba6e91b..d57e76f03ffc 100644 --- a/tests/actions/EnforceActionExportRestrictions.ts +++ b/tests/actions/EnforceActionExportRestrictions.ts @@ -4,6 +4,7 @@ import * as IOU from '@libs/actions/IOU'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; import * as Policy from '@userActions/Policy/Policy'; import * as Task from '@userActions/Task'; @@ -101,6 +102,13 @@ describe('Policy', () => { }); }); +describe('TransactionUtils', () => { + it('does not export getTransaction', () => { + // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal + expect(TransactionUtils.getTransaction).toBeUndefined(); + }); +}); + describe('IOU', () => { it('does not export getPolicy', () => { // @ts-expect-error the test is asserting that it's undefined, so the TS error is normal diff --git a/tests/unit/IOUUtilsTest.ts b/tests/unit/IOUUtilsTest.ts index c3b226f40c45..b18403edfc2b 100644 --- a/tests/unit/IOUUtilsTest.ts +++ b/tests/unit/IOUUtilsTest.ts @@ -220,7 +220,7 @@ describe('hasRTERWithoutViolation', () => { await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithViolation}`, transactionWithViolation); await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithoutViolation}`, transactionWithoutViolation); - expect(hasAnyTransactionWithoutRTERViolation([String(transactionIDWithoutViolation), String(transactionIDWithViolation)], violations)).toBe(true); + expect(hasAnyTransactionWithoutRTERViolation([transactionWithoutViolation, transactionWithViolation], violations)).toBe(true); }); test('Return false if there is no rter without violation in all transactionViolations with given transactionIDs.', async () => { @@ -249,7 +249,7 @@ describe('hasRTERWithoutViolation', () => { }; await Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionIDWithViolation}`, transactionWithViolation); - expect(hasAnyTransactionWithoutRTERViolation([String(transactionIDWithViolation)], violations)).toBe(false); + expect(hasAnyTransactionWithoutRTERViolation([transactionWithViolation], violations)).toBe(false); }); }); diff --git a/tests/unit/ReportUtilsTest.ts b/tests/unit/ReportUtilsTest.ts index b239d35c7d97..e9b277e24e75 100644 --- a/tests/unit/ReportUtilsTest.ts +++ b/tests/unit/ReportUtilsTest.ts @@ -2653,9 +2653,7 @@ describe('ReportUtils', () => { }, }; - Onyx.merge(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`, transaction).then(() => { - expect(canDeleteReportAction(moneyRequestAction, currentReportId)).toBe(false); - }); + expect(canDeleteReportAction(moneyRequestAction, currentReportId, transaction)).toBe(false); }); }); diff --git a/tests/unit/TransactionPreviewUtils.test.ts b/tests/unit/TransactionPreviewUtils.test.ts index 786a1912c723..5a9a555e99a4 100644 --- a/tests/unit/TransactionPreviewUtils.test.ts +++ b/tests/unit/TransactionPreviewUtils.test.ts @@ -60,6 +60,7 @@ describe('TransactionPreviewUtils', () => { const functionArgs = { ...basicProps, transaction: {...basicProps.transaction, comment: {hold: 'true'}}, + originalTransaction: undefined, shouldShowRBR: true, }; @@ -68,7 +69,7 @@ describe('TransactionPreviewUtils', () => { }); it('should handle missing iouReport and transaction correctly', () => { - const functionArgs = {...basicProps, iouReport: undefined, transaction: undefined}; + const functionArgs = {...basicProps, iouReport: undefined, transaction: undefined, originalTransaction: undefined}; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); expect(result.RBRMessage.text).toEqual(''); expect(result.previewHeaderText).toContainEqual({translationPath: 'iou.cash'}); @@ -79,6 +80,7 @@ describe('TransactionPreviewUtils', () => { const functionArgs = { ...basicProps, transaction: {...basicProps.transaction, merchant: '', amount: 0}, + originalTransaction: undefined, shouldShowRBR: true, }; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); @@ -92,28 +94,29 @@ describe('TransactionPreviewUtils', () => { ...basicProps.transaction, managedCard: true, }, + originalTransaction: undefined, }; const cardTransaction = getTransactionPreviewTextAndTranslationPaths(functionArgsWithCardTransaction); - const cashTransaction = getTransactionPreviewTextAndTranslationPaths(basicProps); + const cashTransaction = getTransactionPreviewTextAndTranslationPaths({...basicProps}); expect(cardTransaction.previewHeaderText).toEqual(expect.arrayContaining([{translationPath: 'iou.card'}])); expect(cashTransaction.previewHeaderText).toEqual(expect.arrayContaining([{translationPath: 'iou.cash'}])); }); it('displays appropriate header text if the transaction is bill split', () => { - const functionArgs = {...basicProps, isBillSplit: true}; + const functionArgs = {...basicProps, isBillSplit: true, originalTransaction: undefined}; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); expect(result.previewHeaderText).toEqual(expect.arrayContaining([{translationPath: 'iou.split'}])); }); it('displays description when receipt is being scanned', () => { - const functionArgs = {...basicProps, transaction: {...basicProps.transaction, receipt: {state: CONST.IOU.RECEIPT_STATE.SCANNING}}}; + const functionArgs = {...basicProps, transaction: {...basicProps.transaction, receipt: {state: CONST.IOU.RECEIPT_STATE.SCANNING}}, originalTransaction: undefined}; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); expect(result.previewHeaderText).toEqual(expect.arrayContaining([{translationPath: 'common.receipt'}])); }); it('should apply correct text when transaction is pending and not a bill split', () => { - const functionArgs = {...basicProps, transaction: {...basicProps.transaction, status: CONST.TRANSACTION.STATUS.PENDING}}; + const functionArgs = {...basicProps, transaction: {...basicProps.transaction, status: CONST.TRANSACTION.STATUS.PENDING}, originalTransaction: undefined}; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); expect(result.previewHeaderText).toEqual(expect.arrayContaining([{translationPath: 'iou.pending'}])); }); @@ -123,6 +126,7 @@ describe('TransactionPreviewUtils', () => { ...basicProps, transactionDetails: {amount: 300, currency: 'EUR'}, transaction: {...basicProps.transaction, receipt: {state: CONST.IOU.RECEIPT_STATE.SCANNING}}, + originalTransaction: undefined, }; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); expect(result.displayAmountText.translationPath).toEqual('iou.receiptStatusTitle'); @@ -156,7 +160,7 @@ describe('TransactionPreviewUtils', () => { }); it('shows approved message when the iouReport is canceled', () => { - const functionArgs = {...basicProps, iouReport: {...basicProps.iouReport, isCancelledIOU: true}}; + const functionArgs = {...basicProps, iouReport: {...basicProps.iouReport, isCancelledIOU: true}, originalTransaction: undefined}; const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); expect(result.previewHeaderText).toContainEqual({translationPath: 'iou.canceled'}); }); @@ -166,6 +170,7 @@ describe('TransactionPreviewUtils', () => { ...basicProps, iouReport: {...basicProps.iouReport, stateNum: CONST.REPORT.STATE_NUM.APPROVED, statusNum: CONST.REPORT.STATUS_NUM.APPROVED}, shouldShowRBR: true, + originalTransaction: undefined, }; jest.spyOn(ReportUtils, 'isPaidGroupPolicyExpenseReport').mockReturnValue(true); const result = getTransactionPreviewTextAndTranslationPaths(functionArgs); diff --git a/tests/unit/ViolationUtilsTest.ts b/tests/unit/ViolationUtilsTest.ts index 7a7148a38b52..3dde372bf142 100644 --- a/tests/unit/ViolationUtilsTest.ts +++ b/tests/unit/ViolationUtilsTest.ts @@ -549,7 +549,7 @@ describe('getViolations', () => { await Onyx.multiSet({...transactionCollectionDataSet}); // Should filter out the smartScanFailedViolation - const filteredViolations = getTransactionViolations(transaction.transactionID, transactionViolationsCollection); + const filteredViolations = getTransactionViolations(transaction, transactionViolationsCollection); expect(filteredViolations).toEqual([duplicatedTransactionViolation, tagOutOfPolicyViolation]); }); @@ -567,7 +567,7 @@ describe('getViolations', () => { }; await Onyx.multiSet({...transactionCollectionDataSet}); - const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction.transactionID, transactionViolationsCollection); + const hasWarningTypeViolationRes = hasWarningTypeViolation(transaction, transactionViolationsCollection); expect(hasWarningTypeViolationRes).toBeTruthy(); }); });