Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/components/BrokenConnectionDescription.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React from 'react';
import type {OnyxEntry} from 'react-native-onyx';
import {useOnyx} from 'react-native-onyx';
import useLocalize from '@hooks/useLocalize';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolations from '@hooks/useTransactionViolations';
import {isInstantSubmitEnabled, isPolicyAdmin as isPolicyAdminPolicyUtils} from '@libs/PolicyUtils';
import {isCurrentUserSubmitter, isProcessingReport, isReportApproved, isReportManuallyReimbursed} from '@libs/ReportUtils';
import Navigation from '@navigation/Navigation';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import ROUTES from '@src/ROUTES';
import type {Policy, Report} from '@src/types/onyx';
import TextLink from './TextLink';
Expand All @@ -26,7 +25,7 @@ type BrokenConnectionDescriptionProps = {
function BrokenConnectionDescription({transactionID, policy, report}: BrokenConnectionDescriptionProps) {
const styles = useThemeStyles();
const {translate} = useLocalize();
const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID ?? CONST.DEFAULT_NUMBER_ID}`);
const transactionViolations = useTransactionViolations(transactionID);

const brokenConnection530Error = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530);
const brokenConnectionError = transactionViolations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION);
Expand Down
10 changes: 7 additions & 3 deletions src/components/MoneyReportHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,14 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
return !!transactions && transactions.length > 0 && transactions.every((t) => isExpensifyCardTransaction(t) && isPending(t));
}, [transactions]);
const transactionIDs = transactions?.map((t) => t.transactionID) ?? [];
const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {
selector: (allTransactions) =>
Object.fromEntries(Object.entries(allTransactions ?? {}).filter(([key]) => transactionIDs.includes(key.replace(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, '')))),
});
// Check if there is pending rter violation in all transactionViolations with given transactionIDs.
const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs);
const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDs, violations);
// Check if user should see broken connection violation warning.
const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transactionIDs, moneyRequestReport, policy) && !!transactionThreadReportID;
const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transactionIDs, moneyRequestReport, policy, violations);
const hasOnlyHeldExpenses = hasOnlyHeldExpensesReportUtils(moneyRequestReport?.reportID);
const isPayAtEndExpense = isPayAtEndExpenseTransactionUtils(transaction);
const isArchivedReport = isArchivedReportWithID(moneyRequestReport?.reportID);
Expand All @@ -181,7 +185,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea
const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;

const filteredTransactions = transactions?.filter((t) => t) ?? [];
const shouldShowSubmitButton = canSubmitReport(moneyRequestReport, policy, filteredTransactions);
const shouldShowSubmitButton = canSubmitReport(moneyRequestReport, policy, filteredTransactions, violations);

const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && connectedIntegration && isAdmin && canBeExported(moneyRequestReport);

Expand Down
19 changes: 9 additions & 10 deletions src/components/MoneyRequestHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,13 @@ import useLocalize from '@hooks/useLocalize';
import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolations from '@hooks/useTransactionViolations';
import Navigation from '@libs/Navigation/Navigation';
import {isPolicyAdmin} from '@libs/PolicyUtils';
import {getOriginalMessage, isMoneyRequestAction} from '@libs/ReportActionsUtils';
import {isCurrentUserSubmitter} from '@libs/ReportUtils';
import {
allHavePendingRTERViolation,
getTransactionViolations,
hasPendingRTERViolation,
hasPendingRTERViolation as hasPendingRTERViolationTransactionUtils,
hasReceipt,
isDuplicate as isDuplicateTransactionUtils,
isExpensifyCardTransaction,
Expand Down Expand Up @@ -61,13 +60,14 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
// eslint-disable-next-line rulesdir/prefer-shouldUseNarrowLayout-instead-of-isSmallScreenWidth
const {shouldUseNarrowLayout, isSmallScreenWidth} = useResponsiveLayout();
const route = useRoute();
const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID}`);
const [parentReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${report?.parentReportID ?? CONST.DEFAULT_NUMBER_ID}`);
const [transaction] = useOnyx(
`${ONYXKEYS.COLLECTION.TRANSACTION}${
isMoneyRequestAction(parentReportAction) ? getOriginalMessage(parentReportAction)?.IOUTransactionID ?? CONST.DEFAULT_NUMBER_ID : CONST.DEFAULT_NUMBER_ID
}`,
);
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const transactionViolations = useTransactionViolations(transaction?.transactionID);

const [dismissedHoldUseExplanation, dismissedHoldUseExplanationResult] = useOnyx(ONYXKEYS.NVP_DISMISSED_HOLD_USE_EXPLANATION, {initialValue: true});
const [isLoadingReportData] = useOnyx(ONYXKEYS.IS_LOADING_REPORT_DATA);
const isLoadingHoldUseExplained = isLoadingOnyxValue(dismissedHoldUseExplanationResult);
Expand All @@ -80,12 +80,11 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre

const isReportInRHP = route.name === SCREENS.SEARCH.REPORT_RHP;
const shouldDisplaySearchRouter = !isReportInRHP || isSmallScreenWidth;
const transactionIDList = transaction ? [transaction.transactionID] : [];
const hasAllPendingRTERViolations = allHavePendingRTERViolation(transactionIDList);

const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, parentReport, policy);
const hasPendingRTERViolation = hasPendingRTERViolationTransactionUtils(transactionViolations);

const shouldShowMarkAsCashButton = hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID)));
const shouldShowBrokenConnectionViolation = shouldShowBrokenConnectionViolationTransactionUtils(transaction, report, policy, transactionViolations);
const shouldShowMarkAsCashButton = hasPendingRTERViolation || (shouldShowBrokenConnectionViolation && (!isPolicyAdmin(policy) || isCurrentUserSubmitter(parentReport?.reportID)));

const markAsCash = useCallback(() => {
markAsCashAction(transaction?.transactionID, reportID);
Expand Down Expand Up @@ -122,7 +121,7 @@ function MoneyRequestHeader({report, parentReportAction, policy, onBackButtonPre
),
};
}
if (hasPendingRTERViolation(getTransactionViolations(transaction?.transactionID, transactionViolations))) {
if (hasPendingRTERViolation) {
return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')};
}
if (isScanning) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import useResponsiveLayout from '@hooks/useResponsiveLayout';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolations from '@hooks/useTransactionViolations';
import useWindowDimensions from '@hooks/useWindowDimensions';
import ControlSelection from '@libs/ControlSelection';
import {convertToDisplayString} from '@libs/CurrencyUtils';
Expand All @@ -47,7 +48,6 @@ import type {TransactionDetails} from '@libs/ReportUtils';
import StringUtils from '@libs/StringUtils';
import {
compareDuplicateTransactionFields,
getTransactionViolations,
hasMissingSmartscanFields,
hasNoticeTypeViolation as hasNoticeTypeViolationTransactionUtils,
hasPendingRTERViolation,
Expand Down Expand Up @@ -111,7 +111,7 @@ function MoneyRequestPreviewContent({
const transactionID = isMoneyRequestAction ? getOriginalMessage(action)?.IOUTransactionID : undefined;
const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${transactionID}`);
const [walletTerms] = useOnyx(ONYXKEYS.WALLET_TERMS);
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const violations = useTransactionViolations(transaction?.transactionID);

