Skip to content
14 changes: 7 additions & 7 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -98,17 +98,16 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const [isHoldMenuVisible, setIsHoldMenuVisible] = useState(false);
const [paymentType, setPaymentType] = useState<PaymentMethodType>();
const [requestType, setRequestType] = useState<ActionHandledType>();
const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(moneyRequestReport?.reportID), [moneyRequestReport?.reportID]);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This prevents the header from being refreshed when transactions for the report change causing #47084

const canAllowSettlement = ReportUtils.hasUpdatedTotal(moneyRequestReport, policy);
const policyType = policy?.type;
const isDraft = ReportUtils.isOpenExpenseReport(moneyRequestReport);
const connectedIntegration = PolicyUtils.getConnectedIntegration(policy);

const navigateBackToAfterDelete = useRef<Route>();
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);
const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport.reportID);

const shouldShowPayButton = useMemo(() => IOU.canIOUBePaid(moneyRequestReport, chatReport, policy), [moneyRequestReport, chatReport, policy]);

Expand All @@ -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);
const displayedAmount = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID) && canAllowSettlement ? nonHeldAmount : formattedAmount;
const isAnyTransactionOnHold = ReportUtils.hasHeldExpenses(moneyRequestReport.reportID);
const displayedAmount = isAnyTransactionOnHold && canAllowSettlement ? nonHeldAmount : formattedAmount;
const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout);

