Skip to content

Commit 76520a6

Browse files
authored
Merge pull request #60861 from Expensify/cherry-pick-staging-60824-14649716162-1
🍒 Cherry pick PR #60824 to staging 🍒
2 parents 05cfcb2 + 4da0102 commit 76520a6

4 files changed

Lines changed: 177 additions & 131 deletions

File tree

src/components/MoneyReportHeader.tsx

Lines changed: 38 additions & 109 deletions
Original file line numberDiff line numberDiff line change
@@ -7,41 +7,45 @@ import useLocalize from '@hooks/useLocalize';
77
import useNetwork from '@hooks/useNetwork';
88
import usePaymentAnimations from '@hooks/usePaymentAnimations';
99
import useResponsiveLayout from '@hooks/useResponsiveLayout';
10+
import useSelectedTransactionsActions from '@hooks/useSelectedTransactionsActions';
1011
import useTheme from '@hooks/useTheme';
1112
import useThemeStyles from '@hooks/useThemeStyles';
12-
import {exportReportToCSV} from '@libs/actions/Report';
13-
import {convertToDisplayString} from '@libs/CurrencyUtils';
13+
import {getIOUReportPreviewButtonType, getTotalAmountForIOUReportPreviewButton} from '@libs/MoneyRequestReportUtils';
1414
import Navigation from '@libs/Navigation/Navigation';
1515
import {buildOptimisticNextStepForPreventSelfApprovalsEnabled} from '@libs/NextStepUtils';
1616
import {getConnectedIntegration} from '@libs/PolicyUtils';
17-
import {getIOUActionForTransactionID, getOriginalMessage, isDeletedAction, isMoneyRequestAction, isTrackExpenseAction} from '@libs/ReportActionsUtils';
17+
import {getOriginalMessage, isDeletedAction, isMoneyRequestAction, isTrackExpenseAction} from '@libs/ReportActionsUtils';
1818
import {
1919
canBeExported,
20-
canDeleteCardTransactionByLiabilityType,
2120
canDeleteTransaction,
2221
getArchiveReason,
2322
getBankAccountRoute,
24-
getMoneyRequestSpendBreakdown,
2523
getNonHeldAndFullAmount,
2624
getTransactionsWithReceipts,
25+
hasActionsWithErrors,
2726
hasHeldExpenses as hasHeldExpensesReportUtils,
27+
hasMissingSmartscanFields,
28+
hasNoticeTypeViolations,
2829
hasOnlyHeldExpenses as hasOnlyHeldExpensesReportUtils,
30+
hasReportViolations,
2931
hasUpdatedTotal,
32+
hasViolations,
33+
hasWarningTypeViolations,
3034
isAllowedToApproveExpenseReport,
3135
isAllowedToSubmitDraftExpenseReport,
3236
isArchivedReportWithID,
3337
isInvoiceReport,
3438
isProcessingReport,
3539
isReportApproved,
3640
isReportOwner,
41+
isSettled,
3742
isWaitingForSubmissionFromCurrentUser as isWaitingForSubmissionFromCurrentUserReportUtils,
3843
navigateBackOnDeleteTransaction,
3944
reportTransactionsSelector,
4045
} from '@libs/ReportUtils';
4146
import {
4247
allHavePendingRTERViolation,
4348
checkIfShouldShowMarkAsCashButton,
44-
getTransaction,
4549
hasDuplicateTransactions,
4650
isDuplicate as isDuplicateTransactionUtils,
4751
isExpensifyCardTransaction,
@@ -64,7 +68,6 @@ import {
6468
payInvoice,
6569
payMoneyRequest,
6670
submitReport,
67-
unholdRequest,
6871
} from '@userActions/IOU';
6972
import {markAsCash as markAsCashAction} from '@userActions/Transaction';
7073
import CONST from '@src/CONST';
@@ -123,12 +126,10 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
123126
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
124127
const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout();
125128
const route = useRoute();
126-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
127129
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${moneyRequestReport?.chatReportID}`, {canBeMissing: true});
128-
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
129130
const [nextStep] = useOnyx(`${ONYXKEYS.COLLECTION.NEXT_STEP}${moneyRequestReport?.reportID}`, {canBeMissing: true});
130131
const [transactionThreadReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${transactionThreadReportID}`, {canBeMissing: true});
131-
const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: true});
132+
const [session] = useOnyx(ONYXKEYS.SESSION, {canBeMissing: false});
132133
const requestParentReportAction = useMemo(() => {
133134
if (!reportActions || !transactionThreadReport?.parentReportActionID) {
134135
return null;
@@ -152,7 +153,6 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
152153
const [isDeleteRequestModalVisible, setIsDeleteRequestModalVisible] = useState(false);
153154
const {translate} = useLocalize();
154155
const {isOffline} = useNetwork();
155-
const {reimbursableSpend} = getMoneyRequestSpendBreakdown(moneyRequestReport);
156156
const isOnHold = isOnHoldTransactionUtils(transaction);
157157
const isDeletedParentAction = !!requestParentReportAction && isDeletedAction(requestParentReportAction);
158158
const isDuplicate = isDuplicateTransactionUtils(transaction?.transactionID) && (!isReportApproved({report: moneyRequestReport}) || isApprovedAnimationRunning);
@@ -197,99 +197,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
197197

198198
const {selectedTransactionsID, setSelectedTransactionsID} = useMoneyRequestReportContext();
199199

200-
const selectedTransactionsOptions = useMemo(() => {
201-
if (!selectedTransactionsID.length) {
202-
return [];
203-
}
204-
const options = [];
205-
const selectedTransactions = selectedTransactionsID.map((transactionID) => getTransaction(transactionID)).filter((t) => !!t);
206-
207-
const anyTransactionOnHold = selectedTransactions.some(isOnHoldTransactionUtils);
208-
const allTransactionOnHold = selectedTransactions.every(isOnHoldTransactionUtils);
209-
const isReportReimbursed = moneyRequestReport?.stateNum === CONST.REPORT.STATE_NUM.APPROVED && moneyRequestReport?.statusNum === CONST.REPORT.STATUS_NUM.REIMBURSED;
210-
211-
if (!anyTransactionOnHold && selectedTransactions.length === 1 && !isReportReimbursed) {
212-
options.push({
213-
text: translate('iou.hold'),
214-
icon: Expensicons.Stopwatch,
215-
value: CONST.REPORT.SECONDARY_ACTIONS.HOLD,
216-
onSelected: () => {
217-
if (!moneyRequestReport?.reportID) {
218-
return;
219-
}
220-
Navigation.navigate(ROUTES.SEARCH_MONEY_REQUEST_REPORT_HOLD_TRANSACTIONS.getRoute({reportID: moneyRequestReport.reportID}));
221-
},
222-
});
223-
}
224-
225-
if (allTransactionOnHold && selectedTransactions.length === 1) {
226-
options.push({
227-
text: translate('iou.unhold'),
228-
icon: Expensicons.Stopwatch,
229-
value: 'UNHOLD',
230-
onSelected: () => {
231-
selectedTransactionsID.forEach((transactionID) => {
232-
const action = getIOUActionForTransactionID(reportActions, transactionID);
233-
if (!action?.childReportID) {
234-
return;
235-
}
236-
unholdRequest(transactionID, action?.childReportID);
237-
});
238-
// it's needed in order to recalculate options
239-
setSelectedTransactionsID([...selectedTransactionsID]);
240-
},
241-
});
242-
}
243-
244-
options.push({
245-
value: CONST.REPORT.SECONDARY_ACTIONS.DOWNLOAD,
246-
text: translate('common.download'),
247-
icon: Expensicons.Download,
248-
onSelected: () => {
249-
if (!moneyRequestReport) {
250-
return;
251-
}
252-
exportReportToCSV({reportID: moneyRequestReport.reportID, transactionIDList: selectedTransactionsID}, () => {
253-
setIsDownloadErrorModalVisible(true);
254-
});
255-
},
256-
});
257-
258-
const canAllSelectedTransactionsBeRemoved = selectedTransactionsID.every((transactionID) => {
259-
const canRemoveTransaction = canDeleteCardTransactionByLiabilityType(transactionID);
260-
const action = getIOUActionForTransactionID(reportActions, transactionID);
261-
const isActionDeleted = isDeletedAction(action);
262-
const isIOUActionOwner = typeof action?.actorAccountID === 'number' && typeof session?.accountID === 'number' && action.actorAccountID === session?.accountID;
263-
264-
return canRemoveTransaction && isIOUActionOwner && !isActionDeleted;
265-
});
266-
267-
const canRemoveReportTransaction = canDeleteTransaction(moneyRequestReport);
268-
269-
if (canRemoveReportTransaction && canAllSelectedTransactionsBeRemoved) {
270-
options.push({
271-
text: translate('common.delete'),
272-
icon: Expensicons.Trashcan,
273-
value: CONST.REPORT.SECONDARY_ACTIONS.DELETE,
274-
onSelected: () => {
275-
const iouActions = reportActions.filter((action) => isMoneyRequestAction(action));
276-
277-
const transactionsWithActions = selectedTransactions.map((t) => ({
278-
transactionID: t?.transactionID,
279-
action: iouActions.find((action) => {
280-
const IOUTransactionID = (getOriginalMessage(action) as OnyxTypes.OriginalMessageIOU)?.IOUTransactionID;
281-
282-
return t?.transactionID === IOUTransactionID;
283-
}),
284-
}));
285-
286-
transactionsWithActions.forEach(({transactionID, action}) => action && deleteMoneyRequest(transactionID, action));
287-
setSelectedTransactionsID([]);
288-
},
289-
});
290-
}
291-
return options;
292-
}, [moneyRequestReport, reportActions, selectedTransactionsID, session?.accountID, setSelectedTransactionsID, translate]);
200+
const selectedTransactionsOptions = useSelectedTransactionsActions({report: moneyRequestReport, reportActions, session, onExportFailed: () => setIsDownloadErrorModalVisible(true)});
293201

294202
const shouldShowSelectedTransactionsButton = !!selectedTransactionsOptions.length && !transactionThreadReportID;
295203

@@ -314,7 +222,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
314222
const filteredTransactions = transactions?.filter((t) => t) ?? [];
315223
const shouldShowSubmitButton = canSubmitReport(moneyRequestReport, policy, filteredTransactions, violations);
316224

317-
const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && connectedIntegration && isAdmin && canBeExported(moneyRequestReport);
225+
const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && !!connectedIntegration && isAdmin && canBeExported(moneyRequestReport);
318226

319227
const shouldShowSettlementButton =
320228
!shouldShowSelectedTransactionsButton &&
@@ -349,10 +257,8 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
349257
shouldShowMarkAsCashButton ||
350258
shouldShowExportIntegrationButton;
351259
const bankAccountRoute = getBankAccountRoute(chatReport);
352-
const formattedAmount = convertToDisplayString(reimbursableSpend, moneyRequestReport?.currency);
353260
const {nonHeldAmount, fullAmount, hasValidNonHeldAmount} = getNonHeldAndFullAmount(moneyRequestReport, shouldShowPayButton);
354261
const isAnyTransactionOnHold = hasHeldExpensesReportUtils(moneyRequestReport?.reportID);
355-
const displayedAmount = isAnyTransactionOnHold && canAllowSettlement && hasValidNonHeldAmount ? nonHeldAmount : formattedAmount;
356262
const isMoreContentShown = shouldShowNextStep || shouldShowStatusBar || (shouldShowAnyButton && shouldUseNarrowLayout);
357263
const {isDelegateAccessRestricted} = useDelegateUserDetails();
358264
const [isNoDelegateAccessMenuVisible, setIsNoDelegateAccessMenuVisible] = useState(false);
@@ -512,6 +418,29 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
512418

513419
const shouldShowBackButton = shouldDisplayBackButton || shouldUseNarrowLayout;
514420

421+
const iouReportID = moneyRequestReport?.reportID;
422+
423+
const isIOUSettled = isSettled(iouReportID) || requestParentReportAction?.childStatusNum === CONST.REPORT.STATUS_NUM.REIMBURSED;
424+
425+
const shouldShowRBR =
426+
((hasMissingSmartscanFields(iouReportID) && !isIOUSettled) ||
427+
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
428+
hasViolations(iouReportID, violations, true) ||
429+
hasNoticeTypeViolations(iouReportID, violations, true) ||
430+
hasWarningTypeViolations(iouReportID, violations, true) ||
431+
(isReportOwner(moneyRequestReport) && hasReportViolations(iouReportID)) ||
432+
hasActionsWithErrors(iouReportID)) &&
433+
!isIOUSettled;
434+
435+
const buttonType = getIOUReportPreviewButtonType({
436+
shouldShowPayButton,
437+
shouldShowApproveButton,
438+
shouldShowSubmitButton,
439+
shouldShowSettlementButton,
440+
shouldShowRBR,
441+
shouldShowExportIntegrationButton,
442+
});
443+
515444
return (
516445
<View style={[styles.pt0, styles.borderBottom]}>
517446
<HeaderWithBackButton
@@ -568,7 +497,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
568497
shouldShowApproveButton={shouldShowApproveButton}
569498
shouldDisableApproveButton={shouldDisableApproveButton}
570499
style={[styles.pv2]}
571-
formattedAmount={!hasOnlyHeldExpenses ? displayedAmount : ''}
500+
formattedAmount={getTotalAmountForIOUReportPreviewButton(moneyRequestReport, policy, buttonType)}
572501
isDisabled={isOffline && !canAllowSettlement}
573502
isLoading={!isOffline && !canAllowSettlement}
574503
/>
@@ -647,7 +576,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
647576
addBankAccountRoute={bankAccountRoute}
648577
shouldHidePaymentOptions={!shouldShowPayButton}
649578
shouldShowApproveButton={shouldShowApproveButton}
650-
formattedAmount={!hasOnlyHeldExpenses ? displayedAmount : ''}
579+
formattedAmount={getTotalAmountForIOUReportPreviewButton(moneyRequestReport, policy, buttonType)}
651580
shouldDisableApproveButton={shouldDisableApproveButton}
652581
isDisabled={isOffline && !canAllowSettlement}
653582
isLoading={!isOffline && !canAllowSettlement}

src/components/ReportActionItem/ReportPreview.tsx

Lines changed: 20 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import useTransactionViolations from '@hooks/useTransactionViolations';
3030
import ControlSelection from '@libs/ControlSelection';
3131
import {convertToDisplayString} from '@libs/CurrencyUtils';
3232
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
33+
import {getIOUReportPreviewButtonType, getTotalAmountForIOUReportPreviewButton} from '@libs/MoneyRequestReportUtils';
3334
import Navigation from '@libs/Navigation/Navigation';
3435
import Parser from '@libs/Parser';
3536
import Performance from '@libs/Performance';
@@ -152,17 +153,19 @@ function ReportPreview({
152153
shouldDisplayContextMenu = true,
153154
}: ReportPreviewProps) {
154155
const policy = usePolicy(policyID);
155-
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`);
156+
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`, {canBeMissing: true});
156157
const [iouReport, transactions, violations] = useReportWithTransactionsAndViolations(iouReportID);
157158
const lastTransaction = transactions?.at(0);
158159
const transactionIDList = transactions?.map((reportTransaction) => reportTransaction.transactionID) ?? [];
159-
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET);
160+
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET, {canBeMissing: true});
160161
const [invoiceReceiverPolicy] = useOnyx(
161-
`${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : CONST.DEFAULT_NUMBER_ID}`,
162+
`${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : undefined}`,
163+
{canBeMissing: true},
162164
);
163165
const [invoiceReceiverPersonalDetail] = useOnyx(ONYXKEYS.PERSONAL_DETAILS_LIST, {
164166
selector: (personalDetails) =>
165167
personalDetails?.[chatReport?.invoiceReceiver && 'accountID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.accountID : CONST.DEFAULT_NUMBER_ID],
168+
canBeMissing: true,
166169
});
167170
const theme = useTheme();
168171
const styles = useThemeStyles();
@@ -205,8 +208,8 @@ function ReportPreview({
205208
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(iouReport?.reportID);
206209

207210
const managerID = iouReport?.managerID ?? action.childManagerAccountID ?? CONST.DEFAULT_NUMBER_ID;
208-
const {totalDisplaySpend, reimbursableSpend} = getMoneyRequestSpendBreakdown(iouReport);
209-
const [reports] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}`);
211+
const {totalDisplaySpend} = getMoneyRequestSpendBreakdown(iouReport);
212+
const [reports] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}`, {canBeMissing: true});
210213
const iouSettled = isSettled(iouReportID, isOnSearch ? reports : undefined) || action?.childStatusNum === CONST.REPORT.STATUS_NUM.REIMBURSED;
211214
const previewMessageOpacity = useSharedValue(1);
212215
const previewMessageStyle = useAnimatedStyle(() => ({
@@ -301,19 +304,6 @@ function ReportPreview({
301304
}
302305
};
303306

304-
const getSettlementAmount = () => {
305-
if (hasOnlyHeldExpenses) {
306-
return '';
307-
}
308-
309-
// We shouldn't display the nonHeldAmount as the default option if it's not valid since we cannot pay partially in this case
310-
if (hasHeldExpensesReportUtils(iouReport?.reportID) && canAllowSettlement && hasValidNonHeldAmount) {
311-
return nonHeldAmount;
312-
}
313-
314-
return convertToDisplayString(reimbursableSpend, iouReport?.currency);
315-
};
316-
317307
const getDisplayAmount = (): string => {
318308
if (totalDisplaySpend) {
319309
return convertToDisplayString(totalDisplaySpend, iouReport?.currency);
@@ -425,7 +415,7 @@ function ReportPreview({
425415
const shouldShowSubtitle = !isScanning && (shouldShowSingleRequestMerchantOrDescription || numberOfRequests > 1) && !isDisplayAmountZero(getDisplayAmount());
426416

427417
const isPayAtEndExpense = isPayAtEndExpenseReport(iouReportID, transactions);
428-
const [archiveReason] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, {selector: getArchiveReason});
418+
const [archiveReason] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${iouReportID}`, {selector: getArchiveReason, canBeMissing: true});
429419

430420
const getPendingMessageProps: () => PendingMessageProps = () => {
431421
if (isPayAtEndExpense) {
@@ -475,7 +465,7 @@ function ReportPreview({
475465
*/
476466
const connectedIntegration = getConnectedIntegration(policy);
477467

478-
const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && connectedIntegration && isAdmin && canBeExported(iouReport);
468+
const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && !!connectedIntegration && isAdmin && canBeExported(iouReport);
479469

480470
useEffect(() => {
481471
if (!isPaidAnimationRunning || isApprovedAnimationRunning) {
@@ -516,6 +506,15 @@ function ReportPreview({
516506
Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(iouReportID));
517507
}, [iouReportID]);
518508

509+
const buttonType = getIOUReportPreviewButtonType({
510+
shouldShowPayButton,
511+
shouldShowApproveButton,
512+
shouldShowSubmitButton,
513+
shouldShowSettlementButton,
514+
shouldShowRBR,
515+
shouldShowExportIntegrationButton,
516+
});
517+
519518
return (
520519
<OfflineWithFeedback
521520
pendingAction={iouReport?.pendingFields?.preview}
@@ -623,7 +622,7 @@ function ReportPreview({
623622
isApprovedAnimationRunning={isApprovedAnimationRunning}
624623
canIOUBePaid={canIOUBePaidAndApproved || isPaidAnimationRunning}
625624
onAnimationFinish={stopAnimation}
626-
formattedAmount={getSettlementAmount() ?? ''}
625+
formattedAmount={getTotalAmountForIOUReportPreviewButton(iouReport, policy, buttonType)}
627626
currency={iouReport?.currency}
628627
policyID={policyID}
629628
chatReportID={chatReportID}

0 commit comments

Comments
 (0)