Skip to content
Open
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider';
import {useLockedAccountActions, useLockedAccountState} from '@components/LockedAccountModalProvider';

import useConfirmApproveReportViolations from '@hooks/useConfirmApproveReportViolations';
import {useCurrencyListActions} from '@hooks/useCurrencyList';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useDelegateAccountID from '@hooks/useDelegateAccountID';
Expand All @@ -13,15 +14,16 @@ import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViol
import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import {isSubmitPolicy} from '@libs/PolicyUtils';
import {hasHeldExpensesFromTransactions as hasHeldExpensesReportUtils, hasViolations as hasViolationsReportUtils} from '@libs/ReportUtils';
import getReportApprovalWarningData from '@libs/Violations/getReportApprovalWarningData';

import {approveMoneyRequest} from '@userActions/IOU/ReportWorkflow';
import type AdditionalPayOnyxData from '@userActions/IOU/types/AdditionalPayOnyxData';

import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Policy, Report, Transaction} from '@src/types/onyx';
import type {Policy, Report, Transaction, TransactionViolations} from '@src/types/onyx';

import type {OnyxEntry} from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';

import {delegateEmailSelector} from '@selectors/Account';
import {isTrackIntentUserSelector} from '@selectors/Onboarding';
Expand All @@ -35,12 +37,14 @@ type UseConfirmApprovalOptions = {
fallbackReport?: OnyxEntry<Report>;
fallbackPolicy?: OnyxEntry<Policy>;
fallbackTransactions?: Transaction[];
/** Search warnings used only when no live violation entry is available. */
fallbackViolations?: OnyxCollection<TransactionViolations>;
};

