Skip to content

Commit 80f2d6a

Browse files
authored
Merge pull request Expensify#89387 from Rkdev09/fix/87971-approval-modal-held-expense-amounts
fix: use live transactions for held-expense approval/pay flows
2 parents 3616aa2 + c838f2b commit 80f2d6a

13 files changed

Lines changed: 42 additions & 36 deletions

src/components/MoneyReportHeaderModals.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,8 @@ function MoneyReportHeaderModals({reportID, children}: MoneyReportHeaderModalsPr
4646
const canIOUBePaid = canIOUBePaidAction(moneyRequestReport, chatReport, policy, bankAccountList, currentUserLogin ?? '', accountID);
4747
const onlyShowPayElsewhere = !canIOUBePaid && canIOUBePaidAction(moneyRequestReport, chatReport, policy, bankAccountList, currentUserLogin ?? '', accountID, undefined, true);
4848
const shouldShowPayButton = canIOUBePaid || onlyShowPayElsewhere;
49-
const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(moneyRequestReport, shouldShowPayButton);
50-
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(moneyRequestReport?.reportID, transactions);
49+
const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(moneyRequestReport, shouldShowPayButton, transactions);
50+
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(transactions);
5151
const transactionIDs = transactions.map((t) => t.transactionID);
5252

5353
// Imperative modals

src/components/ReportActionItem/MoneyRequestReportPreview/MoneyRequestReportPreviewContent.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ function MoneyRequestReportPreviewContent({
185185
const [requestType, setRequestType] = useState<ActionHandledType>();
186186
const [paymentType, setPaymentType] = useState<PaymentMethodType>();
187187
const [shouldShowPayButton, setShouldShowPayButton] = useState(false);
188-
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(iouReport?.reportID);
188+
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(transactions);
189189

190190
const handleHoldMenuOpen = (holdRequestType: string, holdPaymentType?: PaymentMethodType, canPay?: boolean) => {
191191
setRequestType(holdRequestType as ActionHandledType);
@@ -772,7 +772,7 @@ function MoneyRequestReportPreviewContent({
772772
!!iouReport &&
773773
!!requestType &&
774774
(() => {
775-
const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(iouReport, shouldShowPayButton);
775+
const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(iouReport, shouldShowPayButton, transactions);
776776
return (
777777
<ProcessMoneyReportHoldMenu
778778
nonHeldAmount={!hasOnlyHeldExpenses && hasValidNonHeldAmount ? nonHeldAmount : undefined}

src/components/Search/SearchList/ListItem/ExpenseReportListItem.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
2121
import useStyleUtils from '@hooks/useStyleUtils';
2222
import useTheme from '@hooks/useTheme';
2323
import useThemeStyles from '@hooks/useThemeStyles';
24+
import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViolationsForReport';
2425
import {handleActionButtonPress} from '@libs/actions/Search';
2526
import {syncMissingAttendeesViolation} from '@libs/AttendeeUtils';
2627
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
@@ -142,6 +143,8 @@ function ExpenseReportListItem<TItem extends ListItem>({
142143
const {showDelegateNoAccessModal} = useDelegateNoAccessActions();
143144
const [amountOwed] = useOnyx(ONYXKEYS.NVP_PRIVATE_AMOUNT_OWED);
144145
const {showHoldMenu} = useHoldMenuModal();
146+
const {transactions: reportTransactions} = useTransactionsAndViolationsForReport(reportItem.reportID);
147+
const liveReportTransactions = useMemo(() => Object.values(reportTransactions), [reportTransactions]);
145148

146149
const handleOnButtonPress = useCallback(() => {
147150
handleActionButtonPress({
@@ -162,8 +165,13 @@ function ExpenseReportListItem<TItem extends ListItem>({
162165
// collection yet. Fall back to the snapshot so the modal can submit.
163166
const moneyRequestReport = parentReport ?? snapshotReport;
164167
const chatReport = parentChatReport ?? snapshotChatReport;
165-
const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(moneyRequestReport, holdItem.allActions?.includes(CONST.SEARCH.ACTION_TYPES.PAY) ?? false);
166-
const hasNonHeldExpenses = holdItem.transactions.some((t) => !isOnHold(t));
168+
const transactionsForHoldMenu = liveReportTransactions.length > 0 ? liveReportTransactions : holdItem.transactions;
169+
const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(
170+
moneyRequestReport,
171+
holdItem.allActions?.includes(CONST.SEARCH.ACTION_TYPES.PAY) ?? false,
172+
transactionsForHoldMenu,
173+
);
174+
const hasNonHeldExpenses = transactionsForHoldMenu.some((t) => !isOnHold(t));
167175
showHoldMenu({
168176
reportID: holdItem.reportID,
169177
chatReportID: holdItem.parentReportID,
@@ -174,7 +182,7 @@ function ExpenseReportListItem<TItem extends ListItem>({
174182
nonHeldAmount: hasNonHeldExpenses && hasValidNonHeldAmount ? nonHeldAmount : undefined,
175183
fullAmount,
176184
hasNonHeldExpenses,
177-
transactionCount: holdItem.transactionCount ?? 0,
185+
transactionCount: transactionsForHoldMenu.length > 0 ? transactionsForHoldMenu.length : (holdItem.transactionCount ?? 0),
178186
});
179187
},
180188
ownerBillingGracePeriodEnd,
@@ -197,6 +205,7 @@ function ExpenseReportListItem<TItem extends ListItem>({
197205
isDelegateAccessRestricted,
198206
showDelegateNoAccessModal,
199207
showHoldMenu,
208+
liveReportTransactions,
200209
ownerBillingGracePeriodEnd,
201210
amountOwed,
202211
]);

src/hooks/useMoneyReportHeaderStatusBar.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ function useMoneyReportHeaderStatusBar(reportID: string | undefined, chatReportI
7474
const hasOnlyPendingTransactions = transactions.length > 0 && transactions.every((t) => isExpensifyCardTransaction(t) && isPending(t));
7575
const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactions, violations, email ?? '', accountID, moneyRequestReport, policy);
7676
const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationForMultipleTransactions(transactions, moneyRequestReport, policy, violations, email ?? '', accountID);
77-
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(moneyRequestReport?.reportID, transactions);
77+
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(transactions);
7878
const isPayAtEndExpense = isPayAtEndExpenseTransactionUtils(transaction);
7979
const hasDuplicates = hasDuplicateTransactions(email ?? '', accountID, moneyRequestReport, policy, allTransactionViolations);
8080
const shouldShowMarkAsResolved = isMarkAsResolvedAction(moneyRequestReport, transactionViolations);

src/hooks/useSelectionModeReportActions.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ function useSelectionModeReportActions({
170170

171171
const shouldShowPayButton = canIOUBePaid || onlyShowPayElsewhere;
172172

173-
const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(report, shouldShowPayButton);
173+
const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(report, shouldShowPayButton, transactions);
174174

175175
const shouldShowApproveButton = canApproveIOU(report, policy, reportMetadata, currentUserAccountID, transactions) && !hasOnlyPendingTransactions;
176176

@@ -564,7 +564,7 @@ function useSelectionModeReportActions({
564564
canAllowSettlement,
565565
isAnyTransactionOnHold,
566566
isInvoiceReport,
567-
hasOnlyHeldExpenses: hasOnlyHeldExpensesReportUtils(report?.reportID, transactions),
567+
hasOnlyHeldExpenses: hasOnlyHeldExpensesReportUtils(transactions),
568568
nonHeldAmount,
569569
fullAmount,
570570
hasValidNonHeldAmount,

src/libs/MoneyRequestReportUtils.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,8 +168,8 @@ const getTotalAmountForIOUReportPreviewButton = (
168168
convertToDisplayString: CurrencyListActionsContextType['convertToDisplayString'],
169169
) => {
170170
// Determine whether the non-held amount is appropriate to display for the PAY button.
171-
const {nonHeldAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(report, reportPreviewAction === CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY);
172-
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(report?.reportID);
171+
const {nonHeldAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(report, reportPreviewAction === CONST.REPORT.REPORT_PREVIEW_ACTIONS.PAY, transactions);
172+
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(transactions);
173173
const canAllowSettlement = hasUpdatedTotal(report, policy);
174174

175175
// Split the total spend into different categories as needed.
@@ -187,7 +187,7 @@ const getTotalAmountForIOUReportPreviewButton = (
187187
}
188188

189189
// We shouldn't display the nonHeldAmount as the default option if it's not valid since we cannot pay partially in this case
190-
if (hasHeldExpensesReportUtils(report?.reportID) && canAllowSettlement && hasValidNonHeldAmount && !hasOnlyHeldExpenses) {
190+
if (hasHeldExpensesReportUtils(transactions) && canAllowSettlement && hasValidNonHeldAmount && !hasOnlyHeldExpenses) {
191191
return nonHeldAmount;
192192
}
193193

src/libs/ReportPrimaryActionUtils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ function getAllExpensesToHoldIfApplicable(
421421
policy: OnyxEntry<Policy>,
422422
currentUserAccountID: number | undefined,
423423
) {
424-
if (!report || !reportActions || !hasOnlyHeldExpenses(report?.reportID)) {
424+
if (!report || !reportActions || !hasOnlyHeldExpenses(reportTransactions)) {
425425
return [];
426426
}
427427

@@ -475,7 +475,7 @@ function getReportPrimaryAction(params: GetReportPrimaryActionParams): ValueOf<t
475475
isChatReportArchived,
476476
invoiceReceiverPolicy,
477477
reportActions,
478-
}) && hasOnlyHeldExpenses(report?.reportID);
478+
}) && hasOnlyHeldExpenses(reportTransactions);
479479
const expensesToHold = getAllExpensesToHoldIfApplicable(report, reportActions, reportTransactions, policy, currentUserAccountID);
480480

481481
if (isMarkAsCashAction(currentUserLogin, currentUserAccountID, report, reportTransactions, violations, policy)) {

src/libs/ReportSecondaryActionUtils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ function getSecondaryReportActions({
907907
reportActions,
908908
isSecondaryAction: true,
909909
}) &&
910-
(hasOnlyHeldExpenses(report?.reportID) || didExportFail)
910+
(hasOnlyHeldExpenses(reportTransactions) || didExportFail)
911911
) {
912912
options.push(CONST.REPORT.SECONDARY_ACTIONS.PAY);
913913
}

src/libs/ReportUtils.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -10826,10 +10826,8 @@ function getAllHeldTransactions(iouReportID?: string): Transaction[] {
1082610826
*
1082710827
* @warning Use `hasHeldExpensesFromTransactions` instead.
1082810828
*/
10829-
function hasHeldExpenses(iouReportID?: string, allReportTransactions?: Transaction[]): boolean {
10830-
const iouReportTransactions = getReportTransactions(iouReportID);
10831-
const transactions = allReportTransactions ?? iouReportTransactions;
10832-
return transactions.some((transaction) => isOnHoldTransactionUtils(transaction));
10829+
function hasHeldExpenses(allReportTransactions: Transaction[]): boolean {
10830+
return allReportTransactions.some((transaction) => isOnHoldTransactionUtils(transaction));
1083310831
}
1083410832

1083510833
/**
@@ -10842,10 +10840,8 @@ function hasHeldExpensesFromTransactions(allReportTransactions: Transaction[]):
1084210840
/**
1084310841
* Check if all expenses in the Report are on hold
1084410842
*/
10845-
function hasOnlyHeldExpenses(iouReportID?: string, allReportTransactions?: Transaction[]): boolean {
10846-
const transactionsByIouReportID = getReportTransactions(iouReportID);
10847-
const reportTransactions = allReportTransactions ?? transactionsByIouReportID;
10848-
return reportTransactions.length > 0 && !reportTransactions.some((transaction) => !isOnHoldTransactionUtils(transaction));
10843+
function hasOnlyHeldExpenses(allReportTransactions: Transaction[]): boolean {
10844+
return allReportTransactions.length > 0 && !allReportTransactions.some((transaction) => !isOnHoldTransactionUtils(transaction));
1084910845
}
1085010846

1085110847
/**
@@ -10869,15 +10865,15 @@ function hasUpdatedTotal(report: OnyxInputOrEntry<Report>, policy: OnyxInputOrEn
1086910865
const hasPendingTransaction = allReportTransactions.some((transaction) => !!transaction.pendingAction);
1087010866
const hasTransactionWithDifferentCurrency = allReportTransactions.some((transaction) => transaction.currency !== report.currency);
1087110867
const hasDifferentWorkspaceCurrency = report.pendingFields?.createChat && isExpenseReport(report) && report.currency !== policy?.outputCurrency;
10872-
const hasOptimisticHeldExpense = hasHeldExpenses(report.reportID) && report?.unheldTotal === undefined;
10868+
const hasOptimisticHeldExpense = hasHeldExpenses(allReportTransactions) && report?.unheldTotal === undefined;
1087310869

1087410870
return !(hasPendingTransaction && (hasTransactionWithDifferentCurrency || hasDifferentWorkspaceCurrency)) && !hasOptimisticHeldExpense && !report.pendingFields?.total;
1087510871
}
1087610872

1087710873
/**
1087810874
* Return held and full amount formatted with used currency
1087910875
*/
10880-
function getNonHeldAndFullAmount(iouReport: OnyxEntry<Report>, shouldExcludeNonReimbursables: boolean): NonHeldAndFullAmount {
10876+
function getNonHeldAndFullAmount(iouReport: OnyxEntry<Report>, shouldExcludeNonReimbursables: boolean, allReportTransactions: Transaction[]): NonHeldAndFullAmount {
1088110877
// if the report is an expense report, the total amount should be negated
1088210878
const coefficient = isExpenseReport(iouReport) ? -1 : 1;
1088310879

@@ -10895,7 +10891,7 @@ function getNonHeldAndFullAmount(iouReport: OnyxEntry<Report>, shouldExcludeNonR
1089510891
// 1. There should be held expenses
1089610892
// 2. For expense reports with negative totals, we need to ensure the unheld amount is valid
1089710893
// by checking that the absolute values are meaningful and different
10898-
const hasHeldExpensesLocal = hasHeldExpenses(iouReport?.reportID);
10894+
const hasHeldExpensesLocal = hasHeldExpenses(allReportTransactions);
1089910895
const hasValidNonHeldAmount =
1090010896
hasHeldExpensesLocal &&
1090110897
// For normal cases (positive amounts or IOU reports)

src/libs/SearchUIUtils.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2373,14 +2373,13 @@ function getActions(
23732373
const shouldOnlyShowElsewhere = !canBePaid && canOnlyBePaidElsewhere;
23742374

23752375
// We're not supporting pay partial amount on search page now.
2376-
if ((canBePaid || shouldOnlyShowElsewhere) && !hasHeldExpenses(report.reportID, allReportTransactions)) {
2376+
if ((canBePaid || shouldOnlyShowElsewhere) && !hasHeldExpenses(allReportTransactions)) {
23772377
allActions.push(CONST.SEARCH.ACTION_TYPES.PAY);
23782378
}
23792379

23802380
if (isExportAvailable) {
23812381
allActions.push(CONST.SEARCH.ACTION_TYPES.EXPORT_TO_ACCOUNTING);
23822382
}
2383-
23842383
if (isClosedReport(report) && !(canBePaid || shouldOnlyShowElsewhere)) {
23852384
return allActions.length > 0 ? allActions : [CONST.SEARCH.ACTION_TYPES.DONE];
23862385
}

0 commit comments

Comments
 (0)