From 020fe1b94c6fe44fbdd1afa6802984da4048501d Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Tue, 30 Jul 2024 13:14:40 +0200 Subject: [PATCH 1/9] reduce usage of getAllReportTransactions in ReportPreview --- src/components/MoneyReportHeader.tsx | 14 +++++++------- .../ReportActionItem/MoneyReportView.tsx | 4 +++- .../ReportActionItem/ReportPreview.tsx | 15 ++++++++------- src/libs/ReportUtils.ts | 18 ++++++++---------- src/libs/TransactionUtils.ts | 16 ++++++++++++++++ 5 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 6fe33e90919c..261a4c7149f1 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -98,14 +98,14 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [paymentType, setPaymentType] = useState(); const [requestType, setRequestType] = useState(); - const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy); + const allTransactions = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID); + const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy, allTransactions); const policyType = policy?.type; const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); const connectedIntegration = PolicyUtils.getConnectedIntegration(policy); - const navigateBackToAfterDelete = useRef(); const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => TransactionUtils.isReceiptBeingScanned(t)); - const transactionIDs = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID).map((t) => t.transactionID); + const transactionIDs = allTransactions.map((t) => t.transactionID); const allHavePendingRTERViolation = TransactionUtils.allHavePendingRTERViolation(transactionIDs); // allTransactions in TransactionUtils might have stale data const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID, transactions); @@ -131,8 +131,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || allHavePendingRTERViolation || shouldShowExportIntegrationButton; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); - const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy); - const displayedAmount = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID) && canAllowSettlement ? nonHeldAmount : formattedAmount; + const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy, allTransactions); + const displayedAmount = TransactionUtils.isAnyTransactionOnHold(allTransactions) && canAllowSettlement ? nonHeldAmount : formattedAmount; const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout); const confirmPayment = (type?: PaymentMethodType | undefined) => { @@ -141,7 +141,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea } setPaymentType(type); setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); - if (ReportUtils.hasHeldExpenses(moneyRequestReport.reportID)) { + if (TransactionUtils.isAnyTransactionOnHold(allTransactions)) { setIsHoldMenuVisible(true); } else if (ReportUtils.isInvoiceReport(moneyRequestReport)) { IOU.payInvoice(type, chatReport, moneyRequestReport); @@ -152,7 +152,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const confirmApproval = () => { setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); - if (ReportUtils.hasHeldExpenses(moneyRequestReport.reportID)) { + if (TransactionUtils.isAnyTransactionOnHold(allTransactions)) { setIsHoldMenuVisible(true); } else { IOU.approveMoneyRequest(moneyRequestReport, true); diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 7c2e29bd983d..66ae611df6be 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -17,6 +17,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; +import * as TransactionUtils from '@libs/TransactionUtils'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import variables from '@styles/variables'; import * as reportActions from '@src/libs/actions/Report'; @@ -47,7 +48,8 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const isSettled = ReportUtils.isSettled(report.reportID); - const isTotalUpdated = ReportUtils.hasUpdatedTotal(report, policy); + const allReportTransactions = TransactionUtils.getAllReportTransactions(report.reportID); + const isTotalUpdated = ReportUtils.hasUpdatedTotal(report, policy, allReportTransactions); const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(report); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index bff62f247eee..132961647270 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -122,12 +122,14 @@ function ReportPreview({ const {canUseViolations} = usePermissions(); const {isOffline} = useNetwork(); + const allTransactions = TransactionUtils.getAllReportTransactions(iouReportID); + const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyTransactionsWithPendingRoutes, hasNonReimbursableTransactions} = useMemo( () => ({ hasMissingSmartscanFields: ReportUtils.hasMissingSmartscanFields(iouReportID), areAllRequestsBeingSmartScanned: ReportUtils.areAllRequestsBeingSmartScanned(iouReportID, action), hasOnlyTransactionsWithPendingRoutes: ReportUtils.hasOnlyTransactionsWithPendingRoutes(iouReportID), - hasNonReimbursableTransactions: ReportUtils.hasNonReimbursableTransactions(iouReportID), + hasNonReimbursableTransactions: TransactionUtils.areSomeTransactionsNonReimbursable(allTransactions), }), // When transactions get updated these status may have changed, so that is a case where we also want to run this. // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps @@ -136,7 +138,7 @@ function ReportPreview({ const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [requestType, setRequestType] = useState(); - const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(iouReport, policy); + const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(iouReport, policy, allTransactions); const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(iouReport?.reportID ?? ''); const [paymentType, setPaymentType] = useState(); @@ -150,8 +152,7 @@ function ReportPreview({ const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport); const isApproved = ReportUtils.isReportApproved(iouReport, action); - const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport, policy); - const allTransactions = TransactionUtils.getAllReportTransactions(iouReportID); + const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport, policy, allTransactions); const numberOfRequests = allTransactions.length; const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); const numberOfScanningReceipts = transactionsWithReceipts.filter((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)).length; @@ -193,7 +194,7 @@ function ReportPreview({ } setPaymentType(type); setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); - if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { + if (TransactionUtils.isAnyTransactionOnHold(allTransactions)) { setIsHoldMenuVisible(true); } else if (chatReport && iouReport) { if (ReportUtils.isInvoiceReport(iouReport)) { @@ -206,7 +207,7 @@ function ReportPreview({ const confirmApproval = () => { setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); - if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { + if (TransactionUtils.isAnyTransactionOnHold(allTransactions)) { setIsHoldMenuVisible(true); } else { IOU.approveMoneyRequest(iouReport, true); @@ -218,7 +219,7 @@ function ReportPreview({ return ''; } - if (ReportUtils.hasHeldExpenses(iouReport?.reportID) && canAllowSettlement) { + if (TransactionUtils.isAnyTransactionOnHold(allTransactions) && canAllowSettlement) { return nonHeldAmount; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1d2541659cf4..74fb24d4a7b7 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -6754,16 +6754,15 @@ function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry /** * Check if money report has any transactions updated optimistically */ -function hasUpdatedTotal(report: OnyxInputOrEntry, policy: OnyxInputOrEntry): boolean { +function hasUpdatedTotal(report: OnyxInputOrEntry, policy: OnyxInputOrEntry, allReportTransactions: Transaction[]): boolean { if (!report) { return true; } - const transactions = TransactionUtils.getAllReportTransactions(report.reportID); - const hasPendingTransaction = transactions.some((transaction) => !!transaction.pendingAction); - const hasTransactionWithDifferentCurrency = transactions.some((transaction) => transaction.currency !== report.currency); + const hasPendingTransaction = allReportTransactions.some((transaction) => !!transaction.pendingAction); + const hasTransactionWithDifferentCurrency = allReportTransactions.some((transaction) => transaction.currency !== report.currency); const hasDifferentWorkspaceCurrency = report.pendingFields?.createChat && isExpenseReport(report) && report.currency !== policy?.outputCurrency; - const hasOptimisticHeldExpense = hasHeldExpenses(report.reportID) && report?.unheldTotal === undefined; + const hasOptimisticHeldExpense = TransactionUtils.isAnyTransactionOnHold(allReportTransactions) && report?.unheldTotal === undefined; return !(hasPendingTransaction && (hasTransactionWithDifferentCurrency || hasDifferentWorkspaceCurrency)) && !hasOptimisticHeldExpense; } @@ -6771,15 +6770,14 @@ function hasUpdatedTotal(report: OnyxInputOrEntry, policy: OnyxInputOrEn /** * Return held and full amount formatted with used currency */ -function getNonHeldAndFullAmount(iouReport: OnyxEntry, policy: OnyxEntry): string[] { - const transactions = TransactionUtils.getAllReportTransactions(iouReport?.reportID ?? '-1'); - const hasPendingTransaction = transactions.some((transaction) => !!transaction.pendingAction); +function getNonHeldAndFullAmount(iouReport: OnyxEntry, policy: OnyxEntry, reportTransactions: Transaction[]): string[] { + const hasPendingTransaction = reportTransactions.some((transaction) => !!transaction.pendingAction); // if the report is an expense report, the total amount should be negated const coefficient = isExpenseReport(iouReport) ? -1 : 1; - if (hasUpdatedTotal(iouReport, policy) && hasPendingTransaction) { - const unheldTotal = transactions.reduce((currentVal, transaction) => currentVal - (!TransactionUtils.isOnHold(transaction) ? transaction.amount : 0), 0); + if (hasUpdatedTotal(iouReport, policy, reportTransactions) && hasPendingTransaction) { + const unheldTotal = reportTransactions.reduce((currentVal, transaction) => currentVal - (!TransactionUtils.isOnHold(transaction) ? transaction.amount : 0), 0); return [CurrencyUtils.convertToDisplayString(unheldTotal, iouReport?.currency), CurrencyUtils.convertToDisplayString((iouReport?.total ?? 0) * coefficient, iouReport?.currency)]; } diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index c044702b2fc0..ee23da7b3cec 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -980,6 +980,20 @@ function buildTransactionsMergeParams(reviewDuplicates: OnyxEntry isOnHold(transaction)); +} + +/** + * Check if any of the transactions is non-reimbursable + */ +function areSomeTransactionsNonReimbursable(transactions: Transaction[]): boolean { + return transactions.some((transaction) => transaction.reimbursable === false); +} + export { buildOptimisticTransaction, calculateTaxAmount, @@ -1053,6 +1067,8 @@ export { buildNewTransactionAfterReviewingDuplicates, buildTransactionsMergeParams, getReimbursable, + isAnyTransactionOnHold, + areSomeTransactionsNonReimbursable, }; export type {TransactionChanges}; From 52fcb34a6639639731dbf18ad97cbf4dfac37c4d Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Tue, 30 Jul 2024 14:10:24 +0200 Subject: [PATCH 2/9] update ReportPreview to operate on a single instance of transactions --- .../ReportActionItem/ReportPreview.tsx | 18 ++-- src/libs/TransactionUtils.ts | 85 ++++++++++++++++++- 2 files changed, 92 insertions(+), 11 deletions(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 132961647270..6343e6b87c3c 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -126,10 +126,10 @@ function ReportPreview({ const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyTransactionsWithPendingRoutes, hasNonReimbursableTransactions} = useMemo( () => ({ - hasMissingSmartscanFields: ReportUtils.hasMissingSmartscanFields(iouReportID), - areAllRequestsBeingSmartScanned: ReportUtils.areAllRequestsBeingSmartScanned(iouReportID, action), - hasOnlyTransactionsWithPendingRoutes: ReportUtils.hasOnlyTransactionsWithPendingRoutes(iouReportID), - hasNonReimbursableTransactions: TransactionUtils.areSomeTransactionsNonReimbursable(allTransactions), + hasMissingSmartscanFields: TransactionUtils.hasAnyTransactionMissingFields(allTransactions), + areAllRequestsBeingSmartScanned: TransactionUtils.areAllTransactionsBeingSmartScanned(allTransactions, action), + hasOnlyTransactionsWithPendingRoutes: TransactionUtils.areAllTransactionsWithPendingRoutes(allTransactions), + hasNonReimbursableTransactions: TransactionUtils.isAnyTransactionNonReimbursable(allTransactions), }), // When transactions get updated these status may have changed, so that is a case where we also want to run this. // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps @@ -139,7 +139,7 @@ function ReportPreview({ const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [requestType, setRequestType] = useState(); const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(iouReport, policy, allTransactions); - const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(iouReport?.reportID ?? ''); + const hasOnlyHeldExpenses = TransactionUtils.areAllExpensesOnHold(allTransactions); const [paymentType, setPaymentType] = useState(); const managerID = iouReport?.managerID ?? action.childManagerAccountID ?? 0; @@ -154,7 +154,7 @@ function ReportPreview({ const isApproved = ReportUtils.isReportApproved(iouReport, action); const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport, policy, allTransactions); const numberOfRequests = allTransactions.length; - const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); + const transactionsWithReceipts = TransactionUtils.getTransactionsWithReceipts(allTransactions); const numberOfScanningReceipts = transactionsWithReceipts.filter((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)).length; const numberOfPendingRequests = transactionsWithReceipts.filter((transaction) => TransactionUtils.isPending(transaction) && TransactionUtils.isCardTransaction(transaction)).length; @@ -162,8 +162,10 @@ function ReportPreview({ const isScanning = hasReceipts && areAllRequestsBeingSmartScanned; const hasErrors = (hasMissingSmartscanFields && !iouSettled) || - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - (canUseViolations && (ReportUtils.hasViolations(iouReportID, transactionViolations) || ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations))) || + (canUseViolations && + (TransactionUtils.isAnyTransactionViolated(allTransactions, transactionViolations) || + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + TransactionUtils.hasAnyTransactionWarningViolation(allTransactions, transactionViolations))) || (ReportUtils.isReportOwner(iouReport) && ReportUtils.hasReportViolations(iouReportID)) || ReportUtils.hasActionsWithErrors(iouReportID); const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index ee23da7b3cec..b028b53a158b 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -5,7 +5,19 @@ import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, OnyxInputOrEntry, Policy, RecentWaypoint, ReviewDuplicates, TaxRate, TaxRates, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; +import type { + Beta, + OnyxInputOrEntry, + Policy, + RecentWaypoint, + ReportAction, + ReviewDuplicates, + TaxRate, + TaxRates, + Transaction, + TransactionViolation, + TransactionViolations, +} from '@src/types/onyx'; import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {IOURequestType} from './actions/IOU'; @@ -699,6 +711,13 @@ function hasViolation(transactionID: string, transactionViolations: OnyxCollecti ); } +/** + * Checks to see if any transaction contains a violation + */ +function isAnyTransactionViolated(transactions: Transaction[], transactionViolations: OnyxCollection): boolean { + return transactions.some((transaction) => hasViolation(transaction.transactionID, transactionViolations)); +} + /** * Checks if any violations for the provided transaction are of type 'notice' */ @@ -990,10 +1009,63 @@ function isAnyTransactionOnHold(transactions: Transaction[]): boolean { /** * Check if any of the transactions is non-reimbursable */ -function areSomeTransactionsNonReimbursable(transactions: Transaction[]): boolean { +function isAnyTransactionNonReimbursable(transactions: Transaction[]): boolean { return transactions.some((transaction) => transaction.reimbursable === false); } +/** + * Checks to see if any of transactions contain a violation of type `warning` + */ +function hasAnyTransactionWarningViolation(transactions: Transaction[], transactionViolations: OnyxCollection): boolean { + return transactions.some((transaction) => hasWarningTypeViolation(transaction.transactionID, transactionViolations)); +} + +/** + * Filters only transactions with a receipt + */ +function getTransactionsWithReceipts(transactions: Transaction[]): Transaction[] { + return transactions.filter((transaction) => hasReceipt(transaction)); +} + +/** + * Check if all expenses in the Report are on hold + */ +function areAllExpensesOnHold(transactions: Transaction[]) { + return transactions.length > 0 && !transactions.some((transaction) => !isOnHold(transaction)); +} + +/** + * Checks whether all the transactions are of the Distance Request type with pending routes + */ +function areAllTransactionsWithPendingRoutes(transactions: Transaction[]) { + if (!transactions || transactions.length === 0) { + return false; + } + + return transactions.every((transaction) => isFetchingWaypointsFromServer(transaction)); +} + +/** + * Check if any of the transactions has required missing fields + */ +function hasAnyTransactionMissingFields(transactions: Transaction[]): boolean { + return transactions.some(hasMissingSmartscanFields); +} + +/** + * Check if all transactions are being smart scanned + */ +function areAllTransactionsBeingSmartScanned(transactions: Transaction[], reportPreviewAction: OnyxEntry) { + const transactionsWithReceipts = getTransactionsWithReceipts(transactions); + + // If we have more requests than requests with receipts, we have some manual requests + if (ReportActionsUtils.getNumberOfMoneyRequests(reportPreviewAction) > transactionsWithReceipts.length) { + return false; + } + + return transactionsWithReceipts.every((transaction) => isReceiptBeingScanned(transaction)); +} + export { buildOptimisticTransaction, calculateTaxAmount, @@ -1068,7 +1140,14 @@ export { buildTransactionsMergeParams, getReimbursable, isAnyTransactionOnHold, - areSomeTransactionsNonReimbursable, + isAnyTransactionNonReimbursable, + isAnyTransactionViolated, + hasAnyTransactionWarningViolation, + getTransactionsWithReceipts, + areAllExpensesOnHold, + areAllTransactionsWithPendingRoutes, + hasAnyTransactionMissingFields, + areAllTransactionsBeingSmartScanned, }; export type {TransactionChanges}; From a8ab569d0d9c786f0e139dc37de307c0b7b779d5 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Tue, 30 Jul 2024 15:09:51 +0200 Subject: [PATCH 3/9] memoize report transactions --- src/components/ReportActionItem/ReportPreview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 6343e6b87c3c..e328b960ecf6 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -122,7 +122,7 @@ function ReportPreview({ const {canUseViolations} = usePermissions(); const {isOffline} = useNetwork(); - const allTransactions = TransactionUtils.getAllReportTransactions(iouReportID); + const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(iouReportID), [iouReportID]); const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyTransactionsWithPendingRoutes, hasNonReimbursableTransactions} = useMemo( () => ({ From b89c1397c7b10ce43ec8b7ee785d160f9042fa28 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 31 Jul 2024 09:53:28 +0200 Subject: [PATCH 4/9] code review updates --- src/components/MoneyReportHeader.tsx | 12 +++---- .../ReportActionItem/ReportPreview.tsx | 5 ++- src/libs/ReportUtils.ts | 32 ------------------- src/libs/TransactionUtils.ts | 4 +-- 4 files changed, 10 insertions(+), 43 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 261a4c7149f1..e0858fec53b5 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -98,7 +98,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [paymentType, setPaymentType] = useState(); const [requestType, setRequestType] = useState(); - const allTransactions = TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID); + const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID, transactions), [moneyRequestReport?.reportID, transactions]); const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy, allTransactions); const policyType = policy?.type; const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); @@ -107,8 +107,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => TransactionUtils.isReceiptBeingScanned(t)); const transactionIDs = allTransactions.map((t) => t.transactionID); const allHavePendingRTERViolation = TransactionUtils.allHavePendingRTERViolation(transactionIDs); - // allTransactions in TransactionUtils might have stale data - const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID, transactions); + const hasOnlyHeldExpenses = TransactionUtils.areAllExpensesOnHold(allTransactions); const shouldShowPayButton = useMemo(() => IOU.canIOUBePaid(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]); @@ -132,7 +131,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy, allTransactions); - const displayedAmount = TransactionUtils.isAnyTransactionOnHold(allTransactions) && canAllowSettlement ? nonHeldAmount : formattedAmount; + const isAnyTransactionOnHold = TransactionUtils.isAnyTransactionOnHold(allTransactions); + const displayedAmount = isAnyTransactionOnHold && canAllowSettlement ? nonHeldAmount : formattedAmount; const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout); const confirmPayment = (type?: PaymentMethodType | undefined) => { @@ -141,7 +141,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea } setPaymentType(type); setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); - if (TransactionUtils.isAnyTransactionOnHold(allTransactions)) { + if (isAnyTransactionOnHold) { setIsHoldMenuVisible(true); } else if (ReportUtils.isInvoiceReport(moneyRequestReport)) { IOU.payInvoice(type, chatReport, moneyRequestReport); @@ -152,7 +152,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const confirmApproval = () => { setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); - if (TransactionUtils.isAnyTransactionOnHold(allTransactions)) { + if (isAnyTransactionOnHold) { setIsHoldMenuVisible(true); } else { IOU.approveMoneyRequest(moneyRequestReport, true); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index f2b86dfd5fb4..5a4a50adcba2 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -122,12 +122,11 @@ function ReportPreview({ const {translate} = useLocalize(); const {canUseViolations} = usePermissions(); const {isOffline} = useNetwork(); - - const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(iouReportID), [iouReportID]); + const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(iouReportID, transactions), [iouReportID, transactions]); const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyTransactionsWithPendingRoutes, hasNonReimbursableTransactions} = useMemo( () => ({ - hasMissingSmartscanFields: TransactionUtils.hasAnyTransactionMissingFields(allTransactions), + hasMissingSmartscanFields: TransactionUtils.hasAnyTransactionMissingSmartscanFields(allTransactions), areAllRequestsBeingSmartScanned: TransactionUtils.areAllTransactionsBeingSmartScanned(allTransactions, action), hasOnlyTransactionsWithPendingRoutes: TransactionUtils.areAllTransactionsWithPendingRoutes(allTransactions), hasNonReimbursableTransactions: TransactionUtils.isAnyTransactionNonReimbursable(allTransactions), diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 0e7db4ad28e8..1e215bb7c17a 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1415,20 +1415,6 @@ function isPolicyAdmin(policyID: string, policies: OnyxCollection): bool return policyRole === CONST.POLICY.ROLE.ADMIN; } -/** - * Checks whether all the transactions linked to the IOU report are of the Distance Request type with pending routes - */ -function hasOnlyTransactionsWithPendingRoutes(iouReportID: string | undefined): boolean { - const transactions = TransactionUtils.getAllReportTransactions(iouReportID); - - // Early return false in case not having any transaction - if (!transactions || transactions.length === 0) { - return false; - } - - return transactions.every((transaction) => TransactionUtils.isFetchingWaypointsFromServer(transaction)); -} - /** * If the report is a thread and has a chat type set, it is a workspace chat. */ @@ -2931,22 +2917,6 @@ function getTransactionsWithReceipts(iouReportID: string | undefined): Transacti return transactions.filter((transaction) => TransactionUtils.hasReceipt(transaction)); } -/** - * For report previews, we display a "Receipt scan in progress" indicator - * instead of the report total only when we have no report total ready to show. This is the case when - * all requests are receipts that are being SmartScanned. As soon as we have a non-receipt request, - * or as soon as one receipt request is done scanning, we have at least one - * "ready" expense, and we remove this indicator to show the partial report total. - */ -function areAllRequestsBeingSmartScanned(iouReportID: string, reportPreviewAction: OnyxEntry): boolean { - const transactionsWithReceipts = getTransactionsWithReceipts(iouReportID); - // If we have more requests than requests with receipts, we have some manual requests - if (ReportActionsUtils.getNumberOfMoneyRequests(reportPreviewAction) > transactionsWithReceipts.length) { - return false; - } - return transactionsWithReceipts.every((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)); -} - /** * Get the transactions related to a report preview with receipts * Get the details linked to the IOU reportAction @@ -7350,7 +7320,6 @@ function isExported(reportActions: OnyxEntry) { export { addDomainToShortMention, completeShortMention, - areAllRequestsBeingSmartScanned, buildOptimisticAddCommentReportAction, buildOptimisticApprovedReportAction, buildOptimisticUnapprovedReportAction, @@ -7502,7 +7471,6 @@ export { hasMissingSmartscanFields, hasNonReimbursableTransactions, hasOnlyHeldExpenses, - hasOnlyTransactionsWithPendingRoutes, hasReportNameError, hasSmartscanError, hasUpdatedTotal, diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index b028b53a158b..b7de4d2704d2 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -1048,7 +1048,7 @@ function areAllTransactionsWithPendingRoutes(transactions: Transaction[]) { /** * Check if any of the transactions has required missing fields */ -function hasAnyTransactionMissingFields(transactions: Transaction[]): boolean { +function hasAnyTransactionMissingSmartscanFields(transactions: Transaction[]): boolean { return transactions.some(hasMissingSmartscanFields); } @@ -1146,7 +1146,7 @@ export { getTransactionsWithReceipts, areAllExpensesOnHold, areAllTransactionsWithPendingRoutes, - hasAnyTransactionMissingFields, + hasAnyTransactionMissingSmartscanFields, areAllTransactionsBeingSmartScanned, }; From 7d0d08b8f391d36ea8fbe5c74460bd025f22cc60 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 31 Jul 2024 11:09:05 +0200 Subject: [PATCH 5/9] get transactions from Onyx in MoneyReportView --- src/components/ReportActionItem/MoneyReportView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 66ae611df6be..59ecbb586dd8 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -47,8 +47,9 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); + const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const isSettled = ReportUtils.isSettled(report.reportID); - const allReportTransactions = TransactionUtils.getAllReportTransactions(report.reportID); + const allReportTransactions = TransactionUtils.getAllReportTransactions(report.reportID, transactions); const isTotalUpdated = ReportUtils.hasUpdatedTotal(report, policy, allReportTransactions); const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(report); From 4264fb4218f44903b6e632b41cbddbc83e5da156 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 31 Jul 2024 11:19:24 +0200 Subject: [PATCH 6/9] code update --- src/components/MoneyReportHeader.tsx | 2 +- src/components/ReportActionItem/MoneyReportView.tsx | 3 +-- src/components/ReportActionItem/ReportPreview.tsx | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index e0858fec53b5..47c4a0e4d856 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -98,7 +98,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [paymentType, setPaymentType] = useState(); const [requestType, setRequestType] = useState(); - const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID, transactions), [moneyRequestReport?.reportID, transactions]); + const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID), [moneyRequestReport?.reportID]); const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy, allTransactions); const policyType = policy?.type; const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 59ecbb586dd8..66ae611df6be 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -47,9 +47,8 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); - const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION); const isSettled = ReportUtils.isSettled(report.reportID); - const allReportTransactions = TransactionUtils.getAllReportTransactions(report.reportID, transactions); + const allReportTransactions = TransactionUtils.getAllReportTransactions(report.reportID); const isTotalUpdated = ReportUtils.hasUpdatedTotal(report, policy, allReportTransactions); const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(report); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 5a4a50adcba2..82406b6f741f 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -122,7 +122,7 @@ function ReportPreview({ const {translate} = useLocalize(); const {canUseViolations} = usePermissions(); const {isOffline} = useNetwork(); - const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(iouReportID, transactions), [iouReportID, transactions]); + const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(iouReportID), [iouReportID]); const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyTransactionsWithPendingRoutes, hasNonReimbursableTransactions} = useMemo( () => ({ From e8df1c9039ab090d8a9906f22e6da400bdcbb038 Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 31 Jul 2024 12:52:46 +0200 Subject: [PATCH 7/9] use reports transactions mapping & reduce diff --- src/components/MoneyReportHeader.tsx | 8 +- .../ReportActionItem/MoneyReportView.tsx | 4 +- .../ReportActionItem/ReportPreview.tsx | 28 +++---- src/libs/ReportUtils.ts | 82 ++++++++++++++---- src/libs/TransactionUtils.ts | 83 ------------------- 5 files changed, 85 insertions(+), 120 deletions(-) diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index 47c4a0e4d856..655cdcaf2443 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -99,7 +99,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const [paymentType, setPaymentType] = useState(); const [requestType, setRequestType] = useState(); const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID), [moneyRequestReport?.reportID]); - const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy, allTransactions); + const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy); const policyType = policy?.type; const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport); const connectedIntegration = PolicyUtils.getConnectedIntegration(policy); @@ -107,7 +107,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const hasScanningReceipt = ReportUtils.getTransactionsWithReceipts(moneyRequestReport?.reportID).some((t) => TransactionUtils.isReceiptBeingScanned(t)); const transactionIDs = allTransactions.map((t) => t.transactionID); const allHavePendingRTERViolation = TransactionUtils.allHavePendingRTERViolation(transactionIDs); - const hasOnlyHeldExpenses = TransactionUtils.areAllExpensesOnHold(allTransactions); + const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID); const shouldShowPayButton = useMemo(() => IOU.canIOUBePaid(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]); @@ -130,8 +130,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || allHavePendingRTERViolation || shouldShowExportIntegrationButton; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport.currency); - const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy, allTransactions); - const isAnyTransactionOnHold = TransactionUtils.isAnyTransactionOnHold(allTransactions); + const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy); + const isAnyTransactionOnHold = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID); const displayedAmount = isAnyTransactionOnHold && canAllowSettlement ? nonHeldAmount : formattedAmount; const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout); diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 66ae611df6be..7c2e29bd983d 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -17,7 +17,6 @@ import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as ReportUtils from '@libs/ReportUtils'; -import * as TransactionUtils from '@libs/TransactionUtils'; import AnimatedEmptyStateBackground from '@pages/home/report/AnimatedEmptyStateBackground'; import variables from '@styles/variables'; import * as reportActions from '@src/libs/actions/Report'; @@ -48,8 +47,7 @@ function MoneyReportView({report, policy, isCombinedReport = false, shouldShowTo const StyleUtils = useStyleUtils(); const {translate} = useLocalize(); const isSettled = ReportUtils.isSettled(report.reportID); - const allReportTransactions = TransactionUtils.getAllReportTransactions(report.reportID); - const isTotalUpdated = ReportUtils.hasUpdatedTotal(report, policy, allReportTransactions); + const isTotalUpdated = ReportUtils.hasUpdatedTotal(report, policy); const {totalDisplaySpend, nonReimbursableSpend, reimbursableSpend} = ReportUtils.getMoneyRequestSpendBreakdown(report); diff --git a/src/components/ReportActionItem/ReportPreview.tsx b/src/components/ReportActionItem/ReportPreview.tsx index 82406b6f741f..201b86c8584f 100644 --- a/src/components/ReportActionItem/ReportPreview.tsx +++ b/src/components/ReportActionItem/ReportPreview.tsx @@ -126,10 +126,10 @@ function ReportPreview({ const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyTransactionsWithPendingRoutes, hasNonReimbursableTransactions} = useMemo( () => ({ - hasMissingSmartscanFields: TransactionUtils.hasAnyTransactionMissingSmartscanFields(allTransactions), - areAllRequestsBeingSmartScanned: TransactionUtils.areAllTransactionsBeingSmartScanned(allTransactions, action), - hasOnlyTransactionsWithPendingRoutes: TransactionUtils.areAllTransactionsWithPendingRoutes(allTransactions), - hasNonReimbursableTransactions: TransactionUtils.isAnyTransactionNonReimbursable(allTransactions), + hasMissingSmartscanFields: ReportUtils.hasMissingSmartscanFields(iouReportID), + areAllRequestsBeingSmartScanned: ReportUtils.areAllRequestsBeingSmartScanned(iouReportID, action), + hasOnlyTransactionsWithPendingRoutes: ReportUtils.hasOnlyTransactionsWithPendingRoutes(iouReportID), + hasNonReimbursableTransactions: ReportUtils.hasNonReimbursableTransactions(iouReportID), }), // When transactions get updated these status may have changed, so that is a case where we also want to run this. // eslint-disable-next-line react-compiler/react-compiler, react-hooks/exhaustive-deps @@ -138,8 +138,8 @@ function ReportPreview({ const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false); const [requestType, setRequestType] = useState(); - const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(iouReport, policy, allTransactions); - const hasOnlyHeldExpenses = TransactionUtils.areAllExpensesOnHold(allTransactions); + const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(iouReport, policy); + const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(iouReport?.reportID ?? ''); const [paymentType, setPaymentType] = useState(); const managerID = iouReport?.managerID ?? action.childManagerAccountID ?? 0; @@ -152,9 +152,9 @@ function ReportPreview({ const isOpenExpenseReport = isPolicyExpenseChat && ReportUtils.isOpenExpenseReport(iouReport); const isApproved = ReportUtils.isReportApproved(iouReport, action); - const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport, policy, allTransactions); + const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport, policy); const numberOfRequests = allTransactions.length; - const transactionsWithReceipts = TransactionUtils.getTransactionsWithReceipts(allTransactions); + const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID); const numberOfScanningReceipts = transactionsWithReceipts.filter((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)).length; const numberOfPendingRequests = transactionsWithReceipts.filter((transaction) => TransactionUtils.isPending(transaction) && TransactionUtils.isCardTransaction(transaction)).length; @@ -162,10 +162,8 @@ function ReportPreview({ const isScanning = hasReceipts && areAllRequestsBeingSmartScanned; const hasErrors = (hasMissingSmartscanFields && !iouSettled) || - (canUseViolations && - (TransactionUtils.isAnyTransactionViolated(allTransactions, transactionViolations) || - // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing - TransactionUtils.hasAnyTransactionWarningViolation(allTransactions, transactionViolations))) || + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing + (canUseViolations && (ReportUtils.hasViolations(iouReportID, transactionViolations) || ReportUtils.hasWarningTypeViolations(iouReportID, transactionViolations))) || (ReportUtils.isReportOwner(iouReport) && ReportUtils.hasReportViolations(iouReportID)) || ReportUtils.hasActionsWithErrors(iouReportID); const lastThreeTransactionsWithReceipts = transactionsWithReceipts.slice(-3); @@ -196,7 +194,7 @@ function ReportPreview({ } setPaymentType(type); setRequestType(CONST.IOU.REPORT_ACTION_TYPE.PAY); - if (TransactionUtils.isAnyTransactionOnHold(allTransactions)) { + if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else if (chatReport && iouReport) { if (ReportUtils.isInvoiceReport(iouReport)) { @@ -209,7 +207,7 @@ function ReportPreview({ const confirmApproval = () => { setRequestType(CONST.IOU.REPORT_ACTION_TYPE.APPROVE); - if (TransactionUtils.isAnyTransactionOnHold(allTransactions)) { + if (ReportUtils.hasHeldExpenses(iouReport?.reportID)) { setIsHoldMenuVisible(true); } else { IOU.approveMoneyRequest(iouReport, true); @@ -221,7 +219,7 @@ function ReportPreview({ return ''; } - if (TransactionUtils.isAnyTransactionOnHold(allTransactions) && canAllowSettlement) { + if (ReportUtils.hasHeldExpenses(iouReport?.reportID) && canAllowSettlement) { return nonHeldAmount; } diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 1e215bb7c17a..64e2e13623e8 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -542,6 +542,7 @@ Onyx.connect({ }); let allTransactions: OnyxCollection = {}; +let reportsTransactions: Record; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true, @@ -550,6 +551,20 @@ Onyx.connect({ return; } allTransactions = Object.fromEntries(Object.entries(value).filter(([, transaction]) => transaction)); + + reportsTransactions = Object.values(value).reduce>((all, transaction) => { + const reportsMap = all; + if (!transaction) { + return reportsMap; + } + + if (!reportsMap[transaction.reportID]) { + reportsMap[transaction.reportID] = []; + } + reportsMap[transaction.reportID].push(transaction); + + return all; + }, {}); }, }); @@ -1406,6 +1421,20 @@ function isPolicyExpenseChatAdmin(report: OnyxEntry, policies: OnyxColle return policyRole === CONST.POLICY.ROLE.ADMIN; } +/** + * Checks whether all the transactions linked to the IOU report are of the Distance Request type with pending routes + */ +function hasOnlyTransactionsWithPendingRoutes(iouReportID: string | undefined): boolean { + const transactions = reportsTransactions[iouReportID ?? ''] ?? []; + + // Early return false in case not having any transaction + if (!transactions || transactions.length === 0) { + return false; + } + + return transactions.every((transaction) => TransactionUtils.isFetchingWaypointsFromServer(transaction)); +} + /** * Checks if the current user is the admin of the policy. */ @@ -1499,7 +1528,7 @@ function hasOnlyNonReimbursableTransactions(iouReportID: string | undefined): bo return false; } - const transactions = TransactionUtils.getAllReportTransactions(iouReportID); + const transactions = reportsTransactions[iouReportID ?? ''] ?? []; if (!transactions || transactions.length === 0) { return false; } @@ -2442,7 +2471,7 @@ function requiresAttentionFromCurrentUser(optionOrReport: OnyxEntry | Op * */ function hasNonReimbursableTransactions(iouReportID: string | undefined): boolean { - const transactions = TransactionUtils.getAllReportTransactions(iouReportID); + const transactions = reportsTransactions[iouReportID ?? ''] ?? []; return transactions.filter((transaction) => transaction.reimbursable === false).length > 0; } @@ -2913,10 +2942,26 @@ const changeMoneyRequestHoldStatus = (reportAction: OnyxEntry, bac * Gets all transactions on an IOU report with a receipt */ function getTransactionsWithReceipts(iouReportID: string | undefined): Transaction[] { - const transactions = TransactionUtils.getAllReportTransactions(iouReportID); + const transactions = reportsTransactions[iouReportID ?? ''] ?? []; return transactions.filter((transaction) => TransactionUtils.hasReceipt(transaction)); } +/** + * For report previews, we display a "Receipt scan in progress" indicator + * instead of the report total only when we have no report total ready to show. This is the case when + * all requests are receipts that are being SmartScanned. As soon as we have a non-receipt request, + * or as soon as one receipt request is done scanning, we have at least one + * "ready" expense, and we remove this indicator to show the partial report total. + */ +function areAllRequestsBeingSmartScanned(iouReportID: string, reportPreviewAction: OnyxEntry): boolean { + const transactionsWithReceipts = getTransactionsWithReceipts(iouReportID); + // If we have more requests than requests with receipts, we have some manual requests + if (ReportActionsUtils.getNumberOfMoneyRequests(reportPreviewAction) > transactionsWithReceipts.length) { + return false; + } + return transactionsWithReceipts.every((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)); +} + /** * Get the transactions related to a report preview with receipts * Get the details linked to the IOU reportAction @@ -2937,7 +2982,9 @@ function getLinkedTransaction(reportAction: OnyxEntry): boolean { - const transactions = TransactionUtils.getAllReportTransactions(reportID); + const transactions = reportsTransactions[reportID] ?? []; return transactions.some((transaction) => TransactionUtils.hasViolation(transaction.transactionID, transactionViolations)); } @@ -5502,7 +5549,7 @@ function hasViolations(reportID: string, transactionViolations: OnyxCollection): boolean { - const transactions = TransactionUtils.getAllReportTransactions(reportID); + const transactions = reportsTransactions[reportID] ?? []; return transactions.some((transaction) => TransactionUtils.hasWarningTypeViolation(transaction.transactionID, transactionViolations)); } @@ -6717,7 +6764,7 @@ function navigateToPrivateNotes(report: OnyxEntry, session: OnyxEntry TransactionUtils.isOnHold(transaction)); } @@ -6725,15 +6772,15 @@ function getAllHeldTransactions(iouReportID?: string): Transaction[] { * Check if Report has any held expenses */ function hasHeldExpenses(iouReportID?: string): boolean { - const transactions = TransactionUtils.getAllReportTransactions(iouReportID); + const transactions = reportsTransactions[iouReportID ?? ''] ?? []; return transactions.some((transaction) => TransactionUtils.isOnHold(transaction)); } /** * Check if all expenses in the Report are on hold */ -function hasOnlyHeldExpenses(iouReportID: string, transactions?: OnyxCollection): boolean { - const reportTransactions = TransactionUtils.getAllReportTransactions(iouReportID, transactions); +function hasOnlyHeldExpenses(iouReportID: string): boolean { + const reportTransactions = reportsTransactions[iouReportID ?? ''] ?? []; return reportTransactions.length > 0 && !reportTransactions.some((transaction) => !TransactionUtils.isOnHold(transaction)); } @@ -6748,15 +6795,17 @@ function shouldDisplayThreadReplies(reportAction: OnyxInputOrEntry /** * Check if money report has any transactions updated optimistically */ -function hasUpdatedTotal(report: OnyxInputOrEntry, policy: OnyxInputOrEntry, allReportTransactions: Transaction[]): boolean { +function hasUpdatedTotal(report: OnyxInputOrEntry, policy: OnyxInputOrEntry): boolean { if (!report) { return true; } + const allReportTransactions = reportsTransactions[report.reportID] ?? []; + const hasPendingTransaction = allReportTransactions.some((transaction) => !!transaction.pendingAction); const hasTransactionWithDifferentCurrency = allReportTransactions.some((transaction) => transaction.currency !== report.currency); const hasDifferentWorkspaceCurrency = report.pendingFields?.createChat && isExpenseReport(report) && report.currency !== policy?.outputCurrency; - const hasOptimisticHeldExpense = TransactionUtils.isAnyTransactionOnHold(allReportTransactions) && report?.unheldTotal === undefined; + const hasOptimisticHeldExpense = hasHeldExpenses(report.reportID) && report?.unheldTotal === undefined; return !(hasPendingTransaction && (hasTransactionWithDifferentCurrency || hasDifferentWorkspaceCurrency)) && !hasOptimisticHeldExpense; } @@ -6764,13 +6813,14 @@ function hasUpdatedTotal(report: OnyxInputOrEntry, policy: OnyxInputOrEn /** * Return held and full amount formatted with used currency */ -function getNonHeldAndFullAmount(iouReport: OnyxEntry, policy: OnyxEntry, reportTransactions: Transaction[]): string[] { +function getNonHeldAndFullAmount(iouReport: OnyxEntry, policy: OnyxEntry): string[] { + const reportTransactions = reportsTransactions[iouReport?.reportID ?? ''] ?? []; const hasPendingTransaction = reportTransactions.some((transaction) => !!transaction.pendingAction); // if the report is an expense report, the total amount should be negated const coefficient = isExpenseReport(iouReport) ? -1 : 1; - if (hasUpdatedTotal(iouReport, policy, reportTransactions) && hasPendingTransaction) { + if (hasUpdatedTotal(iouReport, policy) && hasPendingTransaction) { const unheldTotal = reportTransactions.reduce((currentVal, transaction) => currentVal - (!TransactionUtils.isOnHold(transaction) ? transaction.amount : 0), 0); return [CurrencyUtils.convertToDisplayString(unheldTotal, iouReport?.currency), CurrencyUtils.convertToDisplayString((iouReport?.total ?? 0) * coefficient, iouReport?.currency)]; @@ -7011,7 +7061,7 @@ function getTripTransactions(tripRoomReportID: string | undefined, reportFieldTo const tripTransactionReportIDs = Object.values(ReportConnection.getAllReports() ?? {}) .filter((report) => report && report?.[reportFieldToCompare] === tripRoomReportID) .map((report) => report?.reportID); - return tripTransactionReportIDs.flatMap((reportID) => TransactionUtils.getAllReportTransactions(reportID)); + return tripTransactionReportIDs.flatMap((reportID) => reportsTransactions[reportID ?? ''] ?? []); } function getTripIDFromTransactionParentReport(transactionParentReport: OnyxEntry | undefined | null): string | undefined { @@ -7320,6 +7370,7 @@ function isExported(reportActions: OnyxEntry) { export { addDomainToShortMention, completeShortMention, + areAllRequestsBeingSmartScanned, buildOptimisticAddCommentReportAction, buildOptimisticApprovedReportAction, buildOptimisticUnapprovedReportAction, @@ -7471,6 +7522,7 @@ export { hasMissingSmartscanFields, hasNonReimbursableTransactions, hasOnlyHeldExpenses, + hasOnlyTransactionsWithPendingRoutes, hasReportNameError, hasSmartscanError, hasUpdatedTotal, diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index b7de4d2704d2..0fe193bbadb3 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -711,13 +711,6 @@ function hasViolation(transactionID: string, transactionViolations: OnyxCollecti ); } -/** - * Checks to see if any transaction contains a violation - */ -function isAnyTransactionViolated(transactions: Transaction[], transactionViolations: OnyxCollection): boolean { - return transactions.some((transaction) => hasViolation(transaction.transactionID, transactionViolations)); -} - /** * Checks if any violations for the provided transaction are of type 'notice' */ @@ -999,73 +992,6 @@ function buildTransactionsMergeParams(reviewDuplicates: OnyxEntry isOnHold(transaction)); -} - -/** - * Check if any of the transactions is non-reimbursable - */ -function isAnyTransactionNonReimbursable(transactions: Transaction[]): boolean { - return transactions.some((transaction) => transaction.reimbursable === false); -} - -/** - * Checks to see if any of transactions contain a violation of type `warning` - */ -function hasAnyTransactionWarningViolation(transactions: Transaction[], transactionViolations: OnyxCollection): boolean { - return transactions.some((transaction) => hasWarningTypeViolation(transaction.transactionID, transactionViolations)); -} - -/** - * Filters only transactions with a receipt - */ -function getTransactionsWithReceipts(transactions: Transaction[]): Transaction[] { - return transactions.filter((transaction) => hasReceipt(transaction)); -} - -/** - * Check if all expenses in the Report are on hold - */ -function areAllExpensesOnHold(transactions: Transaction[]) { - return transactions.length > 0 && !transactions.some((transaction) => !isOnHold(transaction)); -} - -/** - * Checks whether all the transactions are of the Distance Request type with pending routes - */ -function areAllTransactionsWithPendingRoutes(transactions: Transaction[]) { - if (!transactions || transactions.length === 0) { - return false; - } - - return transactions.every((transaction) => isFetchingWaypointsFromServer(transaction)); -} - -/** - * Check if any of the transactions has required missing fields - */ -function hasAnyTransactionMissingSmartscanFields(transactions: Transaction[]): boolean { - return transactions.some(hasMissingSmartscanFields); -} - -/** - * Check if all transactions are being smart scanned - */ -function areAllTransactionsBeingSmartScanned(transactions: Transaction[], reportPreviewAction: OnyxEntry) { - const transactionsWithReceipts = getTransactionsWithReceipts(transactions); - - // If we have more requests than requests with receipts, we have some manual requests - if (ReportActionsUtils.getNumberOfMoneyRequests(reportPreviewAction) > transactionsWithReceipts.length) { - return false; - } - - return transactionsWithReceipts.every((transaction) => isReceiptBeingScanned(transaction)); -} - export { buildOptimisticTransaction, calculateTaxAmount, @@ -1139,15 +1065,6 @@ export { buildNewTransactionAfterReviewingDuplicates, buildTransactionsMergeParams, getReimbursable, - isAnyTransactionOnHold, - isAnyTransactionNonReimbursable, - isAnyTransactionViolated, - hasAnyTransactionWarningViolation, - getTransactionsWithReceipts, - areAllExpensesOnHold, - areAllTransactionsWithPendingRoutes, - hasAnyTransactionMissingSmartscanFields, - areAllTransactionsBeingSmartScanned, }; export type {TransactionChanges}; From 2714eec16d1b361a4c2e97063da667c36aa6209d Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 31 Jul 2024 12:55:50 +0200 Subject: [PATCH 8/9] reduce diff --- src/libs/ReportUtils.ts | 18 +++++++++--------- src/libs/TransactionUtils.ts | 14 +------------- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 64e2e13623e8..2e6516a881cc 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -1421,6 +1421,15 @@ function isPolicyExpenseChatAdmin(report: OnyxEntry, policies: OnyxColle return policyRole === CONST.POLICY.ROLE.ADMIN; } +/** + * Checks if the current user is the admin of the policy. + */ +function isPolicyAdmin(policyID: string, policies: OnyxCollection): boolean { + const policyRole = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.role; + + return policyRole === CONST.POLICY.ROLE.ADMIN; +} + /** * Checks whether all the transactions linked to the IOU report are of the Distance Request type with pending routes */ @@ -1435,15 +1444,6 @@ function hasOnlyTransactionsWithPendingRoutes(iouReportID: string | undefined): return transactions.every((transaction) => TransactionUtils.isFetchingWaypointsFromServer(transaction)); } -/** - * Checks if the current user is the admin of the policy. - */ -function isPolicyAdmin(policyID: string, policies: OnyxCollection): boolean { - const policyRole = policies?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`]?.role; - - return policyRole === CONST.POLICY.ROLE.ADMIN; -} - /** * If the report is a thread and has a chat type set, it is a workspace chat. */ diff --git a/src/libs/TransactionUtils.ts b/src/libs/TransactionUtils.ts index 0fe193bbadb3..c044702b2fc0 100644 --- a/src/libs/TransactionUtils.ts +++ b/src/libs/TransactionUtils.ts @@ -5,19 +5,7 @@ import Onyx from 'react-native-onyx'; import type {ValueOf} from 'type-fest'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type { - Beta, - OnyxInputOrEntry, - Policy, - RecentWaypoint, - ReportAction, - ReviewDuplicates, - TaxRate, - TaxRates, - Transaction, - TransactionViolation, - TransactionViolations, -} from '@src/types/onyx'; +import type {Beta, OnyxInputOrEntry, Policy, RecentWaypoint, ReviewDuplicates, TaxRate, TaxRates, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import type {IOURequestType} from './actions/IOU'; From 6a229330eaab9e48a58525ca3d77d06b0d30838e Mon Sep 17 00:00:00 2001 From: Tomasz Misiukiewicz Date: Wed, 31 Jul 2024 13:10:34 +0200 Subject: [PATCH 9/9] fix tests --- src/libs/ReportUtils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libs/ReportUtils.ts b/src/libs/ReportUtils.ts index 2e6516a881cc..4bc0238bcaac 100644 --- a/src/libs/ReportUtils.ts +++ b/src/libs/ReportUtils.ts @@ -542,7 +542,7 @@ Onyx.connect({ }); let allTransactions: OnyxCollection = {}; -let reportsTransactions: Record; +let reportsTransactions: Record = {}; Onyx.connect({ key: ONYXKEYS.COLLECTION.TRANSACTION, waitForCollectionCallback: true,