function useConfirmApproval(
reportID: string | undefined,
startApprovedAnimation: () => void,
{getAdditionalOnyxData, fallbackReport, fallbackPolicy, fallbackTransactions}: UseConfirmApprovalOptions = {},
{getAdditionalOnyxData, fallbackReport, fallbackPolicy, fallbackTransactions, fallbackViolations}: UseConfirmApprovalOptions = {},
) {
const {accountID, email} = useCurrentUserPersonalDetails();
const {getCurrencyDecimals} = useCurrencyListActions();
Expand Down Expand Up @@ -78,6 +82,13 @@ function useConfirmApproval(
const isASAPSubmitBetaEnabled = isBetaEnabled(CONST.BETAS.ASAP_SUBMIT);
const hasViolations = hasViolationsReportUtils(moneyRequestReport?.reportID, transactionViolations, accountID, email ?? '', undefined, transactions);
const isAnyTransactionOnHold = hasHeldExpensesReportUtils(transactions);
const warningData = getReportApprovalWarningData(reportID, transactions, [], fallbackViolations, transactionViolations);
const confirmApproveReportViolations = useConfirmApproveReportViolations({
report: moneyRequestReport,
policy,
transactions: warningData.transactions,
violationsCollection: warningData.violations,
});

const onApprove = (full: boolean) => {
if (isDelegateAccessRestricted) {
Expand All @@ -88,31 +99,33 @@ function useConfirmApproval(
showLockedAccountModal();
return;
}
if (!isSubmitPolicy(policy)) {
startApprovedAnimation();
}
approveMoneyRequest({
personalDetailsByLogins: getPersonalDetailsByLogin(),
getCurrencyDecimals,
expenseReport: moneyRequestReport,
expenseReportPolicy: policy,
rules,
currentUserAccountIDParam: accountID,
currentUserEmailParam: email ?? '',
hasViolations,
isASAPSubmitBetaEnabled,
userBillingGracePeriodEnds,
amountOwed,
ownerBillingGracePeriodEnd,
ownerLogin,
full,
onApproved: startApprovedAnimation,
delegateEmail,
delegateAccountID,
isTrackIntentUser,
allTransactionViolations: {...autoReportedRejectedExpenseViolations, ...transactionViolations},
additionalOnyxData: getAdditionalOnyxData?.(),
});
confirmApproveReportViolations(() => {
if (!isSubmitPolicy(policy)) {
startApprovedAnimation();
}
approveMoneyRequest({
personalDetailsByLogins: getPersonalDetailsByLogin(),
getCurrencyDecimals,
expenseReport: moneyRequestReport,
expenseReportPolicy: policy,
rules,
currentUserAccountIDParam: accountID,
currentUserEmailParam: email ?? '',
hasViolations,
isASAPSubmitBetaEnabled,
userBillingGracePeriodEnds,
amountOwed,
ownerBillingGracePeriodEnd,
ownerLogin,
full,
onApproved: startApprovedAnimation,
delegateEmail,
delegateAccountID,
isTrackIntentUser,
allTransactionViolations: {...autoReportedRejectedExpenseViolations, ...transactionViolations},
additionalOnyxData: getAdditionalOnyxData?.(),
});
}, full);
};

// When there are held expenses the partial/full choice is surfaced up front via the settlement button's approve
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import {useDelegateNoAccessActions, useDelegateNoAccessState} from '@components/DelegateNoAccessModalProvider';
import {useLockedAccountActions, useLockedAccountState} from '@components/LockedAccountModalProvider';

import useConfirmApproveReportViolations from '@hooks/useConfirmApproveReportViolations';
import {useCurrencyListActions} from '@hooks/useCurrencyList';
import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails';
import useOnyx from '@hooks/useOnyx';
Expand Down Expand Up @@ -38,34 +39,37 @@ function useConfirmApproveReportAction(actionButtonData: ReturnType<typeof useRe
const [allTransactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);

const {iouReport, policy, ownerLogin, userBillingGracePeriodEnds, amountOwed, ownerBillingGracePeriodEnd, delegateEmail, delegateAccountID} = actionButtonData;
const confirmApproveReportViolations = useConfirmApproveReportViolations({report: iouReport, policy});

return (full = true) => {
if (isDelegateAccessRestricted) {
showDelegateNoAccessModal();
} else if (isAccountLocked) {
showLockedAccountModal();
} else {
approveMoneyRequest({
personalDetailsByLogins: getPersonalDetailsByLogin(),
getCurrencyDecimals,
expenseReport: iouReport,
expenseReportPolicy: policy,
rules,
currentUserAccountIDParam: currentUserDetails.accountID,
currentUserEmailParam: currentUserDetails.email ?? '',
hasViolations,
isASAPSubmitBetaEnabled: isBetaEnabled(CONST.BETAS.ASAP_SUBMIT),
userBillingGracePeriodEnds,
amountOwed,
ownerBillingGracePeriodEnd,
ownerLogin,
full,
onApproved: startApprovedAnimation,
delegateEmail,
delegateAccountID,
isTrackIntentUser,
allTransactionViolations,
});
confirmApproveReportViolations(() => {
approveMoneyRequest({
personalDetailsByLogins: getPersonalDetailsByLogin(),
getCurrencyDecimals,
expenseReport: iouReport,
expenseReportPolicy: policy,
rules,
currentUserAccountIDParam: currentUserDetails.accountID,
currentUserEmailParam: currentUserDetails.email ?? '',
hasViolations,
isASAPSubmitBetaEnabled: isBetaEnabled(CONST.BETAS.ASAP_SUBMIT),
userBillingGracePeriodEnds,
amountOwed,
ownerBillingGracePeriodEnd,
ownerLogin,
full,
onApproved: startApprovedAnimation,
delegateEmail,
delegateAccountID,
isTrackIntentUser,
allTransactionViolations,
});
}, full);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function ApproveActionCell({isLoading, reportID, policyID, hash, shouldDisablePo
const currentUserDetails = useCurrentUserPersonalDetails();
const {isDelegateAccessRestricted} = useDelegateNoAccessState();
const {currentSearchKey} = useSearchQueryContext();
const {currentSearchResults} = useSearchResultsContext();
const {currentSearchResults, currentSearchTransactionsByReportID, currentSearchViolations} = useSearchResultsContext();

const snapshotReport = currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.REPORT}${reportID}`] as OnyxEntry<Report>;
const snapshotPolicy = currentSearchResults?.data?.[`${ONYXKEYS.COLLECTION.POLICY}${policyID}`] as OnyxEntry<Policy>;
Expand All @@ -52,7 +52,8 @@ function ApproveActionCell({isLoading, reportID, policyID, hash, shouldDisablePo
getAdditionalOnyxData: hash === undefined ? undefined : () => getSearchApproveOnyxData(hash, reportID, currentSearchKey),
fallbackReport: snapshotReport,
fallbackPolicy: snapshotPolicy,
fallbackTransactions: snapshotTransactions,
fallbackTransactions: currentSearchTransactionsByReportID.get(reportID) ?? snapshotTransactions,
fallbackViolations: currentSearchViolations,
});

const [activePolicyID] = useOnyx(ONYXKEYS.NVP_ACTIVE_POLICY_ID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import ListItemComposed from '@components/SelectionList/ListItemComposed';
import type {ListItem} from '@components/SelectionList/types';
import Text from '@components/Text';

import useConfirmApproveReportViolations from '@hooks/useConfirmApproveReportViolations';
import useConfirmModal from '@hooks/useConfirmModal';
import useConfirmSubmitReportViolations from '@hooks/useConfirmSubmitReportViolations';
import {useCurrencyListActions} from '@hooks/useCurrencyList';
Expand All @@ -40,6 +41,7 @@ import {isInvoiceReport, isOpenExpenseReport, isProcessingReport, isReportPendin
import {hasVisibleViolations} from '@libs/SearchUIUtils';
import shouldBreakAccessibilityGrouping from '@libs/shouldBreakAccessibilityGrouping';
import {getAttendees, isViolationDismissed, shouldShowViolation, showHeldExpensesBlockModal, showPendingCardTransactionsBlockModal} from '@libs/TransactionUtils';
import getReportApprovalWarningData from '@libs/Violations/getReportApprovalWarningData';

import variables from '@styles/variables';

Expand Down Expand Up @@ -121,7 +123,7 @@ function ExpenseReportListItemInner<TItem extends ListItem>({
const {getCurrencyDecimals, convertToDisplayString} = useCurrencyListActions();
const {isLargeScreenWidth} = useResponsiveLayout();
const {currentSearchHash, currentSearchKey} = useSearchQueryContext();
const {currentSearchResults} = useSearchResultsContext();
const {currentSearchResults, currentSearchTransactionsByReportID, currentSearchViolations} = useSearchResultsContext();
const [isActionLoading] = useOnyx(`${ONYXKEYS.COLLECTION.RAM_ONLY_REPORT_LOADING_STATE}${reportItem.reportID}`, {
selector: isActionLoadingSelector,
});
Expand Down Expand Up @@ -247,10 +249,18 @@ function ExpenseReportListItemInner<TItem extends ListItem>({
// Scoped live violations for the report's snapshot transactions: before the report's transactions
// hydrate into the live collection, rule/category changes still push violation updates that must
// reflect on the badge (per-row selector, not the screen-level collection merge this slice removed).
const snapshotTransactionIDs = (reportItem.transactions ?? []).map((transaction) => transaction.transactionID);
const snapshotTransactions = currentSearchTransactionsByReportID.get(reportItem.reportID) ?? reportItem.transactions ?? [];
const snapshotTransactionIDs = [...new Set([...snapshotTransactions, ...liveReportTransactions].map((transaction) => transaction.transactionID))];
const [liveViolationsForSnapshotTransactions] = useOnyxWithoutSnapshots(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {
selector: transactionViolationsByIDsSelector(snapshotTransactionIDs),
});
const warningData = getReportApprovalWarningData(reportItem.reportID, snapshotTransactions, liveReportTransactions, currentSearchViolations, liveViolationsForSnapshotTransactions);
const confirmApproveReportViolations = useConfirmApproveReportViolations({
report: reportForViolations,
policy: policyForViolations,
transactions: warningData.transactions,
violationsCollection: warningData.violations,
});
// Approving also recomputes the chat's hasOutstandingChildRequest, which checks sibling reports' transactions for auto-reported rejected expenses.
const [autoReportedRejectedExpenseViolations] = useOnyxWithoutSnapshots(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {selector: autoReportedRejectedExpenseViolationsSelector});
const {currentUserAccountID, currentUserLogin, isASAPSubmitBetaEnabled, isWiseReimbursementsBetaEnabled, activePolicy, chatReportPolicy, delegateEmail, delegateAccountID} =
Expand Down Expand Up @@ -284,6 +294,7 @@ function ExpenseReportListItemInner<TItem extends ListItem>({
onAccountLocked: showLockedAccountModal,
personalPolicyID,
confirmSubmitReportViolations,
confirmApproveReportViolations,
ownerBillingGracePeriodEnd,
openReportSubmitToPopover,
onPendingCardTransactionsBlock: () => showPendingCardTransactionsBlockModal(showConfirmModal, translate, shouldShowMarkAsDoneCopy),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {useSearchQueryContext, useSearchResultsContext} from '@components/Search
import {useRowSelection} from '@components/Search/SearchSelectionProvider';
import type {ListItem} from '@components/SelectionList/types';

import useConfirmApproveReportViolations from '@hooks/useConfirmApproveReportViolations';
import useConfirmModal from '@hooks/useConfirmModal';
import useConfirmSubmitReportViolations from '@hooks/useConfirmSubmitReportViolations';
import {useCurrencyListActions} from '@hooks/useCurrencyList';
Expand All @@ -28,11 +29,13 @@ import useSearchActionPress from '@hooks/useSearchActionPress';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionsAndViolationsForReport from '@hooks/useTransactionsAndViolationsForReport';

import getNonEmptyStringOnyxID from '@libs/getNonEmptyStringOnyxID';
import type {ModifiedMouseEvent} from '@libs/Navigation/helpers/openInternalRouteInNewTab';
import {shouldShowMarkAsDone} from '@libs/ReportUtils';
import {showHeldExpensesBlockModal, showPendingCardTransactionsBlockModal} from '@libs/TransactionUtils';
import getReportApprovalWarningData from '@libs/Violations/getReportApprovalWarningData';

import {handleActionButtonPress} from '@userActions/Search';

Expand Down Expand Up @@ -266,7 +269,7 @@ function ReportListItemHeaderInner<TItem extends ListItem>({
const styles = useThemeStyles();
const theme = useTheme();
const {currentSearchHash, currentSearchKey} = useSearchQueryContext();
const {currentSearchResults: snapshot} = useSearchResultsContext();
const {currentSearchResults: snapshot, currentSearchTransactionsByReportID, currentSearchViolations} = useSearchResultsContext();
const {isLargeScreenWidth} = useResponsiveLayout();
const thereIsFromAndTo = !!reportItem?.from && !!reportItem?.to;
const showUserInfo = (reportItem.type === CONST.REPORT.TYPE.IOU && thereIsFromAndTo) || (reportItem.type === CONST.REPORT.TYPE.EXPENSE && !!reportItem?.from);
Expand Down Expand Up @@ -298,8 +301,18 @@ function ReportListItemHeaderInner<TItem extends ListItem>({
shouldShowMarkAsDoneCopy,
});

const reportTransactionIDs = (reportItem.transactions ?? []).map((transaction) => transaction.transactionID);
const {transactions: reportTransactions} = useTransactionsAndViolationsForReport(reportItem.reportID);
const liveReportTransactions = Object.values(reportTransactions);
const snapshotTransactions = currentSearchTransactionsByReportID.get(reportItem.reportID) ?? reportItem.transactions ?? [];
const reportTransactionIDs = [...new Set([...snapshotTransactions, ...liveReportTransactions].map((transaction) => transaction.transactionID))];
const [allViolations] = useOnyxWithoutSnapshots(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {selector: transactionViolationsByIDsSelector(reportTransactionIDs)});
const warningData = getReportApprovalWarningData(reportItem.reportID, snapshotTransactions, liveReportTransactions, currentSearchViolations, allViolations);
const confirmApproveReportViolations = useConfirmApproveReportViolations({
report: parentReport ?? snapshotReport,
policy: parentPolicy ?? snapshotPolicy,
transactions: warningData.transactions,
violationsCollection: warningData.violations,
});
// Approving also recomputes the chat's hasOutstandingChildRequest, which checks sibling reports' transactions for auto-reported rejected expenses.
const [autoReportedRejectedExpenseViolations] = useOnyxWithoutSnapshots(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {selector: autoReportedRejectedExpenseViolationsSelector});

Expand Down Expand Up @@ -349,6 +362,7 @@ function ReportListItemHeaderInner<TItem extends ListItem>({
personalPolicyID,
ownerBillingGracePeriodEnd,
confirmSubmitReportViolations,
confirmApproveReportViolations,
openReportSubmitToPopover,
onPendingCardTransactionsBlock: () => showPendingCardTransactionsBlockModal(showConfirmModal, translate, shouldShowMarkAsDoneCopy),
onAllHeldExpensesBlock: () => showHeldExpensesBlockModal(showConfirmModal, translate, shouldShowMarkAsDoneCopy),
Expand Down
Loading
Loading