diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx new file mode 100644 index 000000000000..c54bd0058f99 --- /dev/null +++ b/src/components/BrokenConnectionDescription.tsx @@ -0,0 +1,65 @@ +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 * as PolicyUtils from '@libs/PolicyUtils'; +import * as ReportUtils 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'; + +type BrokenConnectionDescriptionProps = { + /** Transaction id of the corresponding report */ + transactionID: string; + + /** Current report */ + report: OnyxEntry; + + /** Policy which the report is tied to */ + policy: OnyxEntry; +}; + +function BrokenConnectionDescription({transactionID, policy, report}: BrokenConnectionDescriptionProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const [transactionViolations] = useOnyx(`${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${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); + const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); + + if (!brokenConnection530Error && !brokenConnectionError) { + return ''; + } + + if (brokenConnection530Error) { + return translate('violations.brokenConnection530Error'); + } + + if (isPolicyAdmin && !ReportUtils.isCurrentUserSubmitter(report?.reportID ?? '')) { + return ( + <> + {`${translate('violations.adminBrokenConnectionError')}`} + Navigation.navigate(ROUTES.WORKSPACE_COMPANY_CARDS.getRoute(policy?.id ?? '-1'))} + >{`${translate('workspace.common.companyCards')}`} + . + + ); + } + + if (ReportUtils.isReportApproved(report) || ReportUtils.isReportManuallyReimbursed(report) || (ReportUtils.isProcessingReport(report) && !PolicyUtils.isInstantSubmitEnabled(policy))) { + return translate('violations.memberBrokenConnectionError'); + } + + return `${translate('violations.memberBrokenConnectionError')} ${translate('violations.markAsCashToIgnore')}`; +} + +BrokenConnectionDescription.displayName = 'BrokenConnectionDescription'; + +export default BrokenConnectionDescription; diff --git a/src/components/MoneyReportHeader.tsx b/src/components/MoneyReportHeader.tsx index fdf6f8edd825..6fbe8bd33839 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -25,6 +25,7 @@ import type * as OnyxTypes from '@src/types/onyx'; import type {PaymentMethodType} from '@src/types/onyx/OriginalMessage'; import type IconAsset from '@src/types/utils/IconAsset'; import isLoadingOnyxValue from '@src/types/utils/isLoadingOnyxValue'; +import BrokenConnectionDescription from './BrokenConnectionDescription'; import Button from './Button'; import ConfirmModal from './ConfirmModal'; import DelegateNoAccessModal from './DelegateNoAccessModal'; @@ -108,6 +109,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const hasOnlyPendingTransactions = allTransactions.length > 0 && allTransactions.every((t) => TransactionUtils.isExpensifyCardTransaction(t) && TransactionUtils.isPending(t)); const transactionIDs = allTransactions.map((t) => t.transactionID); const hasAllPendingRTERViolations = TransactionUtils.allHavePendingRTERViolation([transaction?.transactionID ?? '-1']); + const shouldShowBrokenConnectionViolation = TransactionUtils.shouldShowBrokenConnectionViolation(transaction?.transactionID ?? '-1', moneyRequestReport, policy); const hasOnlyHeldExpenses = ReportUtils.hasOnlyHeldExpenses(moneyRequestReport?.reportID ?? ''); const isPayAtEndExpense = TransactionUtils.isPayAtEndExpense(transaction); const isArchivedReport = ReportUtils.isArchivedRoomWithID(moneyRequestReport?.reportID); @@ -121,25 +123,31 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const onlyShowPayElsewhere = useMemo(() => !canIOUBePaid && getCanIOUBePaid(true), [canIOUBePaid, getCanIOUBePaid]); + const shouldShowMarkAsCashButton = + hasAllPendingRTERViolations || + (shouldShowBrokenConnectionViolation && (!PolicyUtils.isPolicyAdmin(policy) || ReportUtils.isCurrentUserSubmitter(moneyRequestReport?.reportID ?? ''))); + const shouldShowPayButton = canIOUBePaid || onlyShowPayElsewhere; const shouldShowApproveButton = useMemo(() => IOU.canApproveIOU(moneyRequestReport, policy), [moneyRequestReport, policy]); const shouldDisableApproveButton = shouldShowApproveButton && !ReportUtils.isAllowedToApproveExpenseReport(moneyRequestReport); - const shouldShowSubmitButton = !!moneyRequestReport && isDraft && reimbursableSpend !== 0 && !hasAllPendingRTERViolations; + const shouldShowSubmitButton = !!moneyRequestReport && isDraft && reimbursableSpend !== 0 && !hasAllPendingRTERViolations && !shouldShowBrokenConnectionViolation; const isAdmin = policy?.role === CONST.POLICY.ROLE.ADMIN; const shouldShowExportIntegrationButton = !shouldShowPayButton && !shouldShowSubmitButton && connectedIntegration && isAdmin && ReportUtils.canBeExported(moneyRequestReport); - const shouldShowSettlementButton = (shouldShowPayButton || shouldShowApproveButton) && !hasAllPendingRTERViolations && !shouldShowExportIntegrationButton; + const shouldShowSettlementButton = + (shouldShowPayButton || shouldShowApproveButton) && !hasAllPendingRTERViolations && !shouldShowExportIntegrationButton && !shouldShowBrokenConnectionViolation; const shouldDisableSubmitButton = shouldShowSubmitButton && !ReportUtils.isAllowedToSubmitDraftExpenseReport(moneyRequestReport); const isFromPaidPolicy = policyType === CONST.POLICY.TYPE.TEAM || policyType === CONST.POLICY.TYPE.CORPORATE; - const shouldShowStatusBar = hasAllPendingRTERViolations || hasOnlyHeldExpenses || hasScanningReceipt || isPayAtEndExpense || hasOnlyPendingTransactions; + const shouldShowStatusBar = + hasAllPendingRTERViolations || shouldShowBrokenConnectionViolation || hasOnlyHeldExpenses || hasScanningReceipt || isPayAtEndExpense || hasOnlyPendingTransactions; const shouldShowNextStep = !ReportUtils.isClosedExpenseReportWithNoExpenses(moneyRequestReport) && isFromPaidPolicy && !!nextStep?.message?.length && !shouldShowStatusBar; const shouldShowAnyButton = - shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || hasAllPendingRTERViolations || shouldShowExportIntegrationButton; + shouldShowSettlementButton || shouldShowApproveButton || shouldShowSubmitButton || shouldShowNextStep || shouldShowMarkAsCashButton || shouldShowExportIntegrationButton; const bankAccountRoute = ReportUtils.getBankAccountRoute(chatReport); const formattedAmount = CurrencyUtils.convertToDisplayString(reimbursableSpend, moneyRequestReport?.currency); const [nonHeldAmount, fullAmount] = ReportUtils.getNonHeldAndFullAmount(moneyRequestReport, policy); @@ -228,6 +236,18 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea if (hasOnlyHeldExpenses) { return {icon: getStatusIcon(Expensicons.Stopwatch), description: translate('iou.expensesOnHold')}; } + if (shouldShowBrokenConnectionViolation) { + return { + icon: getStatusIcon(Expensicons.Hourglass), + description: ( + + ), + }; + } if (hasAllPendingRTERViolations) { return {icon: getStatusIcon(Expensicons.Hourglass), description: translate('iou.pendingMatchWithCreditCardDescription')}; } @@ -336,7 +356,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea /> )} - {hasAllPendingRTERViolations && !shouldUseNarrowLayout && ( + {shouldShowMarkAsCashButton && !shouldUseNarrowLayout && (