From 2f90dd174d9154df9da609e5d102d4d005672659 Mon Sep 17 00:00:00 2001 From: VickyStash Date: Mon, 14 Oct 2024 14:15:33 +0300 Subject: [PATCH 1/7] Add hasBrokenConnectionViolation and getBrokenConnectionDescription functions --- src/languages/en.ts | 4 +++ src/languages/es.ts | 4 +++ src/libs/TransactionUtils/index.ts | 52 ++++++++++++++++++++++++++++-- 3 files changed, 58 insertions(+), 2 deletions(-) diff --git a/src/languages/en.ts b/src/languages/en.ts index 7f3a5a365142..2fcb51922583 100755 --- a/src/languages/en.ts +++ b/src/languages/en.ts @@ -4688,6 +4688,10 @@ const translations = { return ''; }, + brokenConnection530Error: 'Receipt pending due to broken bank connection.', + adminBrokenConnectionError: 'Receipt pending due to broken bank connection. Please resolve in ', + memberBrokenConnectionError: 'Receipt pending due to broken bank connection. Please ask a workspace admin to resolve.', + markAsCashToIgnore: 'Mark as cash to ignore and request payment.', smartscanFailed: 'Receipt scanning failed. Enter details manually.', someTagLevelsRequired: ({tagName}: ViolationsTagOutOfPolicyParams = {}) => `Missing ${tagName ?? 'Tag'}`, tagOutOfPolicy: ({tagName}: ViolationsTagOutOfPolicyParams = {}) => `${tagName ?? 'Tag'} no longer valid`, diff --git a/src/languages/es.ts b/src/languages/es.ts index d4615b26a255..c483f26b8261 100644 --- a/src/languages/es.ts +++ b/src/languages/es.ts @@ -5199,6 +5199,10 @@ const translations = { } return ''; }, + brokenConnection530Error: 'Recibo pendiente debido a una conexión bancaria rota.', + adminBrokenConnectionError: 'Recibo pendiente debido a una conexión bancaria rota. Por favor, resuélvelo en ', + memberBrokenConnectionError: 'Recibo pendiente debido a una conexión bancaria rota. Por favor, pide a un administrador del espacio de trabajo que lo resuelva.', + markAsCashToIgnore: 'Márcalo como efectivo para ignorar y solicitar el pago.', smartscanFailed: 'No se pudo escanear el recibo. Introduce los datos manualmente', someTagLevelsRequired: ({tagName}: ViolationsTagOutOfPolicyParams = {}) => `Falta ${tagName ?? 'Tag'}`, tagOutOfPolicy: ({tagName}: ViolationsTagOutOfPolicyParams = {}) => `La etiqueta ${tagName ? `${tagName} ` : ''}ya no es válida`, diff --git a/src/libs/TransactionUtils/index.ts b/src/libs/TransactionUtils/index.ts index 11516af54b28..96ca91158eda 100644 --- a/src/libs/TransactionUtils/index.ts +++ b/src/libs/TransactionUtils/index.ts @@ -20,7 +20,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import type {IOURequestType} from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import type {Beta, OnyxInputOrEntry, Policy, RecentWaypoint, ReviewDuplicates, TaxRate, TaxRates, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; +import type {Beta, OnyxInputOrEntry, Policy, RecentWaypoint, Report, ReviewDuplicates, TaxRate, TaxRates, Transaction, TransactionViolation, TransactionViolations} from '@src/types/onyx'; import type {Comment, Receipt, TransactionChanges, TransactionPendingFieldsKey, Waypoint, WaypointCollection} from '@src/types/onyx/Transaction'; import type DeepValueOf from '@src/types/utils/DeepValueOf'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; @@ -583,7 +583,53 @@ function getTransactionViolations(transactionID: string, transactionViolations: * Check if there is pending rter violation in transactionViolations. */ function hasPendingRTERViolation(transactionViolations?: TransactionViolations | null): boolean { - return !!transactionViolations?.some((transactionViolation: TransactionViolation) => transactionViolation.name === CONST.VIOLATIONS.RTER && transactionViolation.data?.pendingPattern); + return !!transactionViolations?.some( + (transactionViolation: TransactionViolation) => + transactionViolation.name === CONST.VIOLATIONS.RTER && + transactionViolation.data?.pendingPattern && + transactionViolation.data?.rterType !== CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION && + transactionViolation.data?.rterType !== CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530, + ); +} + +/** + * Check if there is broken connection violation. + */ +function hasBrokenConnectionViolation(transactionID: string): boolean { + const violations = getTransactionViolations(transactionID, allTransactionViolations); + return !!violations?.find( + (violation) => + violation.name === CONST.VIOLATIONS.RTER && + (violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION || violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530), + ); +} + +/** + * Returns broken connection description. + */ +function getBrokenConnectionDescription(transactionID: string, report: OnyxEntry, policy: OnyxEntry) { + const violations = getTransactionViolations(transactionID, allTransactionViolations); + const brokenConnection530Error = violations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION_530); + const brokenConnectionError = violations?.find((violation) => violation.data?.rterType === CONST.RTER_VIOLATION_TYPES.BROKEN_CARD_CONNECTION); + const isPolicyAdmin = PolicyUtils.isPolicyAdmin(policy); + + if (!brokenConnection530Error && !brokenConnectionError) { + return ''; + } + + if (brokenConnection530Error) { + return Localize.translateLocal('violations.brokenConnection530Error'); + } + + if (isPolicyAdmin) { + return `${Localize.translateLocal('violations.adminBrokenConnectionError')}`; + } + + if (ReportUtils.isReportApproved(report) || ReportUtils.isReportManuallyReimbursed(report) || (ReportUtils.isProcessingReport(report) && !PolicyUtils.isInstantSubmitEnabled(policy))) { + return Localize.translateLocal('violations.memberBrokenConnectionError'); + } + + return `${Localize.translateLocal('violations.memberBrokenConnectionError')}${Localize.translateLocal('violations.markAsCashToIgnore')}`; } /** @@ -1141,6 +1187,8 @@ export { getRecentTransactions, hasReservationList, hasViolation, + hasBrokenConnectionViolation, + getBrokenConnectionDescription, hasNoticeTypeViolation, hasWarningTypeViolation, hasModifiedAmountOrDateViolation, From 7bce745417cf9bc1f883254be4a71603060a4cfc Mon Sep 17 00:00:00 2001 From: VickyStash Date: Tue, 15 Oct 2024 14:49:38 +0300 Subject: [PATCH 2/7] Add broken connection violation warning display --- .../BrokenConnectionDescription.tsx | 62 +++++++++++++++++++ src/components/MoneyReportHeader.tsx | 30 +++++++-- src/components/MoneyRequestHeader.tsx | 22 ++++++- .../MoneyRequestHeaderStatusBar.tsx | 4 +- .../ReportActionItem/ReportPreview.tsx | 10 ++- src/libs/TransactionUtils/index.ts | 32 +++------- 6 files changed, 122 insertions(+), 38 deletions(-) create mode 100644 src/components/BrokenConnectionDescription.tsx diff --git a/src/components/BrokenConnectionDescription.tsx b/src/components/BrokenConnectionDescription.tsx new file mode 100644 index 000000000000..866e547e50a0 --- /dev/null +++ b/src/components/BrokenConnectionDescription.tsx @@ -0,0 +1,62 @@ +import React from 'react'; +import type {OnyxEntry} from 'react-native-onyx'; +import {useOnyx} from 'react-native-onyx'; +import TextLink from '@components/TextLink'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as PolicyUtils from '@libs/PolicyUtils'; +import * as ReportUtils from '@libs/ReportUtils'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {Policy, Report} from '@src/types/onyx'; + +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) { + return ( + <> + {`${translate('violations.adminBrokenConnectionError')}`} + {}} + >{`${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..a0383aeb80bd 100644 --- a/src/components/MoneyReportHeader.tsx +++ b/src/components/MoneyReportHeader.tsx @@ -2,6 +2,7 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import type {OnyxEntry} from 'react-native-onyx'; import {useOnyx} from 'react-native-onyx'; +import BrokenConnectionDescription from '@components/BrokenConnectionDescription'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useResponsiveLayout from '@hooks/useResponsiveLayout'; @@ -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,29 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea const onlyShowPayElsewhere = useMemo(() => !canIOUBePaid && getCanIOUBePaid(true), [canIOUBePaid, getCanIOUBePaid]); + const shouldShowMarkAsCashButton = hasAllPendingRTERViolations || (shouldShowBrokenConnectionViolation && !PolicyUtils.isPolicyAdmin(policy)); + 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 +234,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 +354,7 @@ function MoneyReportHeader({policy, report: moneyRequestReport, transactionThrea /> )} - {hasAllPendingRTERViolations && !shouldUseNarrowLayout && ( + {shouldShowMarkAsCashButton && !shouldUseNarrowLayout && (