const confirmPayment = (type?: PaymentMethodType | undefined) => {
Expand All @@ -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 (isAnyTransactionOnHold) {
setIsHoldMenuVisible(true);
} else if (ReportUtils.isInvoiceReport(moneyRequestReport)) {
IOU.payInvoice(type, chatReport, moneyRequestReport);
Expand All @@ -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 (isAnyTransactionOnHold) {
setIsHoldMenuVisible(true);
} else {
IOU.approveMoneyRequest(moneyRequestReport, true);
Expand Down
2 changes: 1 addition & 1 deletion src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ function ReportPreview({
const {translate} = useLocalize();
const {canUseViolations} = usePermissions();
const {isOffline} = useNetwork();
const allTransactions = useMemo(() => TransactionUtils.getAllReportTransactions(iouReportID), [iouReportID]);

const {hasMissingSmartscanFields, areAllRequestsBeingSmartScanned, hasOnlyTransactionsWithPendingRoutes, hasNonReimbursableTransactions} = useMemo(
() => ({
Expand Down Expand Up @@ -152,7 +153,6 @@ function ReportPreview({

const isApproved = ReportUtils.isReportApproved(iouReport, action);
const canAllowSettlement = ReportUtils.hasUpdatedTotal(iouReport, policy);
const allTransactions = TransactionUtils.getAllReportTransactions(iouReportID);
const numberOfRequests = allTransactions.length;
const transactionsWithReceipts = ReportUtils.getTransactionsWithReceipts(iouReportID);
const numberOfScanningReceipts = transactionsWithReceipts.filter((transaction) => TransactionUtils.isReceiptBeingScanned(transaction)).length;
Expand Down
54 changes: 36 additions & 18 deletions src/libs/ReportUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -542,6 +542,7 @@ Onyx.connect({
});

let allTransactions: OnyxCollection<Transaction> = {};
let reportsTransactions: Record<string, Transaction[]> = {};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

NAB: Maybe rename to reportIDsToTransactions?

Onyx.connect({
key: ONYXKEYS.COLLECTION.TRANSACTION,
waitForCollectionCallback: true,
Expand All @@ -550,6 +551,20 @@ Onyx.connect({
return;
}
allTransactions = Object.fromEntries(Object.entries(value).filter(([, transaction]) => transaction));

reportsTransactions = Object.values(value).reduce<Record<string, Transaction[]>>((all, transaction) => {
const reportsMap = all;
if (!transaction) {
return reportsMap;
}

if (!reportsMap[transaction.reportID]) {
reportsMap[transaction.reportID] = [];
}
reportsMap[transaction.reportID].push(transaction);

return all;
}, {});
},
});

Expand Down Expand Up @@ -1419,7 +1434,7 @@ function isPolicyAdmin(policyID: string, policies: OnyxCollection<Policy>): bool
* 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);
const transactions = reportsTransactions[iouReportID ?? ''] ?? [];

// Early return false in case not having any transaction
if (!transactions || transactions.length === 0) {
Expand Down Expand Up @@ -1513,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;
}
Expand Down Expand Up @@ -2456,7 +2471,7 @@ function requiresAttentionFromCurrentUser(optionOrReport: OnyxEntry<Report> | 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;
}

Expand Down Expand Up @@ -2927,7 +2942,7 @@ const changeMoneyRequestHoldStatus = (reportAction: OnyxEntry<ReportAction>, 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));
}

Expand Down Expand Up @@ -2967,7 +2982,9 @@ function getLinkedTransaction(reportAction: OnyxEntry<ReportAction | OptimisticI
* Check if any of the transactions in the report has required missing fields
*/
function hasMissingSmartscanFields(iouReportID: string): boolean {
return TransactionUtils.getAllReportTransactions(iouReportID).some(TransactionUtils.hasMissingSmartscanFields);
const reportTransactions = reportsTransactions[iouReportID] ?? [];

return reportTransactions.some(TransactionUtils.hasMissingSmartscanFields);
}

/**
Expand Down Expand Up @@ -5524,15 +5541,15 @@ function shouldDisplayTransactionThreadViolations(
* Checks to see if a report contains a violation
*/
function hasViolations(reportID: string, transactionViolations: OnyxCollection<TransactionViolation[]>): boolean {
const transactions = TransactionUtils.getAllReportTransactions(reportID);
const transactions = reportsTransactions[reportID] ?? [];
return transactions.some((transaction) => TransactionUtils.hasViolation(transaction.transactionID, transactionViolations));
}

/**
* Checks to see if a report contains a violation of type `warning`
*/
function hasWarningTypeViolations(reportID: string, transactionViolations: OnyxCollection<TransactionViolation[]>): boolean {
const transactions = TransactionUtils.getAllReportTransactions(reportID);
const transactions = reportsTransactions[reportID] ?? [];
return transactions.some((transaction) => TransactionUtils.hasWarningTypeViolation(transaction.transactionID, transactionViolations));
}

Expand Down Expand Up @@ -6747,23 +6764,23 @@ function navigateToPrivateNotes(report: OnyxEntry<Report>, session: OnyxEntry<Se
* Get all held transactions of a iouReport
*/
function getAllHeldTransactions(iouReportID?: string): Transaction[] {
const transactions = TransactionUtils.getAllReportTransactions(iouReportID);
const transactions = reportsTransactions[iouReportID ?? ''] ?? [];
return transactions.filter((transaction) => TransactionUtils.isOnHold(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<Transaction>): 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));
}

Expand All @@ -6783,9 +6800,10 @@ function hasUpdatedTotal(report: OnyxInputOrEntry<Report>, policy: OnyxInputOrEn
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 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 = hasHeldExpenses(report.reportID) && report?.unheldTotal === undefined;

Expand All @@ -6796,14 +6814,14 @@ function hasUpdatedTotal(report: OnyxInputOrEntry<Report>, policy: OnyxInputOrEn
* Return held and full amount formatted with used currency
*/
function getNonHeldAndFullAmount(iouReport: OnyxEntry<Report>, policy: OnyxEntry<Policy>): string[] {
const transactions = TransactionUtils.getAllReportTransactions(iouReport?.reportID ?? '-1');
const hasPendingTransaction = transactions.some((transaction) => !!transaction.pendingAction);
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) && hasPendingTransaction) {
const unheldTotal = transactions.reduce((currentVal, transaction) => currentVal - (!TransactionUtils.isOnHold(transaction) ? transaction.amount : 0), 0);
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)];
}
Expand Down Expand Up @@ -7043,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<Report> | undefined | null): string | undefined {
Expand Down