const sessionAccountID = session?.accountID;
const managerID = iouReport?.managerID ?? CONST.DEFAULT_NUMBER_ID;
Expand Down Expand Up @@ -144,9 +144,10 @@ function MoneyRequestPreviewContent({
const isOnHold = isOnHoldTransactionUtils(transaction);
const isSettlementOrApprovalPartial = !!iouReport?.pendingFields?.partial;
const isPartialHold = isSettlementOrApprovalPartial && isOnHold;
const hasViolations = hasViolationTransactionUtils(transaction?.transactionID, transactionViolations, true);
const hasNoticeTypeViolations = hasNoticeTypeViolationTransactionUtils(transaction?.transactionID, transactionViolations, true) && isPaidGroupPolicy(iouReport);
const hasWarningTypeViolations = hasWarningTypeViolationTransactionUtils(transaction?.transactionID, transactionViolations, true);
const hasViolations = hasViolationTransactionUtils(transaction, violations, true);

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.

Why didn't we pass transaction?.transactionID?

const hasNoticeTypeViolations = hasNoticeTypeViolationTransactionUtils(transaction?.transactionID, violations, true) && isPaidGroupPolicy(iouReport);
const hasWarningTypeViolations = hasWarningTypeViolationTransactionUtils(transaction?.transactionID, violations, true);

const hasFieldErrors = hasMissingSmartscanFields(transaction);
const isDistanceRequest = isDistanceRequestTransactionUtils(transaction);
const isPerDiemRequest = isPerDiemRequestTransactionUtils(transaction);
Expand All @@ -161,13 +162,7 @@ function MoneyRequestPreviewContent({
const isFullyApproved = isApproved && !isSettlementOrApprovalPartial;

// Get transaction violations for given transaction id from onyx, find duplicated transactions violations and get duplicates
const allDuplicates = useMemo(
() =>
transactionViolations?.[`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transaction?.transactionID}`]?.find(
(violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION,
)?.data?.duplicates ?? [],
[transaction?.transactionID, transactionViolations],
);
const allDuplicates = useMemo(() => violations?.find((violation) => violation.name === CONST.VIOLATIONS.DUPLICATED_TRANSACTION)?.data?.duplicates ?? [], [violations]);

// Remove settled transactions from duplicates
const duplicates = useMemo(() => removeSettledAndApprovedTransactions(allDuplicates), [allDuplicates]);
Expand Down Expand Up @@ -237,7 +232,7 @@ function MoneyRequestPreviewContent({
message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.pending')}`;
}

if (hasPendingRTERViolation(getTransactionViolations(transactionID, transactionViolations))) {
if (hasPendingRTERViolation(violations)) {
message += ` ${CONST.DOT_SEPARATOR} ${translate('iou.pendingMatch')}`;
}

Expand All @@ -247,7 +242,6 @@ function MoneyRequestPreviewContent({
}

if (shouldShowRBR && transaction) {
const violations = getTransactionViolations(transaction.transactionID, transactionViolations);
if (shouldShowHoldMessage) {
return `${message} ${CONST.DOT_SEPARATOR} ${translate('violations.hold')}`;
}
Expand Down Expand Up @@ -285,7 +279,7 @@ function MoneyRequestPreviewContent({
};

const getPendingMessageProps: () => PendingMessageProps = () => {
if (shouldShowBrokenConnectionViolation(transaction ? [transaction.transactionID] : [], iouReport, policy)) {
if (shouldShowBrokenConnectionViolation(transaction, iouReport, policy, violations)) {
return {shouldShow: true, messageIcon: Hourglass, messageDescription: translate('violations.brokenConnection530Error')};
}
return {shouldShow: false};
Expand Down
3 changes: 2 additions & 1 deletion src/components/ReportActionItem/MoneyRequestView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import useCurrentUserPersonalDetails from '@hooks/useCurrentUserPersonalDetails'
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolations from '@hooks/useTransactionViolations';
import useViolations from '@hooks/useViolations';
import type {ViolationField} from '@hooks/useViolations';
import {convertToDisplayString} from '@libs/CurrencyUtils';
Expand Down Expand Up @@ -133,7 +134,7 @@ function MoneyRequestView({report, shouldShowAnimatedBackground, readonly = fals

const [transaction] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION}${linkedTransactionID ?? CONST.DEFAULT_NUMBER_ID}`);
const [transactionBackup] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_BACKUP}${linkedTransactionID ?? CONST.DEFAULT_NUMBER_ID}`);
const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${linkedTransactionID ?? CONST.DEFAULT_NUMBER_ID}`);
const transactionViolations = useTransactionViolations(transaction?.transactionID);

const {
created: transactionDate,
Expand Down
29 changes: 13 additions & 16 deletions src/components/ReportActionItem/ReportPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ import useDelegateUserDetails from '@hooks/useDelegateUserDetails';
import useLocalize from '@hooks/useLocalize';
import useNetwork from '@hooks/useNetwork';
import usePolicy from '@hooks/usePolicy';
import useReportWithTransactionsAndViolations from '@hooks/useReportWithTransactionsAndViolations';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import useTransactionViolations from '@hooks/useTransactionViolations';
import ControlSelection from '@libs/ControlSelection';
import {convertToDisplayString} from '@libs/CurrencyUtils';
import {canUseTouchScreen} from '@libs/DeviceCapabilities';
Expand Down Expand Up @@ -65,18 +67,17 @@ import {
isReportOwner,
isSettled,
isTripRoom as isTripRoomReportUtils,
reportTransactionsSelector,
} from '@libs/ReportUtils';
import StringUtils from '@libs/StringUtils';
import {
getDescription,
getMerchant,
hasPendingUI,
isCardTransaction,
isPartialMerchant,
isPending,
isReceiptBeingScanned,
shouldShowBrokenConnectionViolation as shouldShowBrokenConnectionViolationTransactionUtils,
shouldShowRTERViolationMessage,
} from '@libs/TransactionUtils';
import type {ContextMenuAnchor} from '@pages/home/report/ContextMenu/ReportActionContextMenu';
import variables from '@styles/variables';
Expand Down Expand Up @@ -142,11 +143,9 @@ function ReportPreview({
}: ReportPreviewProps) {
const policy = usePolicy(policyID);
const [chatReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${chatReportID}`);
const [iouReport] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${iouReportID}`);
const [transactions] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {
selector: (_transactions) => reportTransactionsSelector(_transactions, iouReportID),
});
const [transactionViolations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS);
const [iouReport, transactions, violations] = useReportWithTransactionsAndViolations(iouReportID);
const lastTransaction = transactions?.at(0);
const transactionIDList = transactions?.map((reportTransaction) => reportTransaction.transactionID) ?? [];
const [userWallet] = useOnyx(ONYXKEYS.USER_WALLET);
const [invoiceReceiverPolicy] = useOnyx(
`${ONYXKEYS.COLLECTION.POLICY}${chatReport?.invoiceReceiver && 'policyID' in chatReport.invoiceReceiver ? chatReport.invoiceReceiver.policyID : CONST.DEFAULT_NUMBER_ID}`,
Expand Down Expand Up @@ -228,17 +227,16 @@ function ReportPreview({
const hasErrors =
(hasMissingSmartscanFields && !iouSettled) ||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
hasViolations(iouReportID, transactionViolations, true) ||
hasNoticeTypeViolations(iouReportID, transactionViolations, true) ||
hasWarningTypeViolations(iouReportID, transactionViolations, true) ||
hasViolations(iouReportID, violations, true) ||
hasNoticeTypeViolations(iouReportID, violations, true) ||
hasWarningTypeViolations(iouReportID, violations, true) ||
(isReportOwner(iouReport) && hasReportViolations(iouReportID)) ||
hasActionsWithErrors(iouReportID);
const lastThreeTransactions = transactions?.slice(-3) ?? [];
const lastTransaction = transactions?.at(0);
const lastThreeReceipts = lastThreeTransactions.map((transaction) => ({...getThumbnailAndImageURIs(transaction), transaction}));
const transactionIDList = transactions?.map((reportTransaction) => reportTransaction.transactionID) ?? [];
const showRTERViolationMessage = shouldShowRTERViolationMessage(transactions);
const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, iouReport, policy);
const lastTransactionViolations = useTransactionViolations(lastTransaction?.transactionID);
const showRTERViolationMessage = numberOfRequests === 1 && hasPendingUI(lastTransaction, lastTransactionViolations);
const shouldShowBrokenConnectionViolation = numberOfRequests === 1 && shouldShowBrokenConnectionViolationTransactionUtils(transactionIDList, iouReport, policy, violations);
let formattedMerchant = numberOfRequests === 1 ? getMerchant(lastTransaction) : null;
const formattedDescription = numberOfRequests === 1 ? getDescription(lastTransaction) : null;

Expand All @@ -249,8 +247,7 @@ function ReportPreview({
const isArchived = isArchivedReportWithID(iouReport?.reportID);
const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN;
const filteredTransactions = transactions?.filter((transaction) => transaction) ?? [];
const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions, transactionViolations);

const shouldShowSubmitButton = canSubmitReport(iouReport, policy, filteredTransactions, violations);
const shouldDisableSubmitButton = shouldShowSubmitButton && !isAllowedToSubmitDraftExpenseReport(iouReport);

// The submit button should be success green colour only if the user is submitter and the policy does not have Scheduled Submit turned on
Expand Down
24 changes: 24 additions & 0 deletions src/hooks/useReportWithTransactionsAndViolations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import {useOnyx} from 'react-native-onyx';
import type {OnyxCollection, OnyxEntry} from 'react-native-onyx';
import {reportTransactionsSelector} from '@libs/ReportUtils';
import CONST from '@src/CONST';
import ONYXKEYS from '@src/ONYXKEYS';
import type {Report, Transaction, TransactionViolation} from '@src/types/onyx';

function useReportWithTransactionsAndViolations(reportID?: string): [OnyxEntry<Report>, Transaction[], OnyxCollection<TransactionViolation[]>] {
const [report] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT}${reportID ?? CONST.DEFAULT_NUMBER_ID}`);
const [transactions = []] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION, {
selector: (_transactions) => reportTransactionsSelector(_transactions, reportID),
});
const [violations] = useOnyx(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, {

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.

This was causing the the submit button to be shown briefly before the review button so we added transactions as a dependency here (#62981)

selector: (allViolations) =>
Object.fromEntries(
Object.entries(allViolations ?? {}).filter(([key]) =>
transactions.some((transaction) => transaction.transactionID === key.replace(ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS, '')),

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 PR caused #63155 (comment)

),
),
});
return [report, transactions, violations];
}

export default useReportWithTransactionsAndViolations;
Loading